Springboot 系列 (9) - Springboot+OAuth2(四) | 搭建独立资源服务器

 

在 “ Spring 系列 (6) - Springboot+OAuth2(一) | 使用 Security 搭建基于内存验证的授权服务器  ” 里的项目 SpringbootExample06 完成了一个基于内存验证的授权服务器。

本文将搭建一个独立资源服务器,并使用 SpringbootExample06 来测试该独立资源服务器。


1. 开发环境

    Windows版本:Windows 10 Home (20H2)   
    IntelliJ IDEA (https://www.jetbrains.com/idea/download/):Community Edition for Windows 2020.1.4
    Apache Maven (https://maven.apache.org/):3.8.1

    注:Spring 开发环境的搭建,可以参考 “ Spring基础知识(1)- Spring简介、Spring体系结构和开发环境配置 ”。


2. 创建 Spring Boot 基础项目

    项目实例名称:SpringbootExample09
    Spring Boot 版本:2.6.6

    创建步骤:

        (1) 创建 Maven 项目实例 SpringbootExample09;
        (2) Spring Boot Web 配置;
        (3) 导入 Thymeleaf 依赖包;
        (4) 配置 jQuery;
        
    具体操作请参考 “Springboot 系列 (2) - 在 Spring Boot 项目里使用 Thymeleaf、JQuery+Bootstrap 和国际化” 里的项目实例 SpringbootExample02,文末包含如何使用 spring-boot-maven-plugin 插件运行打包的内容。

    SpringbootExample09 和 SpringbootExample02 相比,SpringbootExample09 不配置 Bootstrap、模版文件(templates/*.html)和国际化。


3. 导入 OAuth2 依赖包

    修改 pom.xml

 1         <project ... >
 2             ...
 3             <dependencies>
 4                 ...
 5 
 6                 <!-- OAuth2 -->
 7                 <dependency>
 8                     <groupId>org.springframework.security.oauth</groupId>
 9                     <artifactId>spring-security-oauth2</artifactId>
10                     <version>2.3.4.RELEASE</version>
11                 </dependency>
12 
13                 ...
14             </dependencies>
15 
16             ...
17         </project>


    在IDE中项目列表 -> SpringbootExample09 -> 点击鼠标右键 -> Maven -> Reload Project


4. 配置 Resource Server

    1) 修改 src/main/resources/application.properties 文件,修改后配置如下:

        spring.main.banner-mode=off

        # Web server
        server.display-name=SpringbootExample09-Test
        server.address=localhost
        server.port=8888

        # security
        spring.security.user.name=admin
        spring.security.user.password=123456
        spring.security.user.roles=admin

        # oauth2
        security.oauth2.client.client-id=3
        security.oauth2.client.client-secret=2lo2ijxJ3e
        security.oauth2.resource.token-info-uri=http://localhost:9090/oauth/check_token


        注:security 部分是配置 Oauth2.0 的默认登录用户。oauth2 部分是配置授权服务器(SpringbootExample06 项目)的参数用于 token 的远程验证。

    2) 创建 src/main/java/com/example/config/ResourceServerConfig.java 文件

 1         package com.example.config;
 2 
 3         import org.springframework.beans.factory.annotation.Value;
 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.RemoteTokenServices;
11 
12         @Configuration
13         @EnableResourceServer
14         public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
15 
16             @Value("${security.oauth2.resource.token-info-uri}")
17             private String tokenInfoUri;
18             @Value("${security.oauth2.client.client-id}")
19             private String clientId;
20             @Value("${security.oauth2.client.client-secret}")
21             private String clientSecret;
22 
23             @Override
24             public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
25 
26                 RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
27                 remoteTokenServices.setCheckTokenEndpointUrl(tokenInfoUri);
28                 remoteTokenServices.setClientId(clientId);
29                 remoteTokenServices.setClientSecret(clientSecret);
30 
31                 resources.tokenServices(remoteTokenServices);
32             }
33 
34             @Override
35             public void configure(HttpSecurity http) throws Exception {
36                 /*
37                     注意:
38 
39                     1. 必须先加上: .requestMatchers().antMatchers(...),表示对资源进行保护,也就是说,在访问前要进行OAuth认证。
40                     2. 接着:访问受保护的资源时,要具有相关权限。
41 
42                     否则,请求只是被 Security 的拦截器拦截,请求根本到不了 OAuth2 的拦截器。
43 
44                     requestMatchers() 部分说明:
45 
46                         mvcMatcher(String)}, requestMatchers(), antMatcher(String), regexMatcher(String), and requestMatcher(RequestMatcher).
47                 */
48 
49                 http
50                     // Since we want the protected resources to be accessible in the UI as well we need
51                     // session creation to be allowed (it's disabled by default in 2.0.6)
52                     // 如果不设置 session,那么通过浏览器访问被保护的任何资源时,每次是不同的 SessionID,并且将每次请求的历史都记录在 OAuth2Authentication 的 details 中
53                     .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
54                     .and()
55                     .requestMatchers()
56                     .antMatchers("/api/**","/private/**")
57                     .and()
58                     .authorizeRequests()
59                     .antMatchers("/api/**","/private/**")
60                     .authenticated();
61             }
62 
63         }


        注:/private/** 对应 src/main/resources/static/private 目录及其子目录,/api/** 对应 API 接口。


5. 测试实例 (Web 模式)

    1) 创建 src/main/resources/templates/get_res.html 文件

 1         <html lang="en" xmlns:th="http://www.thymeleaf.org">
 2         <head>
 3             <meta charset="UTF-8">
 4             <title>Get Resource</title>
 5             <script language="javascript" th:src="@{/lib/jquery/jquery-3.6.0.min.js}"></script>
 6         </head>
 7         <body>
 8 
 9             <p><strong>Get Resource from Resource Server</strong></p>
10 
11             <p>Access Token: <br><input type="text" name="access_token" id="access_token" style="width: 50%;" value="" /></p>
12             <p>Resources: <br>
13                 <select id="resource_url" name="resource_url" style="width: 50%;">
14                     <option value=""></option>
15                     <option th:value="@{/api/v1/user}" value="/api/v1/user" th:text="'API Resource -' + @{/api/v1/user}">API Resource</option>
16                     <option th:value="@{/private/res.html}" value="/private/res.html" th:text="'HTML Resource -' + @{/private/res.html}">HTML Resource</option>
17                 </select>
18             </p>
19             <p><button type="button" id="btn_get_res" class="btn btn-default">Get Resource</button></p>
20 
21             <p><textarea name="res_result" id="res_result" class="form-control" style="width: 50%;"  rows="5"></textarea></p>
22 
23 
24             <script type="text/javascript">
25 
26                 $(document).ready(function() {
27 
28                     $('#btn_get_res').click(function() {
29 
30                         var access_token = $('#access_token').val();
31                         if (access_token == '') {
32                             alert('Please enter access token');
33                             $('#access_token').focus();
34                             return;
35                         }
36 
37                         var resource_url = $('#resource_url').val();
38                         if (resource_url == '') {
39                             alert('Please select resource url');
40                             $('#resource_url').focus();
41                             return;
42                         }
43 
44                         $('#res_result').val('Get resource ...');
45 
46                         $.ajax({
47                             type: 'GET',
48                             url: resource_url,
49                             headers: { 'Authorization': 'Bearer ' + access_token },
50                             success: function(response) {
51 
52                                 console.log(response);
53                                 $('#res_result').val(JSON.stringify(response));
54 
55                             },
56                             error: function(err) {
57 
58                                 console.log(err);
59                                 $('#res_result').val('Error: AJAX issue');
60 
61                             }
62                         });
63 
64                     });
65                 });
66 
67             </script>
68         </body>
69         </html>


    2) 创建 src/main/resources/static/private/res.html 文件

 1         <html>
 2         <head>
 3             <meta charset="UTF-8">
 4             <title>Title</title>
 5         </head>
 6         <body>
 7 
 8             <h4>OAuth 2.0 - Resource Server</h4>
 9             <p>Resource Page (HTML)</p>
10 
11         </body>
12         </html> 


        访问 http://localhost:8888/private/res.html,会显示如下内容:

            This XML file does not appear to have any style information associated with it. The document tree is shown below.
            <oauth>
                <error_description>Full authentication is required to access this resource</error_description>
                <error>unauthorized</error>
            </oauth>

        注:上文 ResourceServerConfig 配置了 /private 目录及其子目录处于保护状态,需要 access token 才能访问。
    
    3) 修改 src/main/java/com/example/controller/IndexController.java 文件

 1         package com.example.controller;
 2 
 3         import org.springframework.stereotype.Controller;
 4         import org.springframework.web.bind.annotation.RequestMapping;
 5         import org.springframework.web.bind.annotation.ResponseBody;
 6 
 7         @Controller
 8         public class IndexController {
 9             @ResponseBody
10             @RequestMapping("/test")
11             public String test() {
12                 return "Test Page";
13             }
14 
15             @ResponseBody
16             @RequestMapping("/api/v1/user")
17             public String user() {
18                 return "API v1 user";
19             }
20 
21             @RequestMapping("/get/resource")
22             public String getRes() {
23                 return "get_res";
24             }
25         }


        访问 http://localhost:8888/api/v1/user,也会显示处于保护状态。


6. 运行

    1) 运行授权服务器(SpringbootExample06)

        访问 http://localhost:9090/oauth/test/client,,点击 Get Token 按钮,会在按钮下方显示如下JSON 格式数据:
            
            {"access_token":"91fae539-6d24-4075-8a39-4eb15d197645","token_type":"bearer","expires_in":43199,"scope":"All"}

        这里 91fae539-6d24-4075-8a39-4eb15d197645 就是我们所需要的 Access token。

    2) 访问被保护资源 (Resource Server)

        访问 http://localhost:8888/get/resource,使用上面取到的 Access Token 访问资源。
        

--------------------------------------

示例代码:https://gitee.com/slksm/public-codes/tree/master/demos/springboot-series/SpringbootExample09

 

posted @ 2022-05-17 22:27  垄山小站  阅读(841)  评论(0编辑  收藏  举报