Spring 系列 (17) - Springboot+OAuth2(五) | 在基于内存验证的授权/资源服务器上使用 Swagger3
作者:互联网
本文上接 “Spring 系列 (9) - Springboot+OAuth2(四) | 搭建独立资源服务器”。
Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。总体目标是使客户端和文件系统作为服务器以同样的速度来更新。文件的方法,参数和模型紧密集成到服务器端的代码,允许 API 来始终保持同步。
作用:
(1) 接口(API)文档自动在线生成。
(2) 接口(API)功能测试。
Swagger 是一组开源项目,其中主要项目如下:
(1) Swagger-tools:提供各种与 Swagger 进行集成和交互的工具。例如模式检验、Swagger1.2 文档转换成Swagger2.0 文档等功能;
(2) Swagger-core:用于 Java/Scala 的 Swagger 实现。与 JAX-RS(Jersey、Resteasy、CXF…)、Servlets和Play框架进行集成;
(3) Swagger-js:用于 JavaScript 的 Swagger 实现;
(4) Swagger-node-express:Swagger 模块,用于 node.js 的 Express web 应用框架;
(5) Swagger-ui:一个无依赖的 HTML、JS 和 CSS 集合,可以为 Swagger 兼容 API 动态生成优雅文档;
(6) Swagger-codegen:一个模板驱动引擎,通过分析用户 Swagger 资源声明以各种语言生成客户端代码。
Swagger: https://swagger.io/
Swagger GitHub: https://github.com/swagger-api
在 “Spring 系列 (6) - Springboot+OAuth2(一) | 使用 Security 搭建基于内存验证的授权服务器” 里的项目 SpringbootExample06 完成了一个基于内存验证的授权服务器。
本文将完全复制 SpringbootExample06 的代码和配置到新项目 SpringbootExample17,并在新项目 SpringbootExample17 的基础上修改代码和配置,搭建一个与授权服务器共存的资源服务器,并添加 Swagger-ui 作为 API 测试工具。
注:把 SpringbootExample17 的 pom.xml 的 spring-boot-starter-parent 版本改成 2.5.7 (原版本 2.6.6 无法配置 Swagger 3.0)
1. Security & OAuth2 的配置
1) Security 配置
修改 src/main/java/com/example/config/WebSecurityConfig.java 文件
1 package com.example.config; 2 3 import org.springframework.context.annotation.Bean; 4 import org.springframework.context.annotation.Configuration; 5 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 6 import org.springframework.security.config.annotation.web.builders.HttpSecurity; 7 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 8 import org.springframework.security.authentication.AuthenticationManager; 9 import org.springframework.security.oauth2.provider.token.TokenStore; 10 import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; 11 12 @Configuration 13 public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 14 15 @Override 16 protected void configure(AuthenticationManagerBuilder auth) throws Exception { 17 18 auth.inMemoryAuthentication() 19 .withUser("admin").password("{noop}123456").roles("ADMIN") 20 .and() 21 .withUser("user").password("{noop}123456").roles("USER"); 22 } 23 24 @Override 25 protected void configure(HttpSecurity http) throws Exception { 26 // 配置认证 27 http.authorizeRequests() 28 .antMatchers("/error", "/lib/**", "/oauth/**").permitAll() 29 .anyRequest().authenticated() 30 .and() 31 .formLogin() 32 .and() 33 .csrf().disable(); // 关闭 csrf 保护功能,默认是开启的 34 } 35 36 @Bean 37 public AuthenticationManager authenticationManagerBean() throws Exception { 38 return super.authenticationManagerBean(); 39 } 40 41 @Bean 42 public TokenStore tokenStoreBean() { 43 // token保存在内存中(也可以保存在数据库、Redis中)。 44 // 如果保存在中间件(数据库、Redis),那么资源服务器与认证服务器可以不在同一个工程中。 45 // 注意:如果不保存 access_token,则没法通过 access_token 取得用户信息 46 return new InMemoryTokenStore(); 47 } 48 }
2) Authorization Server 配置
修改 src/main/java/com/example/config/oauth2/AuthorizationServerConfig.java 文件
1 package com.example.config.oauth2; 2 3 import org.springframework.context.annotation.Configuration; 4 import org.springframework.beans.factory.annotation.Autowired; 5 import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; 6 import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; 7 import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; 8 import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; 9 import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; 10 import org.springframework.security.authentication.AuthenticationManager; 11 import org.springframework.security.oauth2.provider.token.TokenStore; 12 13 @Configuration 14 @EnableAuthorizationServer 15 public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { 16 @Autowired 17 private AuthenticationManager authenticationManager; 18 @Autowired 19 private TokenStore tokenStore; 20 21 @Override 22 public void configure(AuthorizationServerSecurityConfigurer authServer) { 23 // 访问权限控制 24 authServer.tokenKeyAccess("permitAll()") 25 .checkTokenAccess("permitAll()") // 如果要限制访问 /oauth/check_token,可以设置为 "isAuthenticated()" 26 .allowFormAuthenticationForClients(); 27 } 28 29 @Override 30 public void configure(ClientDetailsServiceConfigurer clients) throws Exception { 31 32 // 使内存模式 33 clients.inMemory().withClient("1") 34 .secret("{noop}4eti4hAaux") 35 .authorizedGrantTypes("authorization_code", "refresh_token") 36 .scopes("All") 37 //.resourceIds("rids_1") 38 .autoApprove(true) 39 .redirectUris("/oauth/test/code/callback") 40 .and() 41 .withClient("2") 42 .secret("{noop}xGJoD2i2lj") 43 .authorizedGrantTypes("implicit") 44 .scopes("All") 45 //.resourceIds("rids_2") 46 .autoApprove(true) 47 .redirectUris("/oauth/test/implicit/callback") 48 .and() 49 .withClient("3") 50 .secret("{noop}2lo2ijxJ3e") 51 .authorizedGrantTypes("password", "client_credentials") 52 .scopes("All") 53 //.resourceIds("rids_3") 54 .autoApprove(true); 55 } 56 57 @Override 58 public void configure(AuthorizationServerEndpointsConfigurer endpoints) { 59 60 endpoints.authenticationManager(authenticationManager); 61 endpoints.tokenStore(tokenStore); 62 63 } 64 65 }
注:withClient() 设置的 client_id 和 resourceIds() 设置的 resource_id 之间有约束关系,即 client 访问资源时,如果 Client 和 Resource Server 都设置了 resource Id,就会比对 resource Id。
3) Resource Server 配置
创建 src/main/java/com/example/config/oauth2/ResourceServerConfig.java 文件
1 package com.example.config.oauth2; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.context.annotation.Configuration; 5 import org.springframework.security.config.annotation.web.builders.HttpSecurity; 6 import org.springframework.security.config.http.SessionCreationPolicy; 7 import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; 8 import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; 9 import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; 10 import org.springframework.security.oauth2.provider.token.TokenStore; 11 12 @Configuration 13 @EnableResourceServer 14 public class ResourceServerConfig extends ResourceServerConfigurerAdapter { 15 16 @Autowired 17 private TokenStore tokenStore; 18 19 @Override 20 public void configure(ResourceServerSecurityConfigurer resources) throws Exception { 21 22 resources.tokenStore(tokenStore); 23 //resources.tokenStore(tokenStore).resourceId("rids_1"); 24 } 25 26 @Override 27 public void configure(HttpSecurity http) throws Exception { 28 /* 29 注意: 30 31 1. 必须先加上: .requestMatchers().antMatchers(...),表示对资源进行保护,也就是说,在访问前要进行OAuth认证。 32 2. 接着:访问受保护的资源时,要具有相关权限。 33 34 否则,请求只是被 Security 的拦截器拦截,请求根本到不了 OAuth2 的拦截器。 35 36 requestMatchers() 部分说明: 37 38 mvcMatcher(String)}, requestMatchers(), antMatcher(String), regexMatcher(String), and requestMatcher(RequestMatcher). 39 */ 40 41 http 42 // Since we want the protected resources to be accessible in the UI as well we need 43 // session creation to be allowed (it's disabled by default in 2.0.6) 44 // 如果不设置 session,那么通过浏览器访问被保护的任何资源时,每次是不同的 SessionID,并且将每次请求的历史都记录在 OAuth2Authentication 的 details 中 45 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) 46 .and() 47 .requestMatchers() 48 .antMatchers("/api/**") 49 .and() 50 .authorizeRequests() 51 .antMatchers("/api/**") 52 .authenticated(); 53 } 54 }
注:/api/** 对应 JSON 接口。
运行并访问 http://localhost:9090/oauth/test/client,可获取 Access Token。
4. 配置 Swagger3
1) 修改 pom.xml,导入 Swagger3 依赖包
1 <project ... > 2 ... 3 <dependencies> 4 ... 5 6 <dependency> 7 <groupId>io.springfox</groupId> 8 <artifactId>springfox-boot-starter</artifactId> 9 <version>3.0.0</version> 10 </dependency> 11 12 ... 13 </dependencies> 14 15 ... 16 </project>
在IDE中项目列表 -> SpringbootExample17 -> 点击鼠标右键 -> Maven -> Reload Project
注:springfox-boot-starter 的 3.0.0 版本在 Spring Boot 2.6.6 上无法配置运行,本文把 Spring Boot 版本降到 2.5.7。
Swagger 3.0 默认是开启的,访问 http://localhost:9090/swagger-ui/ ,自动跳转到 http://localhost:9090/login,登录后才能访问 http://localhost:9090/swagger-ui/。
如需取消 http://localhost:9090/swagger-ui/ 的安全保护,可在 WebSecurityConfig 类里添加如下代码:
1 ... 2 3 import org.springframework.security.config.annotation.web.builders.WebSecurity; 4 5 ... 6 7 @Override 8 public void configure(WebSecurity web) throws Exception { 9 web.ignoring().antMatchers( "/swagger-ui/*", 10 "/swagger-resources/**", 11 "/v2/api-docs", 12 "/v3/api-docs", 13 "/webjars/**"); 14 }
2) 创建 src/main/java/com/example/config/Swagger3Config.java 文件
1 package com.example.config; 2 3 import java.util.List; 4 import java.util.ArrayList; 5 6 import org.springframework.beans.factory.annotation.Value; 7 import org.springframework.context.annotation.Bean; 8 import org.springframework.context.annotation.Configuration; 9 import springfox.documentation.builders.ApiInfoBuilder; 10 import springfox.documentation.builders.PathSelectors; 11 import springfox.documentation.builders.RequestHandlerSelectors; 12 import springfox.documentation.service.ApiInfo; 13 import springfox.documentation.service.ApiKey; 14 import springfox.documentation.service.SecurityReference; 15 import springfox.documentation.service.SecurityScheme; 16 import springfox.documentation.service.AuthorizationScope; 17 import springfox.documentation.spi.DocumentationType; 18 import springfox.documentation.spi.service.contexts.SecurityContext; 19 import springfox.documentation.spring.web.plugins.Docket; 20 21 @Configuration 22 public class Swagger3Config { 23 @Value("${swagger.enable}") 24 private boolean enable; 25 @Value("${swagger.application.title}") 26 private String applicationTitle; 27 @Value("${swagger.application.description}") 28 private String applicationDescription; 29 @Value("${swagger.application.version}") 30 private String applicationVersion; 31 @Value("${swagger.terms.url}") 32 private String termsUrl; 33 34 @Bean 35 public Docket docketBean() { 36 37 return new Docket(DocumentationType.OAS_30) 38 .enable(enable) 39 .apiInfo(apiInfo()) 40 .select() 41 // API 分组 42 //.groupName("") 43 // 监听指定包的接口 44 .apis(RequestHandlerSelectors.basePackage("com.example.swagger3")) 45 // 监听所有接口 46 //.apis(RequestHandlerSelectors.any()) 47 .paths(PathSelectors.any()) 48 .build() 49 .securitySchemes(securitySchemes()) 50 .securityContexts(securityContexts()); 51 } 52 53 private ApiInfo apiInfo() { 54 return new ApiInfoBuilder() 55 .title(applicationTitle) 56 .description(applicationDescription) 57 .termsOfServiceUrl(termsUrl) 58 .version(applicationVersion) 59 .build(); 60 } 61 62 63 private List<SecurityScheme> securitySchemes() { 64 // 设置请求头信息 65 List<SecurityScheme> result = new ArrayList<>(); 66 ApiKey apiKey = new ApiKey("Authorization", "Authorization", "header"); 67 result.add(apiKey); 68 return result; 69 } 70 71 private List<SecurityContext> securityContexts() { 72 List<SecurityContext> securityContexts = new ArrayList<>(); 73 securityContexts.add( 74 SecurityContext.builder() 75 .securityReferences(defaultAuth()) 76 //.operationSelector(o -> o.requestMappingPattern().matches("/.*")) 77 .forPaths(PathSelectors.any()) 78 .build()); 79 return securityContexts; 80 } 81 82 List<SecurityReference> defaultAuth() { 83 AuthorizationScope authorizationScope = new AuthorizationScope("global", "Global Access"); 84 AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; 85 authorizationScopes[0] = authorizationScope; 86 List<SecurityReference> securityReferences = new ArrayList<>(); 87 securityReferences.add(new SecurityReference("Authorization", authorizationScopes)); 88 return securityReferences; 89 } 90 }
注:DocumentationType.OAS_30 表示 OpenApi 3.0,springfox-boot-starter 提供的 swagger 推荐使用 OAS_30 类型。如果需要使用 Swagger 2 类型,可以修改为 DocumentationType.SWAGGER_2 。
3) 修改 src/main/resources/application.properties 文件,添加如下配置
# swagger
swagger.enable=true
swagger.application.title=Test API
swagger.application.description=Swagger 3.0 API
swagger.application.version=1.0
swagger.terms.url=
注:swagger.enable=false 表示关闭 swagger,无法访问 http://localhost:9090/swagger-ui/。
4) Swagger 文档注解
swagger 通过注解表明该接口会生成文档,包括接口名、请求方法、参数、返回信息等。
@Api:修饰整个类,描述 Controller 的作用
@ApiOperation:描述一个类的一个方法,或者说一个接口
@ApiParam:单个参数描述
@ApiModel:用对象来接收参数
@ApiProperty:用对象接收参数时,描述对象的一个字段
@ApiResponse:HTTP 响应其中 1 个描述
@ApiResponses:HTTP 响应整体描述
@ApiIgnore:使用该注解忽略这个 API
@ApiError:发生错误返回的信息
@ApiImplicitParam:一个请求参数
@ApiImplicitParams:多个请求参数
创建 src/main/java/com/example/swagger3/ApiController.java 文件
1 package com.example.swagger3; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.Map; 6 import java.util.HashMap; 7 8 import org.springframework.web.bind.annotation.RestController; 9 import org.springframework.web.bind.annotation.PostMapping; 10 import org.springframework.web.bind.annotation.RequestMapping; 11 12 import io.swagger.annotations.Api; 13 import io.swagger.annotations.ApiOperation; 14 import io.swagger.annotations.ApiImplicitParam; 15 import io.swagger.annotations.ApiImplicitParams; 16 import io.swagger.annotations.ApiResponse; 17 import io.swagger.annotations.ApiResponses; 18 19 @Api(tags = "API Message Interface") 20 @RestController 21 @RequestMapping("/api") 22 public class ApiController { 23 24 @ApiOperation("Get message list by group id and user name") 25 @ApiImplicitParams(value = { 26 @ApiImplicitParam(name = "id", value = "Group id"), 27 @ApiImplicitParam(name = "name", value = "User name",required = true) 28 }) 29 @ApiResponses({ 30 @ApiResponse(code = 200, message = "OK"), 31 @ApiResponse(code = 204, message = "No result") 32 }) 33 @PostMapping("/message/list") 34 public List<String> getMessageList(Integer id, String name){ 35 List<String> list = new ArrayList<>(); 36 list.add("Message 1 - " + id); 37 list.add("Message 2 - " + name); 38 return list; 39 } 40 41 @ApiOperation("Save message") 42 @ApiImplicitParam(name = "msg", value = "Message") 43 @PostMapping("/message/save") 44 public Map<String, String> save(String msg){ 45 Map<String, String> map = new HashMap<>(); 46 map.put("ret", "OK"); 47 map.put("message", msg); 48 return map; 49 } 50 51 @GetMapping("/demo") 52 public String demo(){ 53 return "Demo Page"; 54 } 55 56 }
运行并访问 http://localhost:9090/swagger-ui/,点击页面上 /api/demo 下的 “Try it out -> Execute”,Response Body 返回值:
{
"error": "unauthorized",
"error_description": "Full authentication is required to access this resource"
}
很显然,/api/** 是 OAuth2 的保护资源,需要 Access Token 才能访问。
访问 http://localhost:9090/oauth/test/client,点击 Get Token 按钮,会在按钮下方显示如下JSON 格式数据:
{"access_token":"91fae539-6d24-4075-8a39-4eb15d197645","token_type":"bearer","expires_in":43199,"scope":"All"}
返回 http://localhost:9090/swagger-ui/ 页面,点击 “Authorie” 按钮,跳出 “Available authorizations” 对话框,在输入框内输入:
Bearer 91fae539-6d24-4075-8a39-4eb15d197645
点击对话框的 “Authorie” 按钮,点击 Close 关闭对话框,再点击页面上 /api/demo 下的 “Try it out -> Execute”,Response Body 返回值:
Demo Page
5. 配置 Knife4j (非必需)
Knife4j 是为 Java MVC 框架集成 Swagger 生成 Api 文档的增强解决方案。
Knife4j:https://doc.xiaominfo.com/knife4j/
修改 pom.xml,导入 Knife4j 依赖包
1 <project ... > 2 ... 3 <dependencies> 4 ... 5 6 <dependency> 7 <groupId>com.github.xiaoymin</groupId> 8 <artifactId>knife4j-spring-boot-starter</artifactId> 9 <version>3.0.3</version> 10 </dependency> 11 12 ... 13 </dependencies> 14 15 ... 16 </project>
在IDE中项目列表 -> SpringbootExample17 -> 点击鼠标右键 -> Maven -> Reload Project
访问 http://localhost:9090/doc.html,自动跳转到 http://localhost:9090/login,登录后才能访问 http://localhost:9090/doc.html。
可以在 WebSecurityConfig 类里修改代码,取消 /doc.html 的安全保护:
1 @Override 2 public void configure(WebSecurity web) throws Exception { 3 web.ignoring().antMatchers( ... 4 5 "/doc.html", 6 7 ...); 8 }
标签:OAuth2,Springboot,17,Swagger,springframework,org,import,swagger,annotation 来源: https://www.cnblogs.com/tkuang/p/16339065.html