系统相关
首页 > 系统相关> > Spring 系列 (17) - Springboot+OAuth2(五) | 在基于内存验证的授权/资源服务器上使用 Swagger3

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