文章目录
1 前言
为了搞懂 springdoc 与 spring security 如何配合,我又学了一遍 spring security 的 JWT 相关的知识。🤣为什么学完就忘呢🤣,也许反反复复是普通人的常态吧,毕竟铁杵磨成针,那得费老大劲儿了~
上一篇《01_学习springdoc的基本使用》 留了一点“遗憾” —— 没有写 springdoc 与 spring security 如何配合。现在“搞定”它了,我们可以愉快地使用 JWT 的访问令牌 accessToken 请求需要认证的 API 接口了~
2 提纲
本文主要内容如下:
- springdoc 的简单配置
- 在 Spring Security 设置访问 swagger 页面时,不需要认证授权
- springdoc 与 Spring Security 的配合
3. 准备工作
3.1 maven 依赖
<!-- security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!-- security jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
<!-- spring doc -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-security</artifactId>
<version>${springdoc.version}</version>
</dependency>
3.2 Spring Security 与 JWT
这一部分需要做到:
- 能够使用
Jwts
创建和解析 token; - 能够使用 refreshToken 获取新的 accessToken;
- 能够自定义一个
JwtFilter
,从请求头Authorization: Bearer xxx.xxx.xxx
中拿到 accessToken,然后解析它,构建UsernamePasswordAuthenticationToken
,最后把它放到SecurityContextHolder
中, 完成认证。
由于本文的重点不是怎么使用 Spring Security ,而且相关的代码也多,所以上述3点就略过了。如果此时的你感觉无从下手,那我推荐在 慕课网 搜索 spring security 来学习。慕课网的这门课,课很好,🤣奈何我愚笨,学了2遍了,刚刚会了 JWT(估计过一阵子就又忘了😂),对后面的 oauth2 还是懵懵懂懂。路漫漫其修远兮,吾将上下而求索。
经过努力😁,springdoc 与 oauth2 的结合,也OK了,链接如下 《04_学习springdoc与oauth结合_简述》
4 springdoc 的简单配置
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MySpringDocConfig {
/**
* 一个自定义的 OpenAPI 对象
*
* @return 一个自定义的 OpenAPI 对象
*/
@Bean
public OpenAPI customOpenApi() {
return new OpenAPI()
.components(new Components()
// 设置 spring security jwt accessToken 认证的请求头 Authorization: Bearer xxx.xxx.xxx
.addSecuritySchemes("authScheme", new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.bearerFormat("JWT")
.scheme("bearer")))
// 设置一些标题什么的
.info(new Info()
.title("学习 Activiti 7 的 API")
.version("1.0.0")
.description("加油↖(^ω^)↗")
.license(new License()
.name("Apache 2.0")
.url("https://www.apache.org/licenses/LICENSE-2.0")));
}
}
效果如下图:
5 在 Spring Security 设置访问 swagger 页面时,不需要认证授权
import com.example.activiti7learn.security.filter.JwtFilter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.MessageDigestPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Configuration
@RequiredArgsConstructor
public class MySecurityConfig {
private final JwtFilter jwtFilter;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated())
.csrf(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable)
// 在普通的用户名密码验证之前, 把 jwt 的过滤器加上
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
// 忽略 /error 页面
return web -> web.ignoring().antMatchers("/error",
// 忽略 swagger 接口路径
"/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html")
// 忽略常见的静态资源路径
.requestMatchers(PathRequest.toStaticResources().atCommonLocations());
}
}
6 springdoc 与 Spring Security 的配合
在第 4 部分,已经有一个 springdoc 的简单配置,其中有和 JWT 相关的配置,代码片断如下(这个是从 springdoc 官网上学到的):
// 设置 spring security jwt accessToken 认证的请求头 Authorization: Bearer xxx.xxx.xxx
.addSecuritySchemes("authScheme", new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.bearerFormat("JWT")
.scheme("bearer")))
有了上面的配置,算是成功第一步了。接下来我们还要给 Controller 的方法加注解,如下:
import com.example.activiti7learn.common.JsonResult;
import com.example.activiti7learn.domain.common.PageInfo;
import com.example.activiti7learn.domain.common.PageReq;
import com.example.activiti7learn.domain.req.def.BpmnXmlReq;
import com.example.activiti7learn.domain.req.def.ProcessDefReq;
import com.example.activiti7learn.domain.resp.def.BpmnXmlResp;
import com.example.activiti7learn.service.ProcessDefinitionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.activiti.api.process.model.ProcessDefinition;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequiredArgsConstructor
@Tag(name = "流程定义", description = "流程定义 API")
@ApiResponses(@ApiResponse(responseCode = "200", description = "接口请求成功"))
@RequestMapping(path = "/process-definition", produces = MediaType.APPLICATION_JSON_VALUE)
public class ProcessDefinitionController {
private final ProcessDefinitionService processDefinitionService;
// 重点在下面的 @SecurityRequirement, 其 name 就是前面 MySpringDocConfig addSecuritySchemes() 配置的 key
@Operation(summary = "分页查询流程定义数据", security = @SecurityRequirement(name = "authScheme"))
@PostMapping("/def-page-data")
public JsonResult<PageInfo<ProcessDefinition>> findProcessDefinitionPage(@RequestBody PageReq<ProcessDefReq> req) {
return processDefinitionService.findProcessDefinitionPage(req);
}
}
写到这里,🤣不得不吐槽一下,使用 springdoc 之后,代码里面的注解是真的多,这么多圈儿 @
都快要把我绕晕了。上面最重要的一句是 @Operation(summary = "分页查询流程定义数据", security = @SecurityRequirement(name = "authScheme"))
,authScheme 就是前面 MySpringDocConfig .addSecuritySchemes("authScheme"
的 authScheme。
大功告成了,是不是觉得有点快,有点突然😁。效果如下图:
7 验证
7.1 登录一个用户
首先在登录API里面登录一个用户,如下图:
由图可知 accessToken 是 eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJzbWl0aCIsImF1dGhvcml0aWVzIjpbIlJPTEVfQUNUSVZJVElfVVNFUiJdLCJpYXQiOjE2NzU4MzM2NjIsImV4cCI6MTY3NTg1MTY2Mn0.aW4ui_kEzrvxxSQbx4XZAfR6UGU-5Q7avyOM4oDnf2IdKB4nvA8Vt0DLo1JwKMtzyvZe_4Zwi6v5s_9HUvTpiQ
7.2 给 springdoc 设置 accessToken
把 7.1 的 accessToken 设置到 springdoc 中,如下图,粘贴好 accessToken 之后,点 Authorize 按钮就行。
7.3 请求一个需要认证的接口
这个时候,再请求一个需要认证的接口,就会发现,它自动加上了请求头 Authorization: Bearer xxx.xxx.xxx
,如下 curl 代码所示:
curl -X 'POST' \
'http://localhost:8081/process-definition/def-page-data' \
-H 'accept: application/json' \
-H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJzbWl0aCIsImF1dGhvcml0aWVzIjpbIlJPTEVfQUNUSVZJVElfVVNFUiJdLCJpYXQiOjE2NzU4MzM2NjIsImV4cCI6MTY3NTg1MTY2Mn0.aW4ui_kEzrvxxSQbx4XZAfR6UGU-5Q7avyOM4oDnf2IdKB4nvA8Vt0DLo1JwKMtzyvZe_4Zwi6v5s_9HUvTpiQ' \
-H 'Content-Type: application/json' \
-d '{
"currentPage": 1,
"pageSize": 20,
"req": {
"processDefinitionId": null,
"processDefinitionKey": "ProcessRuntimeDefKey",
"processDefinitionKeys": null
}
}'
其实费了大半天劲,要的就是自动加上请求头 Authorization: Bearer xxx.xxx.xxx
的效果😁
8 结语
感谢阅读~