Swagger3在线接口文档与SpringSecurity配置
chen subao Lv1

配置Swagger3的接口文档生成,包含特定地址忽略、JWT_BEARER_BUILDER HttpAuthenticationScheme以及在spring security中的使用,配置基于SpringBoot项目。

Swagger3Config配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import java.util.Collections;
import java.util.List;

/**
* swagger文档配置
*/
@Configuration
public class Swagger3Config {

@Bean
public Docket docket() {
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
// 参数上有指定的注解时将整个参数不显示
// https://blog.csdn.net/qq_38380025/article/details/102589397
.ignoredParameterTypes()
.select()
// 不显示error开头的请求
.paths(PathSelectors.regex("/error.*").negate())
.build()
// 统一设置tag
.tags(new Tag("authorization", "统一认证授权"))
.securitySchemes(securitySchemes())
.securityContexts(securityContext());
}

/**
* 基本信息
* @return ApiInfo
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Swagger")
.version("1.0.0")
.description("API接口文档")
.build();
}

/**
* 使用jwt bearer token 授权方案
* 对应页面上的请求有小锁图标
* @return List<SecurityScheme>
*/
private List<SecurityScheme> securitySchemes() {
return Collections.singletonList(HttpAuthenticationScheme.JWT_BEARER_BUILDER
.name("Authorization")
.scheme("bearer")
.build());
}

/**
* bearer 授权上下文
* @return List<SecurityContext>
*/
private List<SecurityContext> securityContext() {
SecurityContext context = new SecurityContext(
defaultAuth(),
// 配置需要访问授权的请求,效果是对应页面上的请求有没有小锁图标
PathSelectors.regex("/auth.*").negate(),
// 配置需要访问授权的请求,效果是对应页面上的请求有没有小锁图标
each -> true,
// operationSelector优先级高于上面两个,配置需要访问授权的请求,效果是对应页面上的请求有没有小锁图标
// 将auth开头的请求和类、方法上有指定注解的请求在swagger页面上放行,不使用jwt bearer token 授权方案
operationContext -> !operationContext.requestMappingPattern().matches("/auth.*") &&
!operationContext.findControllerAnnotation(CustomAccess.class).isPresent() &&
!operationContext.findAnnotation(CustomAccess.class).isPresent()
);
return Collections.singletonList(context);
}

/**
* bearer 授权范围
* @return List<SecurityReference>
*/
private List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope
= new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
return Collections.singletonList(new SecurityReference("Authorization", authorizationScopes));
}
}

SpringSecurityConfig配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.util.*;

/**
* https://www.baeldung.com/spring-security-method-security
*/
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

private final RequestMappingHandlerMapping requestMappingHandlerMapping;

@Override
protected void configure(HttpSecurity http) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry urlRegistry = http
// csrf disable
.csrf()
.disable()

.and()
// create no session
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// enable h2-console
.headers()
.frameOptions()
.sameOrigin()
.and()
// 路径鉴权
.authorizeRequests()
.antMatchers("/auth/**")
.permitAll();
// 通过注解自定义放行请求
passCustomAccess(urlRegistry);
// 之后所有请求都需要授权
urlRegistry.anyRequest().authenticated();
}

@Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**")
// allow anonymous resource requests
.antMatchers(
"/",
"/error/**",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/h2-console/**",

// swagger
"/swagger-ui/index.html",
"/swagger-ui/**",
"/webjars/**",
// swagger api json
"/v3/api-docs",
//用来获取支持的动作
"/swagger-resources/configuration/ui",
//用来获取api-docs的URI
"/swagger-resources",
//安全选项
"/swagger-resources/configuration/security",
"/swagger-resources/**"
);
}

/**
* 去除ROLE_前缀
* @return GrantedAuthorityDefaults
*/
@Bean
public GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("");
}

/**
* BCrypt 算法加密
* @return PasswordEncoder
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

/**
* 通过注解自定义放行请求 {@link CustomAccess}
* @param urlRegistry {@link ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry}
*/
private void passCustomAccess(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry urlRegistry) {
Map<HttpMethod, Set<String>> map = new HashMap<>(16);
requestMappingHandlerMapping.getHandlerMethods().forEach((k, v) -> {
if (v.getBeanType().isAnnotationPresent(CustomAccess.class) || v.hasMethodAnnotation(CustomAccess.class)) {
for (RequestMethod method : k.getMethodsCondition().getMethods()) {
HttpMethod httpMethod = HttpMethod.resolve(method.name());
if (null != httpMethod) {
Set<String> methodPatterns = map.get(httpMethod);
if (CollectionUtils.isEmpty(methodPatterns)) {
methodPatterns = new HashSet<>();
map.put(httpMethod, methodPatterns);
}
methodPatterns.addAll(k.getPatternsCondition().getPatterns());
}
}
}
});
// 添加请求方式对应的地址放行
map.forEach((k, v) -> urlRegistry.antMatchers(k, v.toArray(new String[0])).permitAll());
}
}

CustomAccess自定义访问控制注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.lang.annotation.*;

/**
* 自定义访问控制,需自行控制访问安全
* 不会经过 spring security,获取不到 spring security context
* swagger 在线文档上无 Authorize、小锁图标
*/
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomAccess {

// AccessStrategy value() default AccessStrategy.ANONYMOUS;
}

访问 http://localhost:port/swagger-ui/ 查看效果

生产环境禁用

1
2
3
4
5
6
springfox:
documentation:
swagger-ui:
enabled: false
open-api:
enabled: false
 评论