ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

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

2022-06-02 21:34:45  阅读:162  来源: 互联网

标签:OAuth2 Springboot 17 Swagger springframework org import swagger annotation


本文上接 “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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有