复习

  1. 建表规范
    1. 从需求去分析需要哪些字段
    2. 注释
    3. 命名规范,不要拼音,不要缩写,见名知意,下划线分词
    4. 字段的数据类型选择
      1. 主键 自增 int型
    5. 添加时间、更新时间字段,用于增量同步数据
    6. 关联关系,1v1任意方 1vm存在多一方,多对多中间表
  2. 如何编码
    1. 打印日志
    2. 参数校验
    3. 业务处理
    4. 异常处理
    5. 返回结果
  3. restful复习
    1. URL,每个资源有唯一的地址
    2. 用http协议的请求方法代表对资源的增删改查 api设计
  4. 三层架构复习
    1. 控制层 流程调度+web处理
    2. 服务层 业务逻辑 [厚]
    3. 数据库访问层 数据库访问
  5. spring-boot核心注解 @SpringBootApplication
    1. @SpringBootConfiguration 配置类
    2. @EnableAutoConfiguration 开启自动配置类
    3. @ComponentScan 组件扫描
  6. 一些约定
    1. XXApplication放在根包下
    2. static存放静态资源
    3. templates 存放动态模板
  7. 整合mybatisplus
    1. 加依赖 mybatis-plus-boot-starter
    2. 改配置 mybatis-plus:
    3. 注解 @MapperScan
    4. 插件 配置类,添加插件
  8. thymeleaf后端模板引擎
    1. 两种开发模式
      1. 传统模式 前后端不分离 模板渲染在后端处理
      2. 前后端分离 第一次请求获取到页面,浏览器解析页面时,发送ajax请求从后端获取数据,并将数据解析填充到页面
    2. 使用Thymeleaf 的是传统模式,模板是在后端渲染的

作业:

  • 设计课程course、章chapter、*节lesson,产出建表sql
  • 实现一个课程分页搜索,支持根据课程类别,收费方式进行搜索,spring-boot+mybatis-plus

    日志框架

  1. 日志的作用
    1. 定位问题,查看具体的报错信息
      1. 记录入参和出参
      2. 输出debug信息
      3. 记录错误信息
    2. 跟踪执行轨迹
    3. 记录数据,用于数据统计处理
  2. 如何打印日志
    1. 日志可以输出为文件;分级别输出到文件 xxx.info.log xxx.error.log
    2. 需要打日志的位置:
      1. controller层,系统入口处,表示的是用户传入的参数
      2. controller层,返回值,可选
      3. 出现异常
      4. 业务关键位置打印日志,比如:校验商品数量是够足够;校验用户状态;金额;风控;下订单;
    3. 日志级别 trace->debug->info->warn->error
      1. trace 追踪
      2. debug 调试信息
      3. info 正常信息:打印执行过程
      4. warn 业务异常:业务状态不正常,执行异常终止
      5. error 系统异常:内存溢出,磁盘已满,数据库无法连接
    4. 日志级别是可调整的,新上线系统考虑将日志级别调低,输出更多信息,掌握更全面的情况;一段时间比较稳定运行,考虑调高日志级别
    5. 打印日志,需要消耗性能,占据磁盘空间
  3. spring-boot
    1. 自带日志框架 slf4j+logback ,直接使用 ```java private Logger log = LoggerFactory.getLogger(XXX.class);

log.debug(xxx); log.info(xxxx);

//使用lombok @Slf4j注解 直接使用log打印日志

  1. 1. 日志级别设置
  2. ```yaml
  3. logging:
  4. level:
  5. root: debug # 所有的日志都设为debug
  6. logging:
  7. level:
  8. org.mybatis: debug
  9. com.example.demo56: debug # 指定某个包的日志级别
  1. 日志api的使用
  • 带参数使用 {} 占位 log.info(“日志,支持用 {} 占位”,参数);
  • 输出异常
    1. //错误示例,性能极差
    2. log.error(exception);
    3. log.error(exception.getStackTrace());
    4. //正确示例,性能较优
    5. log.error("异常信息/报错提示",exception);
  1. 常用日志框架
  • jul=> java.util.logging java官方日志记录工具,性能差,已废弃
  • log4j 性能无法持续提升,使用较少
  • slf4j的日志记录API(门面),提供了默认实现 logback ,这套组合目前是主流
    • 通过适配,支持jul,log4j等日志框架
  • log4j2 ,log4j升级,优化了性能

2021年6月17日 Shiro - 图1

安全框架Shiro

目标: Apache Shiro™是一种强大而易于使用的Java安全框架,用于执行身份验证,授权,加密和会话管理。 使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序 - 从最小的移动应用程序到最大的Web和企业应用程序。

shiro架构.png

Authentication:authc 身份认证/登录,验证用户是不是合法用户
Authorization:authz 授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
Session Management:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Support:Web 支持,可以非常容易的集成到 Web 环境;
Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率

Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
Testing:提供测试支持;
Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

简单示例

  1. 依赖

    1. <dependency>
    2. <groupId>org.apache.shiro</groupId>
    3. <artifactId>shiro-core</artifactId>
    4. <version>1.4.1</version>
    5. </dependency>
    6. <dependency>
    7. <groupId>org.slf4j</groupId>
    8. <artifactId>jcl-over-slf4j</artifactId>
    9. <version>1.5.6</version>
    10. </dependency>
    11. <dependency>
    12. <groupId>commons-logging</groupId>
    13. <artifactId>commons-logging</artifactId>
    14. <version>1.0</version>
    15. </dependency>
  2. 配置shiro.ini

    1. [users]
    2. zhangsan=wohenshuai
    3. wangwu=wohenku
  3. 编写测试代码 ```java package com.woniuxy.shiro;

import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory;

import org.apache.shiro.mgt.SecurityManager;

/**

  • Hello world! / public class App { public static void main( String[] args ) {

    1. //得到SecurityManager
    2. Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
    3. SecurityManager securityManager = factory.getInstance();
    4. SecurityUtils.setSecurityManager(securityManager);
    5. //得到Subject
    6. Subject currUser = SecurityUtils.getSubject();
    7. //登录操作:相当于用户输入
    8. UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "1wohenshuai");
    9. try {
    10. currUser.login(token);
    11. System.out.println("认证成功");
    12. }catch (AuthenticationException e){
    13. System.out.println("认证失败");
    14. e.printStackTrace();
    15. }

    } }

  1. <a name="7D4JW"></a>
  2. ## 授权
  3. RBAC: Role-Based Acess Control 基于角色的权限控制
  4. 角色 Role = 多个权限的集合<br />权限 Permission
  5. 校长是一个角色,拥有多个权限,包括开除讲师,开除咨询师,开除学生,涨工资,招人,解散学校,不上晚自习
  6. RBAC= 基于角色,意思是,授权是以角色为单位,不直接给某个用户授权,而是通过授予角色的方式达到授权的目的
  7. 一个用户可以对应多个角色,一个角色可以被授予给多个用户 用户与角色: 多对多<br />一个角色可以对应多个权限,一个权限可以被分配给多个角色 角色与权限: 多对多
  8. 用户 <---用户角色表----> 角色 <-----角色权限表-----> 权限
  9. 1. 修改shiro.ini
  10. ```bash
  11. [roles]
  12. headmaster=fire_teacher,fire_counselor,keepclean
  13. teacher=teach,take_break
  14. [users]
  15. zhangsan=wohenshuai,headmaster
  16. wangwu=wohenku,teacher
  1. 编写测试代码 ```java //判断是否有角色 boolean headmaster = currUser.hasRole(“headmaster”); System.out.println(“是否是校长:” + headmaster); boolean teacher = currUser.hasRole(“teacher”); System.out.println(“是否是teacher:” + teacher); //判断是否有权限 boolean[] permitted = currUser.isPermitted(“fire_teacher”, “keepclean”); System.out.println(“是否拥有权限: \”fire_teacher\”, \”keepclean\” “+ Arrays.toString(permitted));

boolean permitted2 = currUser.isPermitted(“teach”); System.out.println(“是否拥有权限: teach “+permitted2);

  1. <a name="fDeML"></a>
  2. ## shiro自定义Realm
  3. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/953441/1623914298421-49a9054c-6175-4566-985c-26afd5c9d24d.png#height=561&id=Hypi7&margin=%5Bobject%20Object%5D&name=image.png&originHeight=561&originWidth=854&originalType=binary&ratio=1&size=263442&status=done&style=none&width=854)
  4. 1. 建表
  5. 1. 自定义realm
  6. ```java
  7. package com.woniuxy.shiro;
  8. import org.apache.shiro.authc.*;
  9. import org.apache.shiro.authz.AuthorizationInfo;
  10. import org.apache.shiro.realm.AuthorizingRealm;
  11. import org.apache.shiro.subject.PrincipalCollection;
  12. public class MyRealm extends AuthorizingRealm {
  13. /**
  14. * 获取授权信息
  15. * @param principals
  16. * @return
  17. */
  18. @Override
  19. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  20. return null;
  21. }
  22. /**
  23. * 获取认证信息
  24. * @param token
  25. * @return
  26. * @throws AuthenticationException
  27. */
  28. @Override
  29. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
  30. if(!(token!=null&& token instanceof UsernamePasswordToken)) {
  31. throw new AuthenticationException("用户名密码不正确");
  32. }
  33. //转换成UsernamePasswordToken
  34. UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
  35. String username = usernamePasswordToken.getUsername();
  36. //TODO 查询数据库
  37. User user = new User();
  38. user.setUsername(username);
  39. user.setPassword("abc123");
  40. user.setMobile("13232342342");
  41. // 第一个参数 用户信息
  42. //第二个参数 密码
  43. //第三个参数 Realm的名字
  44. return new SimpleAuthenticationInfo(user,user.getPassword(),getName());
  45. }
  46. }
  1. 配置shiro-my.ini

    1. myRealm=com.woniuxy.shiro.MyRealm
    2. securityManager.realms=$myRealm
  2. 测试代码一样的,配置改为shiro-my.ini

  3. 接入mybatis

    1. <dependency>
    2. <groupId>org.mybatis</groupId>
    3. <artifactId>mybatis</artifactId>
    4. <version>3.5.3</version>
    5. </dependency>
    6. <dependency>
    7. <groupId>mysql</groupId>
    8. <artifactId>mysql-connector-java</artifactId>
    9. <version>5.1.48</version>
    10. </dependency>

    ```xml <?xml version=”1.0” encoding=”UTF-8” ?> <!DOCTYPE configuration

    1. PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    2. "http://mybatis.org/dtd/mybatis-3-config.dtd">

  1. ```java
  2. private static SqlSessionFactory sqlSessionFactory;
  3. static{
  4. //读配置文件
  5. InputStream resourceAsStream = null;
  6. try {
  7. resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
  8. } catch (IOException e) {
  9. e.printStackTrace();
  10. }
  11. //构建SQLSessionFactory
  12. sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
  13. }
  14. /**
  15. * 获取授权信息
  16. * @param principals
  17. * @return
  18. */
  19. @Override
  20. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  21. return null;
  22. }
  23. /**
  24. * 获取认证信息
  25. * @param token
  26. * @return
  27. * @throws AuthenticationException
  28. */
  29. @Override
  30. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
  31. if(!(token!=null&& token instanceof UsernamePasswordToken)) {
  32. throw new AuthenticationException("用户名密码不正确");
  33. }
  34. //转换成UsernamePasswordToken
  35. UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
  36. String username = usernamePasswordToken.getUsername();
  37. SqlSession sqlSession = sqlSessionFactory.openSession();
  38. UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  39. User user = mapper.findByUsername(username);
  40. sqlSession.close();
  41. if(user==null){
  42. throw new UnknownAccountException(username+"不存在");
  43. }
  44. // 第一个参数 用户信息
  45. //第二个参数 密码
  46. //第三个参数 Realm的名字
  47. return new SimpleAuthenticationInfo(user,user.getPassword(),getName());
  48. }

principal 用户
credentials 凭据,密码

授权相关

  1. /**
  2. * 获取授权信息
  3. * @param principals
  4. * @return 授权信息(角色、权限)
  5. */
  6. @Override
  7. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  8. System.out.println("进入授权方法:"+principals);
  9. //获取到用户
  10. Object primaryPrincipal = principals.getPrimaryPrincipal();
  11. if(primaryPrincipal==null|| !(primaryPrincipal instanceof User)){
  12. throw new AuthorizationException("Principal不存在或者不合法,"+primaryPrincipal);
  13. }
  14. User user = (User) primaryPrincipal;
  15. //TODO 根据用户查询相应的角色与权限
  16. //封装成AuthorizationInfo,并返回
  17. SimpleAuthorizationInfo authzInfo = new SimpleAuthorizationInfo();
  18. //添加角色
  19. Set<String> roles = new HashSet<>();
  20. roles.add("headmaster");
  21. authzInfo.setRoles(roles);
  22. //添加权限
  23. Set<String> permissions = new HashSet<>();
  24. permissions.add("keepclean");
  25. permissions.add("fire_teacher");
  26. authzInfo.setStringPermissions(permissions);
  27. System.out.println("离开授权方法:"+authzInfo);
  28. return authzInfo;
  29. }
  1. package com.woniuxy.shiro.mapper;
  2. import com.woniuxy.shiro.entity.Role;
  3. import com.woniuxy.shiro.entity.User;
  4. import org.apache.ibatis.annotations.Param;
  5. import org.apache.ibatis.annotations.Select;
  6. import java.util.List;
  7. import java.util.Set;
  8. public interface UserMapper {
  9. @Select("select * from edu_user where username=#{username}")
  10. User findByUsername(String username);
  11. @Select("select r.role_id, r.role_name from edu_role r " +
  12. "join edu_user_role ur on r.role_id = ur.role_id " +
  13. "where ur.user_id = #{userId}")
  14. Set<Role> findRolesByUserId(int userId);
  15. @Select("<script>" +
  16. "select p.permission_name\n" +
  17. "from edu_permission p\n" +
  18. " join edu_role_permission rp on p.permission_id = rp.permission_id\n" +
  19. "where rp.role_id in " +
  20. "<foreach collection='roleIds' open='(' item='roleId' separator=',' close=')'> #{roleId}</foreach> " +
  21. "</script>")
  22. Set<String> findPermissionByRoleIds(@Param("roleIds") List<Integer> roleIds);
  23. }

https://mybatis.org/mybatis-3/zh/dynamic-sql.html

作业:

  1. 完成RBAC5张表的建表SQL
  2. 完成shiro的自定义realm