Spring Security OAuth2 实践
Spring Security OAuth 的生命周期已经结束,官方已经删除它的文档,现在推荐使用 Spring Authorization Server,但是还是有很多老项目在继续使用 Spring Security OAuth,学习它仍然是必要的。虽然官方文档已经删除了,但是找到了一篇较早时间的翻译 Spring Security OAuth2 开发指南,可以作为实践的基础。网络上也有很多如何使用它的文章,这篇文章是自己实践的一点记录。
Spring Security OAuth 的生命周期已经结束,官方已经删除它的文档,现在推荐使用 Spring Authorization Server,但是还是有很多老项目在继续使用 Spring Security OAuth,学习它仍然是必要的。虽然官方文档已经删除了,但是找到了一篇较早时间的翻译 Spring Security OAuth2 开发指南,可以作为实践的基础。网络上也有很多如何使用它的文章,这篇文章是自己实践的一点记录。
备注:Knife4j 通过 com.github.xiaoymin.knife4j.spring.extension.Knife4jOpenApiCustomizer
类的 addOrderExtension
方法给 Tag
类应用 @ApiSupport
注解的 order
属性。
在我们的一个项目中使用了 Knife4j 来显示 Swagger 的文档,具体的依赖如下
1 | <dependency> |
同时我们有两个 Controller,它们分别是
1 | @RestController |
默认情况下,在 Knife4j 的页面中“角色管理”显示在了“用户管理”的前面。我们期望“用户管理”显示在“角色管理”的前面。为了达到这个目的我们使用了 Knife4j 提供的注解 @ApiSupport
,修改后的代码如下所示
1 | @RestController |
但是并没有什么效果,“用户管理”并没有显示在“角色管理”的前面,即 @ApiSupport
注解没有生效。
我们有一个项目使用了 Knife4j,依赖的版本为
1 | <dependency> |
这个版本的 Knife4j 使用了 SpringDoc,其获取 Swagger 配置的地址为 /v3/api-docs/swagger-config
,获取文档的地址为 /v3/api-docs
,如果项目有多个分组,获取单个分组,比如分组“1-动物”,的文档的地址为 /v3/api-docs/1-动物
。
在项目部署时在项目的前面放置了一台 Nginx 反向代理服务器,配置了当访问路径为 /example
时会将请求转发到我们自己的项目
1 | location /example/ { |
当访问 http://localhost/example/doc.html
时,Knife4j 能正常的访问 Swagger 的配置,即 http://localhost/example/v3/api-docs/swagger-config
,但是无法访问 http://localhost/v3/api-docs/1-动物
,因此无法打开 Knife4j 文档页面。
首先创建一个 Spring Boot 项目,然后创建一个实体类 User
,它有两个字段姓名 name
和生日 birthday
,其中 birthday
的类型是 OffsetDataTime
1 | @Data |
最后创建对应的控制器 UserController
,GET 请求的第二种形式将参数封装成了对象
1 | @RestController |
我们要实现接收前端传递过来的形如 yyyy-MM-dd HH:mm:ss
的字符串并自动将它转换为能够使用 OffsetDateTime
类型。
在 SpringBoot 应用中可以使用以下几种方式来控制是否返回值为空的字段
application.yml
中配置全局自动忽略 1 | spring: |
@JsonInclude(JsonInclude.Include.NON_NULL)
Jackson2ObjectMapperBuilderCustomizer
定制器 1 | @Bean |
JacksonObjectMapperConfiguration
自定义一个 ObjectMapper
,覆盖默认的定义 1 | @Bean |
这 4 种方式要么是全局的要么是局部的,它们都是一次性的,设置后就不能改变,每次请求都会返回相同的结果。现在我们要实现根据请求参数中是否包含某个值动态的设置是否返回值为空的字段,具体的说我们要根据请求头中是否包含 X-Include-Non-Null
来控制是否返回值为空的字段。
在日常开发中我们会定义类似下面的 Result
类来统一接口返回结果的结构
1 | public class Result<T> implements Serializable { |
此时 Controller
中的方法就会写成这个样子
1 | @GetMapping("/detail1/{userId}") |
在每个方法上都写上类似 Result<User>
这样的代码还是比较繁琐的,我们期望写成 User
这样简单的形式
1 | @GetMapping("/detail2/{userId}") |
而由框架去处理返回结果的包装。此时就会使用到 Spring 的 ResponseBodyAdvice
接口
1 | @RestControllerAdvice |
同时我们也会使用 SpringDoc 对外提供可访问的文档。此时问题出现了,我们发现在 Swagger 的文档中方法 detail1
和方法 detail2
对应的 Schema 不一样
我们期望 detail2
展示的结果和 detail1
一样。