# HelloSpringBoot **Repository Path**: cola0223/HelloSpringBoot ## Basic Information - **Project Name**: HelloSpringBoot - **Description**: HelloSpringBoot - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-03-01 - **Last Updated**: 2023-03-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # HelloSpringBoot --- 用于记录 SpringBoot 学习过程中的一些技术的入门级项目 > - [x] MyBatisPlus 入门 > > - [x] Knife4J 入门 > > - [x] 全局异常处理器 入门 ## 一、 MyBatisPlus 入门 ### 3.1 简介 [MyBatis-Plus](https://baomidou.com/)(简称 MP)是一个MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。 ### 3.2 特性 ![](https://cola-picgo-1311841992.cos.ap-beijing.myqcloud.com/20230307143548.png) - **无侵入**:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑 - **损耗小**:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作 - **强大的 CRUD 操作**:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求 - **支持 Lambda 形式调用**:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错 - **支持主键自动生成**:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题 - **支持 ActiveRecord 模式**:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作 - **支持自定义全局通用操作**:支持全局通用方法注入( Write once, use anywhere ) - **内置代码生成器**:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用 - **内置分页插件**:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询 - **分页插件支持多种数据库**:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库 - **内置性能分析插件**:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询 - **内置全局拦截插件**:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作 ### 3.3 使用方法 下面我们将介绍 MP 的基础使用,[项目地址:HelloSpringBoot]([HelloSpringBoot: HelloSpringBoot (gitee.com)](https://gitee.com/cola0223/HelloSpringBoot)) #### 3.3.1 引入依赖 ```xml com.baomidou mybatis-plus-boot-starter 3.4.1 ``` #### 3.3.2 配置数据库信息 application.yml ```xml spring: application: name: HelloSpringBoot profiles: active: dev ``` application-dev.yml ```xml server: port: 8092 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 查看日志 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://8.130.21.128:3306/HelloSpringBootMySql?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=utf-8 username: root password: root jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 ``` #### 3.3.3 启动类 ```java @SpringBootApplication @Slf4j public class HelloSpringBootApplication { public static void main(String[] args) { SpringApplication.run(HelloSpringBootApplication.class, args); log.info("HelloSpringBoot 项目启动成功"); } } ``` #### 3.3.4 实体类 [MP 实体类注解官方详细文档](https://baomidou.com/pages/223848/) | 注解名 | 说明 | |-------------|------------------------------------| | @TableName | 表名注解,标识实体类对应的表 | | @TableId | 主键注解,type = IdType.AUTO(数据库 ID 自增) | | @TableField | 字段注解(非主键) | | @TableLogic | 逻辑删除 | ```java @Data @TableName("sys_user") public class User extends BaseEntity { private static final long serialVersionUID = 1L; @TableField("username") private String username; @TableField("password") private String password; } ``` #### 3.3.5 Mapper 类 ```java @Mapper public interface UserMapper extends BaseMapper { } ``` #### 3.3.6 Service 类 ```java public interface UserService extends IService { } ``` ```java @Service public class UserServiceImpl extends ServiceImpl implements UserService { } ``` #### 3.3.7 分页插件的配置 ```java @Configuration public class MyBatisPlusPagePlugin { /** * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false * 避免缓存出现问题(该属性会在旧插件移除后一同移除) * * @return MybatisPlusInterceptor */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } @Bean public ConfigurationCustomizer configurationCustomizer() { return configuration -> configuration.setUseDeprecatedExecutor(false); } } ``` #### 3.3.8 MyBatisPlus 条件构造器 ![](https://cola-picgo-1311841992.cos.ap-beijing.myqcloud.com/20230307172412.png) - **Wrapper** : 条件构造抽象类,最顶端父类 - **AbstractWrapper** : 用于查询条件封装,生成 sql 的 where 条件 - **QueryWrapper** : Entity 对象封装操作类,不是用lambda语法 - **UpdateWrapper** : Update 条件封装,用于Entity对象更新操作 - **AbstractLambdaWrapper** : Lambda 语法使用 Wrapper统一处理解析 lambda 获取 column。 - **LambdaQueryWrapper** :Lambda查询Wrapper - **LambdaUpdateWrapper** : Lambda 更新封装Wrapper ### 3.4 测试 #### 3.4.1 条件分页查询 ```java /** * 条件分页查询 * * @return Result */ @GetMapping("{page}/{limit}") public Result pageQueryUser(@PathVariable Long page, @PathVariable Long limit, UserQueryVo UserQueryVo){ /* 创建 PageInfo 对象 */ Page pageInfo=new Page<>(page,limit); /* 封装查询条件 */ LambdaQueryWrapper wrapper=new LambdaQueryWrapper<>(); String userName=UserQueryVo.getUserName(); if(!StringUtils.isEmpty(userName)){ /*条件存在,进行封装*/ wrapper.like(User::getUsername,userName); } IPage pageModel=userService.page(pageInfo,wrapper); return Result.ok(pageModel); } ``` #### 3.4.2 获取全部表 ```java /** * 获取全部角色列表 * * @return Result> */ @GetMapping("findAll") public Result>findAllUser(){ List userList=userService.list(); return Result.ok(userList); } ``` #### 3.4.3 新增 ```java /** * 新增角色 * * @param user * @return Result.ok */ @PostMapping("save") public Result save(@RequestBody User user){ userService.save(user); return Result.ok(); } ``` #### 3.4.4 根据 ID 获取 ```java /** * 根据 ID 获取角色 * * @param id ID * @return User */ @GetMapping("get/{id}") public Result get(@PathVariable Long id){ User user=userService.getById(id); return Result.ok(user); } ``` #### 3.4.5 修改 ```java /** * 修改角色 * * @param user * @return Result.ok */ @PutMapping("update") public Result updateById(@RequestBody User user){ userService.updateById(user); return Result.ok(); } ``` #### 3.4.6 删除 > - [x] 根据 ID 删除 > - [x] 根据 IDS 列表删除 > 根据 ID ```java /** * 根据 ID 删除角色 * * @param id ID * @return Result.ok */ @DeleteMapping("remove/{id}") public Result remove(@PathVariable Long id){ userService.removeById(id); return Result.ok(); } ``` > 根据 IDS 列表删除 ```java /** * 根据 IDS 列表删除角色 * * @param idList IDS 列表 * @return Result.ok */ @DeleteMapping("batchRemove") public Result batchRemove(@RequestBody List idList){ userService.removeByIds(idList); return Result.ok(); } ``` #### 3.4.7 MP 多对多查询 多对多查询:一个用户可以对应多个角色,一个角色也可以对应多个用户,因此我们需要创建 `sys_role_user` 表维护他们的关系。 > - [x] 添加关系表 `sys_role_user` ![](https://cola-picgo-1311841992.cos.ap-beijing.myqcloud.com/20230309145943.png) > - [x] 创建 UserRole 实体类 ```java @Data @ApiModel(description = "用户角色") @TableName("sys_user_role") public class UserRole extends BaseEntity { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "角色 ID") @TableField("role_id") private Long roleId; @ApiModelProperty(value = "用户 ID") @TableField("user_id") private Long userId; } ``` > - [x] 新增 `AssginRoleVo` 查询类 ```java @ApiModel(description = "角色分配VO") @Data public class AssginRoleVo { @ApiModelProperty(value = "用户 ID") private Long userId; @ApiModelProperty(value = "角色 ID 列表") private List roleIdList; } ``` > - [x] 为 RoleService 添加相关抽象方法并实现 ```java /** * 根据 用户 ID 获取角色数据 * @param userId userId * @return map */ Map findRoleByUserId(Long userId); ``` ```java @Autowired UserRoleMapper userRoleMapper; @Override public Map findRoleByUserId(Long userId){ /*查询所有 Role 列表*/ List allRoleList=this.list(); /*查询该 userId 对应的 UserRole 列表*/ List existUserRoleList=userRoleMapper.selectList( new LambdaQueryWrapper(). eq(UserRole::getUserId,userId). select(UserRole::getRoleId) ); /* 该 userId 具有的 roleId 列表*/ List exitRoleIdList=existUserRoleList.stream().map( userRole->userRole.getRoleId() ).collect(Collectors.toList()); /*该 userId 具有的 roleId 与所有 roleId 的交集即为 该 userId 所拥有的 roleId*/ List assginRoleList=new ArrayList<>(); for(Role role:allRoleList){ if(exitRoleIdList.contains(role.getId())){ assginRoleList.add(role); } } Map roleMap=new HashMap<>(); roleMap.put("assginRoleList",assginRoleList); roleMap.put("allRoleList",allRoleList); return roleMap; } ``` > - [x] 在 RoleController 中添加相关接口 ```java @ApiOperation(value = "根据用户获取角色数据") @GetMapping("/toAssign/{userId}") public Result toAssign(@PathVariable Long userId){ Map roleMap=roleService.findRoleByUserId(userId); return Result.ok(roleMap); } ``` #### 3.4.8 MP 为用户分配角色 > - [x] 为 RoleService 添加相关抽象方法并实现 ```java /** * 分配角色 * @param assginRoleVo assginRoleVo */ void doAssign(AssginRoleVo assginRoleVo); ``` ```java @Override public void doAssign(AssginRoleVo assginRoleVo){ /*先删除该 userId 下所有的 roleId*/ userRoleMapper.delete( new LambdaQueryWrapper(). eq(UserRole::getUserId,assginRoleVo.getUserId())); /*遍历 roleIdList 如果存在 roleId 就插入*/ for(Long roleId:assginRoleVo.getRoleIdList()){ if(StringUtils.isEmpty(roleId))continue; UserRole userRole=new UserRole(); userRole.setUserId(assginRoleVo.getUserId()); userRole.setRoleId(roleId); userRoleMapper.insert(userRole); } } ``` > - [x] 在 RoleController 中添加相关接口 ```java @ApiOperation(value = "根据用户分配角色") @PostMapping("/doAssign") public Result doAssign(@RequestBody AssginRoleVo assginRoleVo){ roleService.doAssign(assginRoleVo); return Result.ok(); } ``` #### 3.4.9 MP 更新用户状态 > - [x] 为UserService 添加相关抽象方法并实现 ```java /** * 更新用户状态 * @param id id * @param status 状态 (0 正常 | 1 封禁) */ void updateStatus(Long id,Integer status); ``` ```java @Override public void updateStatus(Long id,Integer status){ User user=this.getById(id); if(status.intValue()==1){ user.setStatus(status); }else{ user.setStatus(0); } this.updateById(user); } ``` > - [x] 在 UserController 中添加相关接口 ```java @ApiOperation(value = "更新用户状态") @GetMapping("updateStatus/{id}/{status}") public Result updateStatus(@PathVariable Long id, @PathVariable Integer status){ userService.updateStatus(id,status); return Result.ok(); } ``` ## 二、 Knife4J 入门 ### 4.1 简介 [Knife4j](https://doc.xiaominfo.com/) 是一个集Swagger2及OpenAPI3为一体的增强解决方案,可以帮助开发者快速聚合使用OpenAPI规范. ### 4.2 特性 ![](https://cola-picgo-1311841992.cos.ap-beijing.myqcloud.com/20230307162020.png) 前后端分离开发模式中,api文档是最好的沟通方式。 Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。 1. 及时性 (接口变更后,能够及时准确地通知相关前后端开发人员) 2. 规范性 (并且保证接口的规范性,如接口的地址,请求方式,参数及响应格式和错误信息) 3. 一致性 (接口信息一致,不会出现因开发人员拿到的文档版本不一致,而出现分歧) 4. 可测性 (直接在接口文档上进行测试,以方便理解业务) ### 4.3 使用方法 #### 4.3.1 引入依赖 ```xml com.github.xiaoymin knife4j-spring-boot-starter 3.0.3 ``` #### 4.3.2 配置 ```java @Configuration @EnableSwagger2WebMvc public class Knife4jConfig { /** * AdminApi 配置 * * @return Docket */ @Bean public Docket adminApiConfig() { /* 配置分组信息 */ Docket adminApi = new Docket(DocumentationType.SWAGGER_2) .groupName("adminApi") .apiInfo(adminApiInfo()) .select() /* * 只显示admin路径下的页面 * */ .apis(RequestHandlerSelectors.basePackage("[要扫描的包 eg: cola.rh]")) .paths(PathSelectors.regex("[路径(支持通配符) eg:/v1/api/.*]")) .build(); return adminApi; } /** * AdminApi 信息 * * @return ApiInfo */ private ApiInfo adminApiInfo() { return new ApiInfoBuilder() .title("HelloSpringBoot-API文档") .description("本文档描述了HelloSpringBoot系统微服务接口定义") .version("1.0") .contact(new Contact("Cola", "https://gitee.com/cola0223", "1203952894@qq.com")) .build(); } } ``` ```java @Data @ApiModel(description = "用户") @TableName("sys_user") public class User extends BaseEntity { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "用户名") @TableField("username") private String username; @ApiModelProperty(value = "密码") @TableField("password") private String password; } ``` ```java @Api(tags = "用户管理") @RestController @Slf4j @RequestMapping("/v1/api/users") public class UserController { @Autowired private UserService userService; @ApiOperation("HelloSpringBoot") @GetMapping("/helloSpringBoot") public String helloSpringBoot() { return "HelloSpringBoot"; } @ApiOperation("RH") @GetMapping("/rh") public String helloRH() { return "Hello RH"; } @ApiOperation("CBQ") @GetMapping("/cbq") public String helloCBQ() { return "Hello CBQ"; } } ``` ### 4.4 测试 访问:`http://localhost:[项目端口号]/doc.html` 查看文档 ![](https://cola-picgo-1311841992.cos.ap-beijing.myqcloud.com/20230307165750.png) ![](https://cola-picgo-1311841992.cos.ap-beijing.myqcloud.com/20230307165654.png) ![](https://cola-picgo-1311841992.cos.ap-beijing.myqcloud.com/20230307165814.png) ## 三、全局异常处理器 入门 ### 5.1 全局异常处理 ```java @ControllerAdvice public class GlobalExceptionHandler { /** * 全局异常处理,执行的方法 * * @param e Exception * @return Result.fail().message */ @ExceptionHandler(Exception.class) @ResponseBody public Result error(Exception e) { e.printStackTrace(); return Result.fail().message("全局异常处理"); } } ``` ### 5.2 处理特定异常 ```java /** * 特定异常处理 * * @param e ArithmeticException * @return Result.fail().message */ @ExceptionHandler(ArithmeticException.class) @ResponseBody public Result error(ArithmeticException e){ e.printStackTrace(); return Result.fail().message("特定异常处理"); } /** * spring security异常 * * @param e AccessDeniedException * @return Result.fail().message */ @ExceptionHandler(AccessDeniedException.class) @ResponseBody public Result error(AccessDeniedException e)throws AccessDeniedException{ return Result.fail().code(205).message("没有操作权限"); } ``` ### 5.3 处理自定义异常 ```java /** * 自定义异常处理 * * @param e ColaException * @return Result.fail().message */ @ExceptionHandler(ColaException.class) @ResponseBody public Result error(ColaException e){ e.printStackTrace(); return Result.fail().code(e.getCode()).message(e.getMsg()); } ``` ## 四、IDEA + Docker 实现自动部署 > 使用 IDEA + Docker 自动部署 SoringBoot 项目 > - [x] 为 Docker 开启 2375 端口 #### 4.1 为 Docker 开启 2375 端口 1. 修改 `/usr/lib/systemd/system` 目录下的 `docker.service` 文件修改 ==ExecStart==为:`ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix://var/run/docker.sock` 2. `systemctl daemon-reload` 2. `systemctl restart docker` ![](https://cola-picgo-1311841992.cos.ap-beijing.myqcloud.com/20230309144501.png) #### 4.2 在 IDEA 中连接 Docker > - [x] 在 IDEA 中连接 Docker ![](https://cola-picgo-1311841992.cos.ap-beijing.myqcloud.com/20230309144541.png) #### 4.3 在 `pom` 中配置打包插件 > - [x] 在 `pom` 中配置打包插件 ```xml com.spotify docker-maven-plugin 1.2.2 build-image package build hellospringboot latest ${project.basedir} http://xxx:2375 / ${project.build.directory} ${project.build.finalName}.jar ``` #### 4.4 编写 dockerfile > - [x] 编写 dockerfile 在项目目录下创建 `dockerfile` ```dockerfile #这是基础镜像 FROM java:8 # 设置元数据 LABEL version="1.0" description="HelloSpringBootDocker" By="Cola" # 工作空间 WORKDIR /myApp #将上下文中 jar 包复制到 /myApp 目录下,并且重命名为 HelloSpringBoot.jar ADD ./target/HelloSpringBoot-0.0.1-SNAPSHOT.jar HelloSpringBoot.jar #在容器启动的时候运行命令,来启动我们的项目(这其实就是一段Linux命令,该命令可以在服务启动时加一些参数) ENTRYPOINT ["sh", "-c", "java -jar HelloSpringBoot.jar"] ``` #### 4.5 打包并编辑运行配置 > - [x] 打包并编辑运行配置 ![](https://cola-picgo-1311841992.cos.ap-beijing.myqcloud.com/20230309144757.png) ![](https://cola-picgo-1311841992.cos.ap-beijing.myqcloud.com/20230309144833.png) ## 五、JWT JWT 是 JSON Web Token 的缩写,即 JSON Web 令牌,是一种自包含令牌。 是为了在网络应用环境间传递声明而执行的一种基于 JSON 的开放标准。 JWT 的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如用在用户登录上。 JWT 最重要的作用就是对 token信息的防伪作用。 ### 5.1 JWT 概述 一个JWT由三个部分组成: - **JWT 头** - **有效载荷** - **签名哈希** 最后由这三者组合进行 BASE64URL 编码得到 JWT ![](https://cola-picgo-1311841992.cos.ap-beijing.myqcloud.com/20230309225405.png) > JWT头部 JWT头部分是一个描述JWT元数据的 JSON 对象,通常如下所示。 ```json { "alg": "HS256", "typ": "JWT" } ``` 在上面的代码中,alg 属性表示签名使用的算法,默认为 HMAC SHA256(写为HS256);typ 属性表示令牌的类型,JWT 令牌统一写为 JWT。 > 有效载荷 有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择。 ```json iss: jwt签发者 sub: 主题 aud: 接收jwt的一方 exp: jwt的过期时间,这个过期时间必须要大于签发时间 nbf: 定义在什么时间之前,该jwt都是不可用的. iat: jwt的签发时间 jti: jwt的唯一身份标识,主要用来作为一次性token, 从而回避重放攻击。 ``` 请注意,默认情况下 JWT 是未加密的,任何人都可以解读其内容,因此不要构建隐私信息字段,存放保密信息,以防止信息泄露。 JSON 对象也使用 Base64 URL 算法转换为字符串保存。 > 签名哈希 签名哈希部分是对上面两部分数据签名,通过指定的算法生成哈希,以确保数据不会被篡改。 首先,需要指定一个密码(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用标头中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名。 ```json HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(claims), secret) ==> 签名hash ``` > Base64URL算法 如前所述,JWT头和有效载荷序列化的算法都用到了Base64URL。该算法和常见Base64算法类似,稍有差别。 作为令牌的JWT可以放在URL中(例如api.example/?token=xxx)。 Base64中用的三个字符是"+","/"和"=" ,由于在URL中有特殊含义,因此Base64URL中对他们做了替换:"="去掉,"+"用"-"替换,"/"用"_"替换,这就是Base64URL算法。 ### 5.2 JWT 的使用 > - [x] 引入依赖 ```xml io.jsonwebtoken jjwt 0.9.1 ``` > - [x] 添加 JWT 帮助类 ```java public class JwtHelper { /*设置 Token 过期时间*/ private static long tokenExpiration = 7 * 24 * 60 * 60 * 1000; /*设置盐值*/ private static String tokenSignKey = "Cola"; /** * 根据 id 和 用户名生成 token * * @param userId userId * @param username username * @return token */ public static String createToken(Long userId, String username) { String token = Jwts.builder() /*分类*/ .setSubject("AUTH-USER") /*设置token有效时长*/ .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration)) /*设置主体部分*/ .claim("userId", userId) .claim("username", username) /*签名部分*/ .signWith(SignatureAlgorithm.HS512, tokenSignKey) .compressWith(CompressionCodecs.GZIP) .compact(); return token; } /** * 从生成 token 字符串获取用户 id * * @param token token * @return id */ public static Long getUserId(String token) { try { if (StringUtils.isEmpty(token)) return null; Jws claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token); Claims claims = claimsJws.getBody(); Integer userId = (Integer) claims.get("userId"); return userId.longValue(); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 从生成token字符串获取用户名称 * * @param token token * @return username */ public static String getUsername(String token) { try { if (StringUtils.isEmpty(token)) return ""; Jws claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token); Claims claims = claimsJws.getBody(); return (String) claims.get("username"); } catch (Exception e) { e.printStackTrace(); return null; } } } ``` ### 5.3 栗子 🌰 > - [x] 使用 MD5 增强保存用户功能 ```java public final class MD5Util { public static String encrypt(String strSrc) { try { char hexChars[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; byte[] bytes = strSrc.getBytes(); MessageDigest md = MessageDigest.getInstance("MD5"); md.update(bytes); bytes = md.digest(); int j = bytes.length; char[] chars = new char[j * 2]; int k = 0; for (int i = 0; i < bytes.length; i++) { byte b = bytes[i]; chars[k++] = hexChars[b >>> 4 & 0xf]; chars[k++] = hexChars[b & 0xf]; } return new String(chars); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); throw new RuntimeException("MD5加密出错!!+" + e); } } } ``` ```java @ApiOperation(value = "新增用户") @PostMapping("save") public Result saveByMd5(@RequestBody User user){ userService.saveByMd5(user); return Result.ok(); } ``` ```java /** * Md5 加密 密码后保存用户 * * @param user user */ void saveByMd5(User user); ``` ```java @Override public void saveByMd5(User user){ /*使用 Md5 加密密码*/ String passwordMd5=MD5Util.encrypt(user.getPassword()); user.setPassword(passwordMd5); this.save(user); } ``` > - [x] 登录功能 ```java /** * 登录 * * @param loginVo loginVo * @return Result */ @ApiOperation("登录") @PostMapping("login") public Result login(@RequestBody LoginVo loginVo){ String username=loginVo.getUsername(); User user=userService.getByUsername(username); if(null==user){ throw new ColaException(201,"用户不存在"); } if(!MD5Util.encrypt(loginVo.getPassword()).equals(user.getPassword())){ throw new ColaException(201,"密码错误"); } if(user.getStatus().intValue()==0){ throw new ColaException(201,"账号被封禁"); } HashMap map=new HashMap<>(); map.put("token",JwtHelper.createToken(user.getId(),user.getUsername())); return Result.ok(map); } ``` ```java /** * 通过 username 获取用户 * * @param username username * @return User */ User getByUsername(String username); ``` ```java @Override public User getByUsername(String username){ User user=this.getOne(new LambdaQueryWrapper(). eq(User::getUsername,username)); return user; } ``` ![](https://cola-picgo-1311841992.cos.ap-beijing.myqcloud.com/20230309230139.png) ## 六、Spring Security 入门 ### 6.1 Spring Security 概述 #### 6.1.1 Spring Security 简介 Spring 是非常流行和成功的 Java 应用开发框架, [Spring Security](https://spring.io/projects/spring-security) 正是 Spring 家族中的成员。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。 正如你可能知道的关于安全方面的两个核心功能是“**认证**”和“**授权**”,一般来说,Web 应用的安全性包括 - **用户认证(Authentication)** - **用户授权(Authorization)** 两个部分,这两点也是 SpringSecurity 重要核心功能。 > 用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码,系统通过校验用户名和密码来完成认证过程。 > > 通俗点说就是系统认为用户是否能登录 **** > 用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。 > > 通俗点讲就是系统判断用户是否有权限去做某些事情。 **** #### 6.1.2 同款产品 (Shiro) 比较 > - [x] [Spring Security](https://spring.io/projects/spring-security) Spring 官方出品 - 和 Spring 无缝整合。 - 全面的权限控制。 - 专门为 Web 开发而设计。 - 旧版本不能脱离 Web 环境使用。 - 新版本对整个框架进行了分层抽取,分成核心模块和 Web 模块。单独引入核心模块就可以脱离 Web 环境。 - 重量级。 > - [x] [Apache Shiro | Simple. Java. Security.](https://shiro.apache.org/) Apach 旗下出品 - 轻量级。Shiro 主张的理念是把复杂的事情变简单。针对对性能有更高要求的互联网应用有更好表现。 - 通用性。 - 好处:不局限于 Web 环境,可以脱离 Web 环境使用。 - 缺陷:在 Web 环境下一些特定的需求需要手动编写代码定制。 Spring Security 是 Spring 家族中的一个安全管理框架,实际上,在 Spring Boot 出现之前,Spring Security 就已经发展了多年了,但是使用的并不多,安全管理这个领域,一直是 Shiro 的天下。 相对于 Shiro,在 SSM 中整合 Spring Security 都是比较麻烦的操作,所以,Spring Security 虽然功能比 Shiro 强大,但是使用反而没有 Shiro 多(Shiro 虽然功能没有Spring Security 多,但是对于大部分项目而言,Shiro 也够用了)。自从有了 Spring Boot 之后,Spring Boot 对于 Spring Security 提供了自动化配置方案,可以使用更少的配置来使用 Spring Security。 ### 6.2 Spring Security 的使用 > 要对 Web 资源进行保护,最好的办法莫过于 Filter。 > 要想对方法调用进行保护,最好的办法莫过于 AOP。 Spring Security 进行认证和鉴权的时候,就是利用的一系列的 Filter 来进行拦截的。 ![](https://cola-picgo-1311841992.cos.ap-beijing.myqcloud.com/20230310023619.png) > 如图所示,一个请求想要访问到 API 就会从左到右经过蓝线框里的过滤器,其中 > > - **绿色部分是负责认证的过滤器** > > - **蓝色部分是负责异常处理** > > - **橙色部分则是负责授权** > > 经过一系列拦截最终访问到我们的 API。 这里面我们只需要重点关注两个过滤器即可: - `UsernamePasswordAuthenticationFilter` 负责登录认证 - `FilterSecurityInterceptor` 负责权限授权。 说明:**Spring Security 的核心逻辑全在这一套过滤器中,过滤器里会调用各种组件完成功能,掌握了这些过滤器和组件你就掌握了Spring Security**!这个框架的使用方式就是对这些过滤器和组件进行扩展。 > - [x] 引入依赖 ```xml org.springframework.boot spring-boot-starter-security ``` 说明:依赖包(spring-boot-starter-security)导入后,Spring Security 就默认提供了许多功能将整个应用给保护了起来: ​ 1、要求经过身份验证的用户才能与应用程序进行交互 ​ 2、创建好了默认登录表单 ​ 3、生成用户名为`user`的随机密码并打印在控制台上 ![](https://cola-picgo-1311841992.cos.ap-beijing.myqcloud.com/20230309231836.png) ![](https://cola-picgo-1311841992.cos.ap-beijing.myqcloud.com/20230309231859.png) ### 6.2.1 用户认证 Authentication > - [x] 用户认证流程 ![](https://cola-picgo-1311841992.cos.ap-beijing.myqcloud.com/20230310031219.png) 1. 用户提交用户名和密码 2. 将请求信息封装到 Authentication 中,实现类为 UsernamePasswordAuthenticationToken 3. 认证 authenticate() 4. 委托认证 authenticate() 5. 获取用户信息 loadUserByUsername() 6. 返回 UserDetails 7. 通过 PasswordEncoder 对比 UserDeatils 中的密码与 Authentication 中的密码是否一致 8. 填充 Authentication 9. 返回 Authentication 10. 通过 SecurityContextHolder.getContext().setAuthentication() 方法将 Authentication 保存到上下文中 --- TODO ### 6.2.2 用户授权 Authorization ### 6.3 栗子 🌰