前言: 之前在有台腾讯云的服务器上部署配置了一个SpringBoot微服务,没过多久平台提示项目下的某些包有安全漏洞,外加上Idea下编写pom依赖时,有些包也是提示了一些安全漏洞,就升级了一下SpringBoot版本和Swagger版本,结果升级版本后Swagger出现了一些问题,这里顺便记录一下Swagger的相关集成和配置
具体代码案例传送门:https://gitea.jnssd.com/jnssd/spring-boot-openapi
1、安装 目前SpringBoot下最新的Swagger版本为3.0版本,其实已经很久没有更新了,貌似现在的最新的SpringBoot上都是集成SpringDoc来配置Swagger的,SpringDoc这里按下不表,Swagger的依赖信息:
1 2 3 4 5 <dependency > <groupId > io.springfox</groupId > <artifactId > springfox-boot-starter</artifactId > <version > 3.0.0</version > </dependency >
2、Swagger实现 2.1、配置 项目下创建一个SwaggerConfig
类文件,添加如下配置信息:
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 package com.jnssd.config;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.builders.RequestHandlerSelectors;import springfox.documentation.oas.annotations.EnableOpenApi;import springfox.documentation.service.ApiInfo;import springfox.documentation.spi.DocumentationType;import springfox.documentation.spring.web.plugins.Docket;@Configuration @EnableOpenApi public class SwaggerConfig { @Bean public Docket api () { return new Docket (DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.jnssd" )) .paths(PathSelectors.any()) .build(); } public ApiInfo apiInfo () { return new ApiInfoBuilder () .title("Swagger项目测试" ) .description("novel项目接口文档" ) .build(); } }
2.2、访问 浏览器访问
http://127.0.0.1:8080/swagger-ui/index.html
即可得到如下页面信息:
3、Swagger配置安全方案
Swagger在SWAGGER_2
模式和OAS_30
模式下的安全方案配置是不一样的,以下是通过源码的方式说明和配置
3.1 Swagger下的SWAGGER_2
文档 3.1.1、源码说明 我们研究Swagger2下的访问源码,可看见Swagger在如下代码中配置实现了安全规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public Map<String, SecuritySchemeDefinition> toSecuritySchemeDefinitions (ResourceListing from) { if (from == null ) { return new HashMap <>(); } TreeMap<String, SecuritySchemeDefinition> result = new TreeMap <>(from.getSecuritySchemes().stream() .collect(toMap( SecurityScheme::getName, toSecuritySchemeDefinition()))); return result; } private Function<SecurityScheme, SecuritySchemeDefinition> toSecuritySchemeDefinition () {return input -> factories.get(input.getType()).create(input);}
可看见SecuritySchemeDefinition下的安全方案实现的,而该接口由以下几个类实现
而Swagger2下使用oauth2时,是通过OAuth类进行相关配置解析,通过OAuth2AuthFactory类下解析,源码为:
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 public SecuritySchemeDefinition create (SecurityScheme input) { OAuth oAuth = (OAuth) input; OAuth2Definition definition = new OAuth2Definition (); for (GrantType each : oAuth.getGrantTypes()) { if ("authorization_code" .equals(each.getType())) { definition.accessCode(((AuthorizationCodeGrant) each).getTokenRequestEndpoint().getUrl(), ((AuthorizationCodeGrant) each).getTokenEndpoint().getUrl()); } else if ("implicit" .equals(each.getType())) { definition.implicit(((ImplicitGrant) each).getLoginEndpoint().getUrl()); } else if ("application" .equals(each.getType())) { definition.application(((ClientCredentialsGrant) each).getTokenUrl()); } else if ("password" .equals(each.getType())) { definition.password(((ResourceOwnerPasswordCredentialsGrant) each).getTokenUrl()); } else { throw new IllegalArgumentException (String.format("Security scheme of type %s not supported" , input.getClass().getSimpleName())); } } for (AuthorizationScope each : oAuth.getScopes()) { definition.addScope(each.getScope(), each.getDescription()); } VendorExtensionsMapper vendorMapper = new VendorExtensionsMapper (); definition.setVendorExtensions(vendorMapper.mapExtensions(input.getVendorExtensions())); return definition; }
3.1.2、具体实现 SwaggerConfig类具体配置如下
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 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 package com.jnssd.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.util.AntPathMatcher;import springfox.documentation.builders.ApiInfoBuilder;import springfox.documentation.builders.OAuthBuilder;import springfox.documentation.builders.PathSelectors;import springfox.documentation.builders.RequestHandlerSelectors;import springfox.documentation.oas.annotations.EnableOpenApi;import springfox.documentation.service.*;import springfox.documentation.spi.DocumentationType;import springfox.documentation.spi.service.contexts.SecurityContext;import springfox.documentation.spring.web.plugins.Docket;import springfox.documentation.swagger.web.ApiKeyVehicle;import springfox.documentation.swagger2.annotations.EnableSwagger2;import java.util.ArrayList;import java.util.Arrays;import java.util.Collections;import java.util.List;@Configuration @EnableSwagger2 @EnableOpenApi public class SwaggerConfig { private final String[] securitySchemeNames = {"ApiKey模式" , "basicAuth模式" , "oauth2的Password模式" , "oauth2的authorization_code模式" , "oauth2的implicit模式" , "oauth2的clientCredentials模式" }; @Bean public Docket api () { return new Docket (DocumentationType.SWAGGER_2) .apiInfo(apiInfo()).select() .apis(RequestHandlerSelectors.basePackage("com.jnssd" )) .paths(PathSelectors.any()) .build() .securitySchemes(initSecuritySchemeList()) .securityContexts(Collections.singletonList(securityContext())); } public ApiInfo apiInfo () { return new ApiInfoBuilder () .title("Swagger项目测试" ) .description("novel项目接口文档" ) .build(); } public List<SecurityScheme> initSecuritySchemeList () { List<SecurityScheme> list = new ArrayList <>(); list.add(securitySchemeBasicAuth()); list.add(securitySchemeApiKey()); list.add(securitySchemeOAuth2Password()); list.add(securitySchemeOAuth2ClientCredentials()); list.add(securitySchemeOAuth2AuthorizationCode()); list.add(securitySchemeOAuth2Implicit()); return list; } private SecurityScheme securitySchemeApiKey () { return new ApiKey ("ApiKey模式" , "Authorization" , ApiKeyVehicle.HEADER.getValue()); } private SecurityScheme securitySchemeBasicAuth () { return new BasicAuth ("basicAuth模式" ); } private SecurityScheme securitySchemeOAuth2Password () { List<GrantType> grantTypes = new ArrayList <>(); grantTypes.add(new ResourceOwnerPasswordCredentialsGrant ("/oauth/token" )); return new OAuthBuilder ().name("oauth2的Password模式" ).scopes(scopes()).grantTypes(grantTypes).build(); } private SecurityScheme securitySchemeOAuth2AuthorizationCode () { List<GrantType> grantTypes = new ArrayList <>(); TokenRequestEndpoint tokenRequestEndpoint = new TokenRequestEndpoint ("/oauth/authorize" , "" , "" ); TokenEndpoint tokenEndpoint = new TokenEndpoint ("/oauth/token" , "token" ); grantTypes.add(new AuthorizationCodeGrant (tokenRequestEndpoint, tokenEndpoint)); return new OAuthBuilder ().name("oauth2的authorization_code模式" ).scopes(scopes()).grantTypes(grantTypes).build(); } private SecurityScheme securitySchemeOAuth2Implicit () { List<GrantType> grantTypes = new ArrayList <>(); ImplicitGrant implicitGrant = new ImplicitGrant (new LoginEndpoint ("/oauth/authorize" ), "token" ); grantTypes.add(implicitGrant); return new OAuthBuilder ().name("oauth2的implicit模式" ).scopes(scopes()).grantTypes(grantTypes).build(); } private SecurityScheme securitySchemeOAuth2ClientCredentials () { List<GrantType> grantTypes = new ArrayList <>(); grantTypes.add(new ClientCredentialsGrant ("/oauth/token" )); return new OAuthBuilder ().name("oauth2的clientCredentials模式" ).scopes(scopes()).grantTypes(grantTypes).build(); } private List<AuthorizationScope> scopes () { List<AuthorizationScope> list = new ArrayList <>(); list.add(new AuthorizationScope ("read_scope" , "Grants read access" )); list.add(new AuthorizationScope ("write_scope" , "Grants write access" )); list.add(new AuthorizationScope ("admin_scope" , "Grants read write and delete access" )); return list; } private SecurityContext securityContext () { List<SecurityReference> list = new ArrayList <>(); Arrays.stream(securitySchemeNames).forEach(name -> list.add(new SecurityReference (name, new AuthorizationScope [0 ]))); return SecurityContext.builder().operationSelector(operationContext -> { System.out.println("operationContext" + operationContext); AntPathMatcher pathMatcher = new AntPathMatcher (); String path = operationContext.requestMappingPattern(); return pathMatcher.match("/menu/**" , path) || pathMatcher.match("/user/**" , path); }).securityReferences(list).build(); } }
3.1.3、相关安全方案配置后的页面如下:
3.2 Swagger下的OAS_30
文档 3.2.1、源码说明 如果在创建Docket
时使用的是 new Docket(DocumentationType.OAS_30)
构建,页面上只有ApiKey
模式和BasicAuth
模式,OAS_30
文档下的安全方案的创建必须是通过OAuth2Scheme
类来构建的,因为在服务上渲染构建Swagger页面时,是通过如下配置生成的,具体代码如下:
springfox-oas-3.0.0-sources.jar!\springfox\documentation\oas\mappers\SecuritySchemeMapper.java
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 void mapScheme (Map<String, SecurityScheme> map, springfox.documentation.service.SecurityScheme scheme) { SecurityScheme mapped = null ; SecurityScheme securityScheme = new SecurityScheme () .extensions(new VendorExtensionsMapper ().mapExtensions(scheme.getVendorExtensions())); if (scheme instanceof HttpAuthenticationScheme) { mapped = securityScheme .type(SecurityScheme.Type.HTTP) .description(scheme.getDescription()) .bearerFormat(((HttpAuthenticationScheme) scheme).getBearerFormat()) .scheme(((HttpAuthenticationScheme) scheme).getScheme()); } else if (scheme instanceof OAuth2Scheme) { OAuthFlows flows = new OAuthFlows (); Scopes scopes = new Scopes (); ((OAuth2Scheme) scheme).getScopes() .forEach(s -> scopes.addString(s.getScope(), s.getDescription())); OAuthFlow flow = new OAuthFlow () .authorizationUrl(((OAuth2Scheme) scheme).getAuthorizationUrl()) .refreshUrl(((OAuth2Scheme) scheme).getRefreshUrl()) .tokenUrl(((OAuth2Scheme) scheme).getTokenUrl()) .scopes(scopes); switch (((OAuth2Scheme) scheme).getFlowType()) { case "password" : flows.password(flow); break ; case "clientCredentials" : flows.clientCredentials(flow); break ; case "authorizationCode" : flows.authorizationCode(flow); break ; case "implicit" : default : flows.implicit(flow); break ; } mapped = securityScheme .type(SecurityScheme.Type.OAUTH2) .description(scheme.getDescription()) .flows(flows); } else if (scheme instanceof ApiKey) { mapped = securityScheme .type(SecurityScheme.Type.APIKEY) .name(scheme.getName()) .in(mapIn(((ApiKey) scheme).getPassAs())); } else if (scheme instanceof OpenIdConnectScheme) { mapped = securityScheme .type(SecurityScheme.Type.OPENIDCONNECT) .name(scheme.getName()) .openIdConnectUrl(((OpenIdConnectScheme) scheme).getOpenIdConnectUrl()); } if (mapped != null ) { map.put(scheme.getName(), mapped); } }
因此,如果配置的权限的文档是OAS_30的,需要通过OAuth2Scheme对象构建相关安全规则
3.2.2、具体配置如下: 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 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 package com.jnssd.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.util.AntPathMatcher;import springfox.documentation.builders.ApiInfoBuilder;import springfox.documentation.builders.OpenIdConnectSchemeBuilder;import springfox.documentation.builders.PathSelectors;import springfox.documentation.builders.RequestHandlerSelectors;import springfox.documentation.oas.annotations.EnableOpenApi;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.ArrayList;import java.util.Arrays;import java.util.Collections;import java.util.List;@Configuration @EnableOpenApi public class SwaggerConfig { private final String[] securitySchemeNames = {"JWT模式" , "ApiKey模式" , "OIDC模式" , "oauth2的Password模式" , "oauth2的authorization_code模式" , "oauth2的implicit模式" , "oauth2的clientCredentials模式" }; @Bean public Docket api () { return new Docket (DocumentationType.OAS_30) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.jnssd" )) .paths(PathSelectors.any()) .build() .securitySchemes(initSecuritySchemeList()) .securityContexts(Collections.singletonList(securityContext())); } public ApiInfo apiInfo () { return new ApiInfoBuilder () .title("Swagger项目测试" ) .description("novel项目接口文档" ) .build(); } private List<SecurityScheme> initSecuritySchemeList () { List<SecurityScheme> list = new ArrayList <>(); list.add(httpAuthenticationScheme()); list.add(securitySchemeApiKey()); list.add(securitySchemeOpenIdConnect()); list.add(securitySchemeOauth2ClientCredentials()); list.add(securitySchemeOauth2implicit()); list.add(securitySchemeOauth2Password()); list.add(securitySchemeOauth2AuthorizationCode()); return list; } private SecurityScheme httpAuthenticationScheme () { return HttpAuthenticationScheme.JWT_BEARER_BUILDER.name("JWT模式" ).build(); } private SecurityScheme securitySchemeApiKey () { return new ApiKey ("ApiKey模式" , "Authorization" , "header" ); } private SecurityScheme securitySchemeOpenIdConnect () { return new OpenIdConnectSchemeBuilder () .name("OpenIdConnect授权" ) .description("OpenIdConnect授权配置" ) .openIdConnectUrl("https://your-openid-connect-url" ) .build(); } private SecurityScheme securitySchemeOauth2AuthorizationCode () { return OAuth2Scheme.OAUTH2_AUTHORIZATION_CODE_FLOW_BUILDER .name("oauth2的authorization_code模式" ) .authorizationUrl("/oauth/authorize" ) .tokenUrl("/oauth/token" ) .scopes(scopes()) .build(); } private SecurityScheme securitySchemeOauth2implicit () { return OAuth2Scheme.OAUTH2_IMPLICIT_FLOW_BUILDER .name("oauth2的implicit模式" ) .authorizationUrl("/oauth/authorize" ) .scopes(scopes()) .build(); } private SecurityScheme securitySchemeOauth2ClientCredentials () { return OAuth2Scheme.OAUTH2_CLIENT_CREDENTIALS_FLOW_BUILDER .name("oauth2的clientCredentials模式" ) .tokenUrl("/oauth/authorize" ) .scopes(scopes()) .build(); } private SecurityScheme securitySchemeOauth2Password () { return OAuth2Scheme.OAUTH2_PASSWORD_FLOW_BUILDER .name("oauth2的Password模式" ) .tokenUrl("/oauth/token" ) .scopes(scopes()) .build(); } private List<AuthorizationScope> scopes () { List<AuthorizationScope> list = new ArrayList <>(); list.add(new AuthorizationScope ("read_scope" , "Grants read access" )); list.add(new AuthorizationScope ("write_scope" , "Grants write access" )); list.add(new AuthorizationScope ("admin_scope" , "Grants read write and delete access" )); return list; } private SecurityContext securityContext () { List<SecurityReference> list = new ArrayList <>(); Arrays.stream(securitySchemeNames).forEach(name -> list.add(new SecurityReference (name, new AuthorizationScope [0 ]))); return SecurityContext.builder().operationSelector(operationContext -> { System.out.println("operationContext" + operationContext); AntPathMatcher pathMatcher = new AntPathMatcher (); String path = operationContext.requestMappingPattern(); return pathMatcher.match("/menu/**" , path) || pathMatcher.match("/user/**" , path); }).securityReferences(list).build(); } }
3.2.3、相关安全方案配置后的页面如下: OAS_30
的文档下在basicAuth
模式基础下,添加了JWT
模式,使用HttpAuthenticationScheme
类来构建basic
模式和JWT
模式,而其他方案的显示差不多之前展示了就不展示了,这里只对JWT
模式说明
JWT
模式
4、问题 4.1、启动报错如:Failed to start bean ‘documentationPluginsBootstrapper’ 如果使用的是2.7以上的SpringBoot版本,启动后会报如下错误信息:
1 2 3 4 5 Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled. 2023 -10 -16 14 :28 :47.623 ERROR 1476 --- [ main] o.s.boot.SpringApplication : Application run failedorg.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper' ; nested exception is java.lang.NullPointerException at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:182 ) ~[spring-context-5.3 .30 .jar:5.3 .30 ]
需要在application.yml下添加如下配置:
1 2 3 4 spring: mvc: pathmatch: matching-strategy: ant_path_matcher
重新启动即可