Spring 系列 (6) – 在 Spring Boot 项目里使用 Security 和 OAuth2 搭建授权服务器(一)(Spring series (6) – using security and oauth2 to build an authorization server in the spring boot project (1))

Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架。Spring Boot 给 Spring Security 提供了自动化配置方案 (spring-boot-starter-security),可以零配置使用 Spring Security。OAuth 2.0 是 OAuth 协议的延续版本,但不向前兼容 OAuth 1.0 (即完全废止了 OAuth 1.0)。OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。比如:一个网站,允许用户不注册为该网站用户,而是使用微信授权后登录该网站。微信在这个流程中就处于 OAuth2 server(授权服务器)的位置,这个网站处于 OAuth client,这个网站(OAuth client)和微信(OAuth server)属于信任关系,带着微信授权令牌的用户,也被这个网站信任。

1. OAuth 简介

    1) OAuth 基本概念

        (1) Third-party Application:第三方应用程序,本文中又称 “客户端”(client);        (2) HTTP Service:HTTP 服务提供商,本文中简称 “服务提供商”;        (3) Resource Owner:资源所有者,本文中又称 “用户”(user);        (4) User Agent:用户代理,本文中就是指浏览器;        (5) Authorization Server:认证服务器,即服务提供商专门用来处理认证的服务器。        (6) Resource Server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。      OAuth 在 “客户端”与 “服务提供商” 之间,设置了一个授权层(Authorization Layer)。”客户端” 不能直接登录 “服务提供商”,只能登录授权层,以此将用户与客户端区分开来。”客户端” 登录授权层所用的令牌(Token),与用户的密码不同。用户可以在登录的时候,指定授权层令牌的权限范围和有效期。      ”客户端” 登录授权层以后,”服务提供商” 根据令牌的权限范围和有效期,向 “客户端” 开放用户储存的资料。

    2) 授权模式

        授权码模式(Authorization Code)、简化模式(Implicit)、密码模式(Resource Owner Password Credentials)、客户端模式(Client Credentials)        (1) 授权码模式(Authorization Code)             a) 用户访问客户端,后者将前者导向认证服务器;            b) 用户选择是否给予客户端授权;            c) 假设用户给予授权,认证服务器将用户导向客户端事先指定的”重定向URI”(Redirection URI),同时附上一个授权码;            d) 客户端收到授权码,附上早先的”重定向URI”,向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见;            e) 认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(Access Token)和更新令牌(Refresh Token);            注:授权码模式是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与 “服务提供商” 的认证服务器进行互动。      (2) 简化模式(Implicit)            a) 用户决定是否给于客户端授权;            b) 假设用户给予授权,认证服务器将用户导向客户端指定的 “重定向 URI”,并在 URI 的 Hash 部分包含了访问令牌;            c) 浏览器向资源服务器发出请求,其中不包括上一步收到的 Hash 值;            d) 资源服务器返回一个网页,其中包含的代码可以获取Hash值中的令牌;            e) 浏览器执行上一步获得的脚本,提取出令牌;            f) 浏览器将令牌发给客户端;            注: 简化模式 (或称隐式模式),不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了 “授权码” 这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。       (3) 密码模式(Resource Owner Password Credentials)            a) 用户向客户端提供用户名和密码;            b) 客户端将用户名和密码发给认证服务器,向后者请求令牌;            c) 认证服务器确认无误后,向客户端提供访问令牌;            注:密码模式,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向 “服务商提供商” 索要授权。        (4) 客户端模式(Client Credentials)            a) 客户端向认证服务器进行身份认证,并要求一个访问令牌;            b) 认证服务器确认无误后,向客户端提供访问令牌;            注:客户端模式指客户端以自己的名义,而不是以用户的名义,向 “服务提供商” 进行认证。严格地说,客户端模式并不属于 OAuth 框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求 “服务提供商” 提供服务,其实不存在授权问题。    OAuth 的客户端凭证校验是通过 ClientDetailsService 来实现的,OAuth 默认为我们提供了InMemoryClientDetailsService (内存验证) 和 JdbcClientDetailsService(JDBC验证),也可以实现自定义的 ClientDetailsService (自定义验证)。        本文将搭建一个基于内存验证的授权服务器。

2. 开发环境

    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体系结构和开发环境配置 ”。

3. 创建 Spring Boot 基础项目

    项目实例名称:SpringbootExample06    Spring Boot 版本:2.6.6    创建步骤:        (1) 创建 Maven 项目实例 SpringbootExample06;        (2) Spring Boot Web 配置;        (3) 导入 Thymeleaf 依赖包;        (4) 配置 jQuery;            具体操作请参考 “Spring 系列 (2) – 在 Spring Boot 项目里使用 Thymeleaf、JQuery+Bootstrap 和国际化” 里的项目实例 SpringbootExample02,文末包含如何使用 spring-boot-maven-plugin 插件运行打包的内容。    SpringbootExample06 和 SpringbootExample02 相比,SpringbootExample06 不配置 Bootstrap、模版文件(templates/*.html)和国际化。

4. 导入 Security、 OAuth2 依赖包

    修改 pom.xml

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

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

5. 配置 Security & OAuth2 (基于内存验证)

    1) 创建 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 
10         @Configuration
11         public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
12 
13             @Override
14             protected void configure(AuthenticationManagerBuilder auth) throws Exception {
15                 // 内存模式
16                 auth.inMemoryAuthentication()
17                         .withUser("admin").password("{noop}123456").roles("ROLE_ADMIN")
18                         .and()
19                         .withUser("user").password("{noop}123456").roles("ROLE_USER");
20             }
21 
22             @Override
23             protected void configure(HttpSecurity http) throws Exception {
24                 // 配置认证
25                 http.authorizeRequests()
26                         .antMatchers("/error", "/lib/**", "/oauth/**").permitAll()
27                         .anyRequest().authenticated()
28                         .and()
29                         .formLogin()
30                         .and()
31                         .csrf().disable(); // 关闭 csrf 保护功能,默认是开启的
32             }
33 
34             @Bean
35             public AuthenticationManager authenticationManagerBean() throws Exception {
36                 return super.authenticationManagerBean();
37             }
38 
39         }

        注:{noop} 表示 NoOpPasswordEncoder,{noop} 配合没有加过密的密码使用;{bcrypt} 表示 BCryptPasswordEncoder,{bcrypt} 配合加密过的密码使用;其它加密方式,参考两者的命名方式用小写字母。        这里定义的 AuthenticationManager 是 Spring Bean,在 AuthorizationServerConfig 里会装配使用。    2) 创建 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 
12         @Configuration
13         @EnableAuthorizationServer
14         public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter  {
15             @Autowired
16             private AuthenticationManager authenticationManager;
17 
18             @Override
19             public void configure(AuthorizationServerSecurityConfigurer authServer) {
20                 // 访问权限控制
21                 authServer.tokenKeyAccess("permitAll()")
22                         .checkTokenAccess("isAuthenticated()")
23                         .allowFormAuthenticationForClients();
24             }
25 
26             @Override
27             public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
28 
29                 // 使内存模式
30                 clients.inMemory().withClient("1")
31                                     .secret("{noop}4eti4hAaux")
32                                     .authorizedGrantTypes("authorization_code", "refresh_token")
33                                     .scopes("All")
34                                     .autoApprove(true)
35                                     .redirectUris("/oauth/test/code/callback")
36                                     .and()
37                                     .withClient("2")
38                                     .secret("{noop}xGJoD2i2lj")
39                                     .authorizedGrantTypes("implicit")
40                                     .scopes("All")
41                                     .autoApprove(true)
42                                     .redirectUris("/oauth/test/implicit/callback")
43                                     .and()
44                                     .withClient("3")
45                                     .secret("{noop}2lo2ijxJ3e")
46                                     .authorizedGrantTypes("password", "client_credentials")
47                                     .scopes("All")
48                                     .autoApprove(true);
49             }
50 
51             @Override
52             public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
53 
54                 endpoints.authenticationManager(authenticationManager);
55 
56             }
57 
58         }

6. 测试实例 (Web 模式)

    1) 授权码模式(Authorization Code)实例

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

  1             <html>
  2             <head>
  3                 <meta charset="UTF-8">
  4                 <title th:text="${var}">Title</title>
  5                 <script language="javascript" th:src="@{/lib/jquery/jquery-3.6.0.min.js}"></script>
  6             </head>
  7             <body>
  8 
  9                 <h4>OAuth 2.0 - Authorization Code</h4>
 10 
 11                 <p>Token URL: <br><input type="text" name="url" id="url" style="width: 50%;" th:value="@{/oauth/token}" value="" /></p>
 12                 <p>Client Id: <br><input type="text" name="client_id" id="client_id" style="width: 50%;" value="1" /></p>
 13                 <p>Client Secret: <br><input type="text" name="client_secret" id="client_secret" style="width: 50%;" value="4eti4hAaux" /></p>
 14                 <p>Code: <br><textarea name="code" id="code" rows="5" style="width: 50%;" readonly th:text="${code}"></textarea></p>
 15 
 16                 <p> </p>
 17                 <p>
 18                     <button type="button" id="btn_get_token" th:unless="${code}==null">Get Token</button>
 19                     <button type="button" id="btn_refresh_code" th:if="${code}==null">Refresh Code</button>
 20                     <br><br><textarea name="result" id="result" style="width: 50%;" rows="8"></textarea>
 21                 </p>
 22 
 23                 <p> </p>
 24                 <script type="text/javascript">
 25 
 26                     var tokenObj = null;
 27 
 28                     $(document).ready(function(){
 29 
 30                         $('#btn_get_token').click(function() {
 31 
 32                             var url = $('#url').val();
 33                             if (url == '') {
 34                                 alert('Please enter url');
 35                                 $('#url').focus();
 36                                 return;
 37                             }
 38 
 39                             var client_id = $('#client_id').val();
 40                             if (client_id == '') {
 41                                 alert('Please enter client id');
 42                                 $('#client_id').focus();
 43                                 return;
 44                             }
 45 
 46                             var client_secret = $('#client_secret').val();
 47                             if (client_secret == '') {
 48                                 alert('Please enter client secret');
 49                                 $('#client_secret').focus();
 50                                 return;
 51                             }
 52 
 53                             var code = $('#code').val();
 54                             if (code == '') {
 55                                 alert('Please enter code');
 56                                 $('#code').focus();
 57                                 return;
 58                             }
 59 
 60                             $('#result').val('Sending post ...');
 61 
 62                             $.ajax({
 63 
 64                                 type: 'POST',
 65                                 url: url,
 66                                 data: {
 67                                     grant_type: 'authorization_code',
 68                                     client_id: client_id,
 69                                     client_secret: client_secret,
 70                                     redirect_uri: '/oauth/test/code/callback',
 71                                     code: code,
 72                                 },
 73                                 success: function(response) {
 74 
 75                                     console.log(response);
 76                                     $('#result').val(JSON.stringify(response));
 77 
 78                                     tokenObj = response;
 79 
 80                                 },
 81                                 error: function() {
 82 
 83                                     $('#result').val('Error: AJAX issue');
 84 
 85                                     tokenObj = null;
 86 
 87                                 }
 88                             });
 89 
 90                         });
 91 
 92                         $('#btn_refresh_code').click(function() {
 93                             window.location.href = "/oauth/test/code";
 94                         });
 95 
 96                     });
 97 
 98                 </script>
 99 
100             </body>
101             </html>

        (2) 创建 src/main/java/com/example/controller/OauthController.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.RequestParam;
 6             import org.springframework.ui.Model;
 7 
 8             @Controller
 9             @RequestMapping("/oauth")
10             public class OauthController {
11 
12                 @RequestMapping("/test/code")
13                 public String testCode() {
14                     return "redirect:/oauth/authorize?client_id=1&redirect_uri=/oauth/test/code/callback&response_type=code";
15                 }
16 
17                 @RequestMapping("/test/code/callback")
18                 public String testCodeCallback(@RequestParam String code, Model model) {
19                     model.addAttribute("code", code);
20                     return "authorization_code";
21                 }
22             }

            运行并访问 http://localhost:9090/oauth/test/code,自动跳转到 http://localhost:9090/login (Spring security 的默认页面)。                        输入上文 WebSecurityConfig 里配置的用户名/密码 (admin/123456),登录后跳转到 http://localhost:9090/test/code/callback,点击 Get Token 按钮,会在按钮下方显示如下数据:                {                    “access_token”: “0cea651f-4d01-43c0-9421-bc70d4eca081”,                    “token_type”: “bearer”,                    “refresh_token”: “a3418c82-5f80-4ee8-a1dd-9bdbfce52bdc”,                    “expires_in”: 43199,                    “scope”: “All”                }    2) 简化模式(Implicit)实例        (1) 创建 src/main/resources/templates/implicit.html 文件

 1             <html>
 2             <head>
 3                 <meta charset="UTF-8">
 4                 <title th:text="${var}">Title</title>
 5                 <script language="javascript" th:src="@{/lib/jquery/jquery-3.6.0.min.js}"></script>
 6             </head>
 7             <body>
 8 
 9                 <h4>OAuth 2.0 - Implicit</h4>
10 
11                 <p id="message"></p>
12 
13                 <script type="text/javascript">
14 
15                     function QueryStringToJSON(str) {
16                         var pairs = str.split('&');
17                         var result = {};
18                         pairs.forEach(function (pair) {
19                             pair = pair.split('=');
20                             var name = pair[0]
21                             var value = pair[1]
22                             if (name.length)
23                                 if (result[name] !== undefined) {
24                                     if (!result[name].push) {
25                                         result[name] = [result[name]];
26                                     }
27                                     result[name].push(value || '');
28                                 } else {
29                                     result[name] = value || '';
30                                 }
31                         });
32                         return (result);
33                     }
34 
35                     $(document).ready(function() {
36 
37                         var urlObj = window.location.href.split("#");
38                         if (urlObj.length > 1) {
39 
40                             var str = '';
41                             var obj = QueryStringToJSON(urlObj[1]);
42                             for (k in obj) {
43                                 str += k + ': ' + obj[k] + '<br>';
44                             }
45 
46                             $('#message').html(str);
47 
48                         }
49 
50                     });
51 
52                 </script>
53             </body>
54             </html>

        (2) 修改 src/main/java/com/example/controller/OauthController.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.RequestParam;
 6             import org.springframework.ui.Model;
 7 
 8             @Controller
 9             @RequestMapping("/oauth")
10             public class OauthController {
11 
12                 ...
13 
14                 @RequestMapping("/test/implicit")
15                 public String testImplicit() {
16                     return "redirect:/oauth/authorize?client_id=2&client_secret=xGJoD2i2lj&grant_type=implicit&response_type=token";
17                 }
18 
19                 @RequestMapping("/test/implicit/callback")
20                 public String testImplicitCallback(HttpServletRequest request) {
21                     return "implicit";
22                 }
23 
24             }

            运行并访问 http://localhost:9090/oauth/test/implicit,自动跳转到 http://localhost:9090/login (Spring security 的默认页面)。                        输入上文 WebSecurityConfig 里配置的用户名和密码登录,登录后跳转到 http://localhost:9090/test/implicit/callback#access_token=52856cfb-29ab-46dc-87af-06a7d025bb77&token_type=bearer&expires_in=40739&scope=All,解析 “#” 符号之后的数据,显示如下:                OAuth 2.0 – Implicit                access_token: 52856cfb-29ab-46dc-87af-06a7d025bb77                token_type: bearer                expires_in: 40739                scope: All

    3) 密码模式(Resource Owner Password Credentials)实例        (1) 创建 src/main/resources/templates/password.html 文件

 1             <html>
 2             <head>
 3                 <meta charset="UTF-8">
 4                 <title th:text="${var}">Title</title>
 5                 <script language="javascript" th:src="@{/lib/jquery/jquery-3.6.0.min.js}"></script>
 6             </head>
 7             <body>
 8 
 9                 <h4>OAuth 2.0 - Password</h4>
10 
11                 <p>Token URL: <br><input type="text" name="url" id="url" style="width: 50%;" th:value="@{/oauth/token}" value="" /></p>
12                 <p>Client Id: <br><input type="text" name="client_id" id="client_id" style="width: 50%;" value="3" /></p>
13                 <p>Client Secret: <br><input type="text" name="client_secret" id="client_secret" style="width: 50%;" value="2lo2ijxJ3e" /></p>
14                 <p>Username: <br><input type="text" name="username" id="username" style="width: 50%;" value="admin" /></p>
15                 <p>Password: <br><input type="text" name="password" id="password" style="width: 50%;" value="123456" /></p>
16 
17                 <p> </p>
18                 <p>
19                     <button type="button" id="btn_get_token" >Get Token</button>
20                     <br><br><textarea name="result" id="result" style="width: 50%;" rows="8"></textarea>
21                 </p>
22 
23                 <script type="text/javascript">
24 
25                     $(document).ready(function() {
26 
27                     $('#btn_get_token').click(function() {
28 
29                             var url = $('#url').val();
30                             if (url == '') {
31                                 alert('Please enter url');
32                                 $('#url').focus();
33                                 return;
34                             }
35 
36                             var client_id = $('#client_id').val();
37                             if (client_id == '') {
38                                 alert('Please enter client id');
39                                 $('#client_id').focus();
40                                 return;
41                             }
42 
43                             var client_secret = $('#client_secret').val();
44                             if (client_secret == '') {
45                                 alert('Please enter client secret');
46                                 $('#client_secret').focus();
47                                 return;
48                             }
49 
50                             var username = $('#username').val();
51                             if (username == '') {
52                                 alert('Please enter username');
53                                 $('#username').focus();
54                                 return;
55                             }
56 
57                             var password = $('#password').val();
58                             if (password == '') {
59                                 alert('Please enter password');
60                                 $('#password').focus();
61                                 return;
62                             }
63 
64                             $('#result').val('Sending post ...');
65 
66                             $.ajax({
67 
68                                 type: 'POST',
69                                 url: url,
70                                 data: {
71                                     grant_type: 'password',
72                                     client_id: client_id,
73                                     client_secret: client_secret,
74                                     username: username,
75                                     password: password,
76                                 },
77                                 success: function(response) {
78 
79                                     console.log(response);
80                                     $('#result').val(JSON.stringify(response));
81 
82                                 },
83                                 error: function() {
84 
85                                     $('#result').val('Error: AJAX issue');
86 
87                                 }
88                             });
89 
90                         });
91 
92                     });
93 
94                 </script>
95             </body>
96             </html>

        (2) 修改 src/main/java/com/example/controller/OauthController.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.RequestParam;
 6             import org.springframework.ui.Model;
 7 
 8             @Controller
 9             @RequestMapping("/oauth")
10             public class OauthController {
11 
12                 ...
13 
14                 @RequestMapping("/test/password")
15                 public String testPassword() {
16                     return "password";
17                 }
18 
19             }

            运行并访问 http://localhost:9090/oauth/test/password ,点击 Get Token 按钮,会在按钮下方显示如下JSON 格式数据:                            {“access_token”:”00f15603-786e-46d1-a091-8169b3bb2e94″,”token_type”:”bearer”,”expires_in”:43200,”scope”:”All”}    4) 客户端模式(Client Credentials)实例        (1) 创建 src/main/resources/templates/client.html 文件

 1             <html>
 2             <head>
 3                 <meta charset="UTF-8">
 4                 <title th:text="${var}">Title</title>
 5                 <script language="javascript" th:src="@{/lib/jquery/jquery-3.6.0.min.js}"></script>
 6             </head>
 7             <body>
 8 
 9                 <h4>OAuth 2.0 - Client</h4>
10 
11                 <p>Token URL: <br><input type="text" name="url" id="url" style="width: 50%;" th:value="@{/oauth/token}" value="" /></p>
12                 <p>Client Id: <br><input type="text" name="client_id" id="client_id" style="width: 50%;" value="3" /></p>
13                 <p>Client Secret: <br><input type="text" name="client_secret" id="client_secret" style="width: 50%;" value="2lo2ijxJ3e" /></p>
14 
15                 <p> </p>
16                 <p>
17                     <button type="button" id="btn_get_token" >Get Token</button>
18                     <br><br><textarea name="result" id="result" style="width: 50%;" rows="8"></textarea>
19                 </p>
20 
21                 <script type="text/javascript">
22 
23                     $(document).ready(function() {
24 
25                     $('#btn_get_token').click(function() {
26 
27                             var url = $('#url').val();
28                             if (url == '') {
29                                 alert('Please enter url');
30                                 $('#url').focus();
31                                 return;
32                             }
33 
34                             var client_id = $('#client_id').val();
35                             if (client_id == '') {
36                                 alert('Please enter client id');
37                                 $('#client_id').focus();
38                                 return;
39                             }
40 
41                             var client_secret = $('#client_secret').val();
42                             if (client_secret == '') {
43                                 alert('Please enter client secret');
44                                 $('#client_secret').focus();
45                                 return;
46                             }
47 
48                             $('#result').val('Sending post ...');
49 
50                             $.ajax({
51 
52                                 type: 'POST',
53                                 url: url,
54                                 data: {
55                                     grant_type: 'client_credentials',
56                                     client_id: client_id,
57                                     client_secret: client_secret,
58                                 },
59                                 success: function(response) {
60 
61                                     console.log(response);
62                                     $('#result').val(JSON.stringify(response));
63 
64                                 },
65                                 error: function() {
66 
67                                     $('#result').val('Error: AJAX issue');
68 
69                                 }
70                             });
71 
72                         });
73 
74                     });
75 
76                 </script>
77             </body>
78             </html>

        (2) 修改 src/main/java/com/example/controller/OauthController.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.RequestParam;
 6             import org.springframework.ui.Model;
 7 
 8             @Controller
 9             @RequestMapping("/oauth")
10             public class OauthController {
11 
12                 ...
13 
14                 @RequestMapping("/test/client")
15                 public String testClient() {
16                     return "client";
17                 }
18 
19             }

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

————————

Spring security is a powerful and highly customizable authentication and access control framework. Spring boot provides spring security with an automatic configuration scheme (spring boot starter security). Spring security can be used with zero configuration. OAuth 2.0 is a continuation of OAuth protocol, but it is not forward compatible with OAuth 1.0 (that is, OAuth 1.0 is completely abolished). OAuth (open authorization) is an open standard that allows a user to allow a third-party application to access the user’s private resources (such as photos, videos, contact lists) stored on a website without providing a user name and password to the third-party application. For example, a website allows users to log in to the website after using wechat authorization instead of registering as users of the website. Wechat is in the position of oauth2 server (authorization server) in this process. This website is in OAuth client. This website (OAuth client) and wechat (OAuth server) belong to a trust relationship. Users with wechat authorization token are also trusted by this website.

1. OAuth 简介

1) basic concept of OAuth

        (1) Third-party Application:第三方应用程序,本文中又称 “客户端”(client);        (2) HTTP Service:HTTP 服务提供商,本文中简称 “服务提供商”;        (3) Resource Owner:资源所有者,本文中又称 “用户”(user);        (4) User Agent:用户代理,本文中就是指浏览器;        (5) Authorization Server:认证服务器,即服务提供商专门用来处理认证的服务器。        (6) Resource Server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。      OAuth 在 “客户端”与 “服务提供商” 之间,设置了一个授权层(Authorization Layer)。”客户端” 不能直接登录 “服务提供商”,只能登录授权层,以此将用户与客户端区分开来。”客户端” 登录授权层所用的令牌(Token),与用户的密码不同。用户可以在登录的时候,指定授权层令牌的权限范围和有效期。      ”客户端” 登录授权层以后,”服务提供商” 根据令牌的权限范围和有效期,向 “客户端” 开放用户储存的资料。

2) authorization mode

        授权码模式(Authorization Code)、简化模式(Implicit)、密码模式(Resource Owner Password Credentials)、客户端模式(Client Credentials)        (1) 授权码模式(Authorization Code)             a) 用户访问客户端,后者将前者导向认证服务器;            b) 用户选择是否给予客户端授权;            c) 假设用户给予授权,认证服务器将用户导向客户端事先指定的”重定向URI”(Redirection URI),同时附上一个授权码;            d) 客户端收到授权码,附上早先的”重定向URI”,向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见;            e) 认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(Access Token)和更新令牌(Refresh Token);            注:授权码模式是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与 “服务提供商” 的认证服务器进行互动。      (2) 简化模式(Implicit)            a) 用户决定是否给于客户端授权;            b) 假设用户给予授权,认证服务器将用户导向客户端指定的 “重定向 URI”,并在 URI 的 Hash 部分包含了访问令牌;            c) 浏览器向资源服务器发出请求,其中不包括上一步收到的 Hash 值;            d) 资源服务器返回一个网页,其中包含的代码可以获取Hash值中的令牌;            e) 浏览器执行上一步获得的脚本,提取出令牌;            f) 浏览器将令牌发给客户端;            注: 简化模式 (或称隐式模式),不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了 “授权码” 这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。       (3) 密码模式(Resource Owner Password Credentials)            a) 用户向客户端提供用户名和密码;            b) 客户端将用户名和密码发给认证服务器,向后者请求令牌;            c) 认证服务器确认无误后,向客户端提供访问令牌;            注:密码模式,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向 “服务商提供商” 索要授权。        (4) 客户端模式(Client Credentials)            a) 客户端向认证服务器进行身份认证,并要求一个访问令牌;            b) 认证服务器确认无误后,向客户端提供访问令牌;            注:客户端模式指客户端以自己的名义,而不是以用户的名义,向 “服务提供商” 进行认证。严格地说,客户端模式并不属于 OAuth 框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求 “服务提供商” 提供服务,其实不存在授权问题。    OAuth 的客户端凭证校验是通过 ClientDetailsService 来实现的,OAuth 默认为我们提供了InMemoryClientDetailsService (内存验证) 和 JdbcClientDetailsService(JDBC验证),也可以实现自定义的 ClientDetailsService (自定义验证)。        本文将搭建一个基于内存验证的授权服务器。

2. Development environment

Windows version: 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 note: for the construction of spring development environment, please refer to “spring Basics (1) – spring introduction, spring architecture and development environment configuration”.

3. 创建 Spring Boot 基础项目

Project instance name: springbootexample06 spring boot version: 2.6.6 creating steps: (1) create Maven project instance springbootexample06; (2) spring boot web configuration; (3) import thymeleaf dependency package; (4) configure jQuery; For specific operations, please refer to the project instance springbootexample02 in “spring series (2) – using thymeleaf, jQuery + bootstrap and internationalization in spring boot project”. The end of the article includes how to use the spring boot Maven plugin plug-in to run the package. Compared with springbootexample02, springbootexample06 does not configure bootstrap, template files (templates / *. HTML) and internationalization.

4. 导入 Security、 OAuth2 依赖包

Modify POM xml

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

Project list in IDE – & gt; SpringbootExample06 -> Right click – & gt; Maven -> Reload Project

5. 配置 Security & OAuth2 (基于内存验证)

    1) 创建 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 
10         @Configuration
11         public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
12 
13             @Override
14             protected void configure(AuthenticationManagerBuilder auth) throws Exception {
15                 // 内存模式
16                 auth.inMemoryAuthentication()
17                         .withUser("admin").password("{noop}123456").roles("ROLE_ADMIN")
18                         .and()
19                         .withUser("user").password("{noop}123456").roles("ROLE_USER");
20             }
21 
22             @Override
23             protected void configure(HttpSecurity http) throws Exception {
24                 // 配置认证
25                 http.authorizeRequests()
26                         .antMatchers("/error", "/lib/**", "/oauth/**").permitAll()
27                         .anyRequest().authenticated()
28                         .and()
29                         .formLogin()
30                         .and()
31                         .csrf().disable(); // 关闭 csrf 保护功能,默认是开启的
32             }
33 
34             @Bean
35             public AuthenticationManager authenticationManagerBean() throws Exception {
36                 return super.authenticationManagerBean();
37             }
38 
39         }

Note: {NOOP} means nooppasswordencoder, {NOOP} is used with a password that has not been encrypted; {bcrypt} indicates bcryptpasswordencoder, {bcrypt} is used with encrypted password; For other encryption methods, refer to their naming methods and use lowercase letters. The AuthenticationManager defined here is a spring bean, which will be assembled and used in the authorizationserverconfig. < strong > 2) create Src / main / Java / COM / example / config / oauth2 / authorizationserverconfig Java file < / strong >

 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 
12         @Configuration
13         @EnableAuthorizationServer
14         public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter  {
15             @Autowired
16             private AuthenticationManager authenticationManager;
17 
18             @Override
19             public void configure(AuthorizationServerSecurityConfigurer authServer) {
20                 // 访问权限控制
21                 authServer.tokenKeyAccess("permitAll()")
22                         .checkTokenAccess("isAuthenticated()")
23                         .allowFormAuthenticationForClients();
24             }
25 
26             @Override
27             public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
28 
29                 // 使内存模式
30                 clients.inMemory().withClient("1")
31                                     .secret("{noop}4eti4hAaux")
32                                     .authorizedGrantTypes("authorization_code", "refresh_token")
33                                     .scopes("All")
34                                     .autoApprove(true)
35                                     .redirectUris("/oauth/test/code/callback")
36                                     .and()
37                                     .withClient("2")
38                                     .secret("{noop}xGJoD2i2lj")
39                                     .authorizedGrantTypes("implicit")
40                                     .scopes("All")
41                                     .autoApprove(true)
42                                     .redirectUris("/oauth/test/implicit/callback")
43                                     .and()
44                                     .withClient("3")
45                                     .secret("{noop}2lo2ijxJ3e")
46                                     .authorizedGrantTypes("password", "client_credentials")
47                                     .scopes("All")
48                                     .autoApprove(true);
49             }
50 
51             @Override
52             public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
53 
54                 endpoints.authenticationManager(authenticationManager);
55 
56             }
57 
58         }

6. Test example (WEB Mode)

< strong > 1) example of authorization code mode < / strong >

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

  1             <html>
  2             <head>
  3                 <meta charset="UTF-8">
  4                 <title th:text="${var}">Title</title>
  5                 <script language="javascript" th:src="@{/lib/jquery/jquery-3.6.0.min.js}"></script>
  6             </head>
  7             <body>
  8 
  9                 <h4>OAuth 2.0 - Authorization Code</h4>
 10 
 11                 <p>Token URL: <br><input type="text" name="url" id="url" style="width: 50%;" th:value="@{/oauth/token}" value="" /></p>
 12                 <p>Client Id: <br><input type="text" name="client_id" id="client_id" style="width: 50%;" value="1" /></p>
 13                 <p>Client Secret: <br><input type="text" name="client_secret" id="client_secret" style="width: 50%;" value="4eti4hAaux" /></p>
 14                 <p>Code: <br><textarea name="code" id="code" rows="5" style="width: 50%;" readonly th:text="${code}"></textarea></p>
 15 
 16                 <p> </p>
 17                 <p>
 18                     <button type="button" id="btn_get_token" th:unless="${code}==null">Get Token</button>
 19                     <button type="button" id="btn_refresh_code" th:if="${code}==null">Refresh Code</button>
 20                     <br><br><textarea name="result" id="result" style="width: 50%;" rows="8"></textarea>
 21                 </p>
 22 
 23                 <p> </p>
 24                 <script type="text/javascript">
 25 
 26                     var tokenObj = null;
 27 
 28                     $(document).ready(function(){
 29 
 30                         $('#btn_get_token').click(function() {
 31 
 32                             var url = $('#url').val();
 33                             if (url == '') {
 34                                 alert('Please enter url');
 35                                 $('#url').focus();
 36                                 return;
 37                             }
 38 
 39                             var client_id = $('#client_id').val();
 40                             if (client_id == '') {
 41                                 alert('Please enter client id');
 42                                 $('#client_id').focus();
 43                                 return;
 44                             }
 45 
 46                             var client_secret = $('#client_secret').val();
 47                             if (client_secret == '') {
 48                                 alert('Please enter client secret');
 49                                 $('#client_secret').focus();
 50                                 return;
 51                             }
 52 
 53                             var code = $('#code').val();
 54                             if (code == '') {
 55                                 alert('Please enter code');
 56                                 $('#code').focus();
 57                                 return;
 58                             }
 59 
 60                             $('#result').val('Sending post ...');
 61 
 62                             $.ajax({
 63 
 64                                 type: 'POST',
 65                                 url: url,
 66                                 data: {
 67                                     grant_type: 'authorization_code',
 68                                     client_id: client_id,
 69                                     client_secret: client_secret,
 70                                     redirect_uri: '/oauth/test/code/callback',
 71                                     code: code,
 72                                 },
 73                                 success: function(response) {
 74 
 75                                     console.log(response);
 76                                     $('#result').val(JSON.stringify(response));
 77 
 78                                     tokenObj = response;
 79 
 80                                 },
 81                                 error: function() {
 82 
 83                                     $('#result').val('Error: AJAX issue');
 84 
 85                                     tokenObj = null;
 86 
 87                                 }
 88                             });
 89 
 90                         });
 91 
 92                         $('#btn_refresh_code').click(function() {
 93                             window.location.href = "/oauth/test/code";
 94                         });
 95 
 96                     });
 97 
 98                 </script>
 99 
100             </body>
101             </html>

        (2) 创建 src/main/java/com/example/controller/OauthController.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.RequestParam;
 6             import org.springframework.ui.Model;
 7 
 8             @Controller
 9             @RequestMapping("/oauth")
10             public class OauthController {
11 
12                 @RequestMapping("/test/code")
13                 public String testCode() {
14                     return "redirect:/oauth/authorize?client_id=1&redirect_uri=/oauth/test/code/callback&response_type=code";
15                 }
16 
17                 @RequestMapping("/test/code/callback")
18                 public String testCodeCallback(@RequestParam String code, Model model) {
19                     model.addAttribute("code", code);
20                     return "authorization_code";
21                 }
22             }

Run and access http://localhost:9090/oauth/test/code , auto jump to http://localhost:9090/login (the default page of spring security). Enter the user name / password (admin / 123456) configured in websecurityconfig above, log in and jump to http://localhost:9090/test/code/callback , click the get token button, The following data will be displayed under the button: {“access_token”: “0cea651f-4d01-43c0-9421-bc70d4eca081”, “token_type”: “bear”, “refresh_token”: “a3418c82-5f80-4ee8-a1dd-9bdbfce52bdc”, “expires_in” : 43199, “scope”: “all”} < strong > 2) simplify mode (implicit) instance < / strong > (1) create Src / main / resources / templates / implicit HTML file

 1             <html>
 2             <head>
 3                 <meta charset="UTF-8">
 4                 <title th:text="${var}">Title</title>
 5                 <script language="javascript" th:src="@{/lib/jquery/jquery-3.6.0.min.js}"></script>
 6             </head>
 7             <body>
 8 
 9                 <h4>OAuth 2.0 - Implicit</h4>
10 
11                 <p id="message"></p>
12 
13                 <script type="text/javascript">
14 
15                     function QueryStringToJSON(str) {
16                         var pairs = str.split('&');
17                         var result = {};
18                         pairs.forEach(function (pair) {
19                             pair = pair.split('=');
20                             var name = pair[0]
21                             var value = pair[1]
22                             if (name.length)
23                                 if (result[name] !== undefined) {
24                                     if (!result[name].push) {
25                                         result[name] = [result[name]];
26                                     }
27                                     result[name].push(value || '');
28                                 } else {
29                                     result[name] = value || '';
30                                 }
31                         });
32                         return (result);
33                     }
34 
35                     $(document).ready(function() {
36 
37                         var urlObj = window.location.href.split("#");
38                         if (urlObj.length > 1) {
39 
40                             var str = '';
41                             var obj = QueryStringToJSON(urlObj[1]);
42                             for (k in obj) {
43                                 str += k + ': ' + obj[k] + '<br>';
44                             }
45 
46                             $('#message').html(str);
47 
48                         }
49 
50                     });
51 
52                 </script>
53             </body>
54             </html>

        (2) 修改 src/main/java/com/example/controller/OauthController.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.RequestParam;
 6             import org.springframework.ui.Model;
 7 
 8             @Controller
 9             @RequestMapping("/oauth")
10             public class OauthController {
11 
12                 ...
13 
14                 @RequestMapping("/test/implicit")
15                 public String testImplicit() {
16                     return "redirect:/oauth/authorize?client_id=2&client_secret=xGJoD2i2lj&grant_type=implicit&response_type=token";
17                 }
18 
19                 @RequestMapping("/test/implicit/callback")
20                 public String testImplicitCallback(HttpServletRequest request) {
21                     return "implicit";
22                 }
23 
24             }

Run and access http://localhost:9090/oauth/test/implicit , auto jump to http://localhost:9090/login (the default page of spring security). Enter the user name and password configured in websecurityconfig above to log in, and then jump to http://localhost:9090/test/implicit/callback#access_token=52856cfb -29ab-46dc-87af-06a7d025bb77& token_ type=bearer& expires_ in=40739& Scope = all, the data after parsing the “#” symbol is displayed as follows: OAuth 2.0 – implicit access_ token: 52856cfb-29ab-46dc-87af-06a7d025bb77                token_ type: bearer                expires_ in: 40739                scope: All

    3) 密码模式(Resource Owner Password Credentials)实例        (1) 创建 src/main/resources/templates/password.html 文件

 1             <html>
 2             <head>
 3                 <meta charset="UTF-8">
 4                 <title th:text="${var}">Title</title>
 5                 <script language="javascript" th:src="@{/lib/jquery/jquery-3.6.0.min.js}"></script>
 6             </head>
 7             <body>
 8 
 9                 <h4>OAuth 2.0 - Password</h4>
10 
11                 <p>Token URL: <br><input type="text" name="url" id="url" style="width: 50%;" th:value="@{/oauth/token}" value="" /></p>
12                 <p>Client Id: <br><input type="text" name="client_id" id="client_id" style="width: 50%;" value="3" /></p>
13                 <p>Client Secret: <br><input type="text" name="client_secret" id="client_secret" style="width: 50%;" value="2lo2ijxJ3e" /></p>
14                 <p>Username: <br><input type="text" name="username" id="username" style="width: 50%;" value="admin" /></p>
15                 <p>Password: <br><input type="text" name="password" id="password" style="width: 50%;" value="123456" /></p>
16 
17                 <p> </p>
18                 <p>
19                     <button type="button" id="btn_get_token" >Get Token</button>
20                     <br><br><textarea name="result" id="result" style="width: 50%;" rows="8"></textarea>
21                 </p>
22 
23                 <script type="text/javascript">
24 
25                     $(document).ready(function() {
26 
27                     $('#btn_get_token').click(function() {
28 
29                             var url = $('#url').val();
30                             if (url == '') {
31                                 alert('Please enter url');
32                                 $('#url').focus();
33                                 return;
34                             }
35 
36                             var client_id = $('#client_id').val();
37                             if (client_id == '') {
38                                 alert('Please enter client id');
39                                 $('#client_id').focus();
40                                 return;
41                             }
42 
43                             var client_secret = $('#client_secret').val();
44                             if (client_secret == '') {
45                                 alert('Please enter client secret');
46                                 $('#client_secret').focus();
47                                 return;
48                             }
49 
50                             var username = $('#username').val();
51                             if (username == '') {
52                                 alert('Please enter username');
53                                 $('#username').focus();
54                                 return;
55                             }
56 
57                             var password = $('#password').val();
58                             if (password == '') {
59                                 alert('Please enter password');
60                                 $('#password').focus();
61                                 return;
62                             }
63 
64                             $('#result').val('Sending post ...');
65 
66                             $.ajax({
67 
68                                 type: 'POST',
69                                 url: url,
70                                 data: {
71                                     grant_type: 'password',
72                                     client_id: client_id,
73                                     client_secret: client_secret,
74                                     username: username,
75                                     password: password,
76                                 },
77                                 success: function(response) {
78 
79                                     console.log(response);
80                                     $('#result').val(JSON.stringify(response));
81 
82                                 },
83                                 error: function() {
84 
85                                     $('#result').val('Error: AJAX issue');
86 
87                                 }
88                             });
89 
90                         });
91 
92                     });
93 
94                 </script>
95             </body>
96             </html>

        (2) 修改 src/main/java/com/example/controller/OauthController.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.RequestParam;
 6             import org.springframework.ui.Model;
 7 
 8             @Controller
 9             @RequestMapping("/oauth")
10             public class OauthController {
11 
12                 ...
13 
14                 @RequestMapping("/test/password")
15                 public String testPassword() {
16                     return "password";
17                 }
18 
19             }

Run and access http://localhost:9090/oauth/test/password , click the get token button, and the following JSON format data will be displayed below the button: {“access_token”: “00f15603-786e-46d1-a091-8169b3bb2e94”, “token_type”: “bear”, “expires_in”: 43200, “scope”: “all”} < strong > 4) client credentials instance < / strong > (1) create Src / main / resources / templates / client HTML file

 1             <html>
 2             <head>
 3                 <meta charset="UTF-8">
 4                 <title th:text="${var}">Title</title>
 5                 <script language="javascript" th:src="@{/lib/jquery/jquery-3.6.0.min.js}"></script>
 6             </head>
 7             <body>
 8 
 9                 <h4>OAuth 2.0 - Client</h4>
10 
11                 <p>Token URL: <br><input type="text" name="url" id="url" style="width: 50%;" th:value="@{/oauth/token}" value="" /></p>
12                 <p>Client Id: <br><input type="text" name="client_id" id="client_id" style="width: 50%;" value="3" /></p>
13                 <p>Client Secret: <br><input type="text" name="client_secret" id="client_secret" style="width: 50%;" value="2lo2ijxJ3e" /></p>
14 
15                 <p> </p>
16                 <p>
17                     <button type="button" id="btn_get_token" >Get Token</button>
18                     <br><br><textarea name="result" id="result" style="width: 50%;" rows="8"></textarea>
19                 </p>
20 
21                 <script type="text/javascript">
22 
23                     $(document).ready(function() {
24 
25                     $('#btn_get_token').click(function() {
26 
27                             var url = $('#url').val();
28                             if (url == '') {
29                                 alert('Please enter url');
30                                 $('#url').focus();
31                                 return;
32                             }
33 
34                             var client_id = $('#client_id').val();
35                             if (client_id == '') {
36                                 alert('Please enter client id');
37                                 $('#client_id').focus();
38                                 return;
39                             }
40 
41                             var client_secret = $('#client_secret').val();
42                             if (client_secret == '') {
43                                 alert('Please enter client secret');
44                                 $('#client_secret').focus();
45                                 return;
46                             }
47 
48                             $('#result').val('Sending post ...');
49 
50                             $.ajax({
51 
52                                 type: 'POST',
53                                 url: url,
54                                 data: {
55                                     grant_type: 'client_credentials',
56                                     client_id: client_id,
57                                     client_secret: client_secret,
58                                 },
59                                 success: function(response) {
60 
61                                     console.log(response);
62                                     $('#result').val(JSON.stringify(response));
63 
64                                 },
65                                 error: function() {
66 
67                                     $('#result').val('Error: AJAX issue');
68 
69                                 }
70                             });
71 
72                         });
73 
74                     });
75 
76                 </script>
77             </body>
78             </html>

        (2) 修改 src/main/java/com/example/controller/OauthController.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.RequestParam;
 6             import org.springframework.ui.Model;
 7 
 8             @Controller
 9             @RequestMapping("/oauth")
10             public class OauthController {
11 
12                 ...
13 
14                 @RequestMapping("/test/client")
15                 public String testClient() {
16                     return "client";
17                 }
18 
19             }

Run and access http://localhost:9090/oauth/test/client , click the get token button, and the following JSON format data will be displayed below the button: {“access_token”: “91fae539-6d24-4075-8a39-4eb15d197645”, “token_type”: “bear”, “expires_in”: 43199, “scope”: “all”}