《Spring Security實作OAuth2.0授權服務 - 基礎版》介紹了如何使用Spring Security實作OAuth2.0授權和資源保護,但是使用的都是Spring Security預設的登入頁、授權頁,client和token資訊也是儲存在記憶體中的。
本文将介紹如何在Spring Security OAuth項目中自定義登入頁面、自定義授權頁面、資料庫配置client資訊、資料庫儲存授權碼和token令牌。
一、引入依賴
需要在基礎版之上引入thymeleaf、JDBC、mybatis、mysql等依賴。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
1 <!-- thymeleaf -->
2 <dependency>
3 <groupId>org.springframework.boot</groupId>
4 <artifactId>spring-boot-starter-thymeleaf</artifactId>
5 </dependency>
6 <dependency>
7 <groupId>org.thymeleaf.extras</groupId>
8 <artifactId>thymeleaf-extras-springsecurity4</artifactId>
9 </dependency>
10
11 <!-- JDBC -->
12 <dependency>
13 <groupId>org.springframework.boot</groupId>
14 <artifactId>spring-boot-starter-jdbc</artifactId>
15 </dependency>
16 <dependency>
17 <groupId>org.apache.commons</groupId>
18 <artifactId>commons-dbcp2</artifactId>
19 </dependency>
20
21 <!-- Mybatis -->
22 <dependency>
23 <groupId>org.mybatis.spring.boot</groupId>
24 <artifactId>mybatis-spring-boot-starter</artifactId>
25 <version>1.1.1</version>
26 </dependency>
27
28 <!-- MySQL -->
29 <dependency>
30 <groupId>mysql</groupId>
31 <artifactId>mysql-connector-java</artifactId>
32 </dependency>
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
二、client和token表
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
1 -- used in tests that use HSQL
2 create table oauth_client_details (
3 client_id VARCHAR(255) PRIMARY KEY,
4 resource_ids VARCHAR(255),
5 client_secret VARCHAR(255),
6 scope VARCHAR(255),
7 authorized_grant_types VARCHAR(255),
8 web_server_redirect_uri VARCHAR(255),
9 authorities VARCHAR(255),
10 access_token_validity INTEGER,
11 refresh_token_validity INTEGER,
12 additional_information TEXT(4096),
13 autoapprove VARCHAR(255)
14 );
15
16 create table oauth_client_token (
17 token_id VARCHAR(255),
18 token BLOB,
19 authentication_id VARCHAR(255) PRIMARY KEY,
20 user_name VARCHAR(255),
21 client_id VARCHAR(255)
22 );
23
24 create table oauth_access_token (
25 token_id VARCHAR(255),
26 token BLOB,
27 authentication_id VARCHAR(255) PRIMARY KEY,
28 user_name VARCHAR(255),
29 client_id VARCHAR(255),
30 authentication BLOB,
31 refresh_token VARCHAR(255)
32 );
33
34 create table oauth_refresh_token (
35 token_id VARCHAR(255),
36 token BLOB,
37 authentication BLOB
38 );
39
40 create table oauth_code (
41 code VARCHAR(255), authentication BLOB
42 );
43
44 create table oauth_approvals (
45 userId VARCHAR(255),
46 clientId VARCHAR(255),
47 scope VARCHAR(255),
48 status VARCHAR(10),
49 expiresAt TIMESTAMP,
50 lastModifiedAt TIMESTAMP
51 );
52
53
54 -- customized oauth_client_details table
55 create table ClientDetails (
56 appId VARCHAR(255) PRIMARY KEY,
57 resourceIds VARCHAR(255),
58 appSecret VARCHAR(255),
59 scope VARCHAR(255),
60 grantTypes VARCHAR(255),
61 redirectUrl VARCHAR(255),
62 authorities VARCHAR(255),
63 access_token_validity INTEGER,
64 refresh_token_validity INTEGER,
65 additionalInformation VARCHAR(4096),
66 autoApproveScopes VARCHAR(255)
67 );
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
在oauth_client_details表添加資料:
1 INSERT INTO `oauth_client_details` VALUES ('net5ijy', NULL, '123456', 'all,read,write', 'authorization_code,refresh_token,password', NULL, 'ROLE_TRUSTED_CLIENT', 7200, 7200, NULL, NULL);
2 INSERT INTO `oauth_client_details` VALUES ('tencent', NULL, '123456', 'all,read,write', 'authorization_code,refresh_code', NULL, 'ROLE_TRUSTED_CLIENT', 3600, 3600, NULL, NULL);
三、使用者、角色表
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
1 CREATE TABLE `springcloud_user` (
2 `id` int(11) NOT NULL AUTO_INCREMENT ,
3 `username` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
4 `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
5 `phone` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
6 `email` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
7 `create_time` datetime NOT NULL ,
8 PRIMARY KEY (`id`)
9 )
10 ENGINE=InnoDB
11 DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
12 AUTO_INCREMENT=1;
13
14 CREATE TABLE `springcloud_role` (
15 `id` int(11) NOT NULL AUTO_INCREMENT ,
16 `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
17 PRIMARY KEY (`id`)
18 )
19 ENGINE=InnoDB
20 DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
21 AUTO_INCREMENT=1;
22
23 CREATE TABLE `springcloud_user_role` (
24 `user_id` int(11) NOT NULL ,
25 `role_id` int(11) NOT NULL ,
26 FOREIGN KEY (`role_id`) REFERENCES `springcloud_role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
27 FOREIGN KEY (`user_id`) REFERENCES `springcloud_user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
28 INDEX `user_id_fk` USING BTREE (`user_id`) ,
29 INDEX `role_id_fk` USING BTREE (`role_id`)
30 )
31 ENGINE=InnoDB
32 DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci;
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
使用者表添加資料
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
1 INSERT INTO `springcloud_user` VALUES (1, 'admin001', '$2a$10$sXHKvdufrEfE2900ME40nOSBmeHRRUOF71szu22uaqqL8FIJeJDYW', '13622114309', '[email protected]', '2019-4-7 09:31:07');
2 INSERT INTO `springcloud_user` VALUES (2, 'admin002', '$2a$10$sXHKvdufrEfE2900ME40nOSBmeHRRUOF71szu22uaqqL8FIJeJDYW', '17809837654', '[email protected]', '2019-4-7 09:33:00');
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
角色表添加資料
1 INSERT INTO `springcloud_role` VALUES (1, 'ADMIN');
2 INSERT INTO `springcloud_role` VALUES (2, 'DBA');
3 INSERT INTO `springcloud_role` VALUES (3, 'USER');
使用者角色關系表添加資料
1 INSERT INTO `springcloud_user_role` VALUES (1, 1);
2 INSERT INTO `springcloud_user_role` VALUES (2, 1);
四、實體類和工具類
1、User實體類
封裝授權伺服器登入使用者資訊
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
1 public class User implements Serializable {
2
3 private Integer id;
4 private String username;
5 private String password;
6 private String phone;
7 private String email;
8 private Set<Role> roles = new HashSet<Role>();
9 private Date createTime;
10
11 // getter & setter
12
13 @Override
14 public int hashCode() {
15 final int prime = 31;
16 int result = 1;
17 result = prime * result + ((id == null) ? 0 : id.hashCode());
18 return result;
19 }
20 @Override
21 public boolean equals(Object obj) {
22 if (this == obj)
23 return true;
24 if (obj == null)
25 return false;
26 if (getClass() != obj.getClass())
27 return false;
28 User other = (User) obj;
29 if (id == null) {
30 if (other.id != null)
31 return false;
32 } else if (!id.equals(other.id))
33 return false;
34 return true;
35 }
36 @Override
37 public String toString() {
38 return "User [id=" + id + ", username=" + username + ", password="
39 + password + ", phone=" + phone + ", email=" + email
40 + ", roles=" + roles + ", createTime=" + createTime + "]";
41 }
42 }
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
2、Role實體類
封裝角色資訊
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
1 public class Role implements Serializable {
2
3 private Integer id;
4 private String name;
5
6 public Role() {
7 super();
8 }
9 public Role(String name) {
10 super();
11 this.name = name;
12 }
13
14 // getter & setter
15
16 @Override
17 public String toString() {
18 return "Role [id=" + id + ", name=" + name + "]";
19 }
20 }
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
3、ResponseMessage工具類
封裝接口響應資訊
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
1 public class ResponseMessage {
2
3 private Integer code;
4 private String message;
5
6 public ResponseMessage() {
7 super();
8 }
9
10 public ResponseMessage(Integer code, String message) {
11 super();
12 this.code = code;
13 this.message = message;
14 }
15
16 // getter & setter
17
18 public static ResponseMessage success() {
19 return new ResponseMessage(0, "操作成功");
20 }
21
22 public static ResponseMessage fail() {
23 return new ResponseMessage(99, "操作失敗");
24 }
25 }
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
五、DAO和Service編寫
1、資料源配置
在application.properties檔案配置datasource
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
1 spring.datasource.url=jdbc:mysql://localhost:3306/test
2 spring.datasource.username=system
3 spring.datasource.password=123456
4 spring.datasource.driver-class-name=com.mysql.jdbc.Driver
5
6 spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource
7 spring.datasource.dbcp2.initial-size=5
8 spring.datasource.dbcp2.max-active=25
9 spring.datasource.dbcp2.max-idle=10
10 spring.datasource.dbcp2.min-idle=5
11 spring.datasource.dbcp2.max-wait-millis=10000
12 spring.datasource.dbcp2.validation-query=SELECT 1
13 spring.datasource.dbcp2.connection-properties=characterEncoding=utf8
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
使用dbcp2資料源
2、mapper.xml
在src/main/resources下建立org.net5ijy.oauth2.mapper包,建立user-mapper.xml配置檔案
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
1 <mapper namespace="org.net5ijy.oauth2.repository.UserRepository">
2
3 <resultMap type="User" id="UserResultMap">
4 <result column="id" property="id" jdbcType="INTEGER" javaType="int" />
5 <result column="username" property="username" jdbcType="VARCHAR"
6 javaType="string" />
7 <result column="password" property="password" jdbcType="VARCHAR"
8 javaType="string" />
9 <result column="phone" property="phone" jdbcType="VARCHAR"
10 javaType="string" />
11 <result column="email" property="email" jdbcType="VARCHAR"
12 javaType="string" />
13 <result column="create_time" property="createTime" jdbcType="TIMESTAMP"
14 javaType="java.util.Date" />
15 <collection property="roles" select="selectRolesByUserId"
16 column="id"></collection>
17 </resultMap>
18
19 <!-- 根據使用者名查詢使用者 -->
20 <select id="findByUsername" parameterType="java.lang.String"
21 resultMap="UserResultMap">
22 <![CDATA[
23 select * from springcloud_user where username = #{username}
24 ]]>
25 </select>
26
27 <!-- 根據user id查詢使用者擁有的role -->
28 <select id="selectRolesByUserId" parameterType="java.lang.Integer"
29 resultType="Role">
30 <![CDATA[
31 select r.id, r.name from springcloud_user_role ur, springcloud_role r
32 where ur.role_id = r.id and ur.user_id = #{id}
33 ]]>
34 </select>
35
36 </mapper>
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
因為我們的例子隻使用了findByUsername功能,是以隻寫這個sql就可以了
3、DAO接口
1 public interface UserRepository {
2
3 User findByUsername(String username);
4 }
4、UserService
接口
1 public interface UserService {
2
3 User getUser(String username);
4 }
實作類
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
1 @Service
2 public class UserServiceImpl implements UserService {
3
4 static Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
5
6 @Autowired
7 private UserRepository userRepository;
8
9 @Autowired
10 private JdbcTemplate jdbcTemplate;
11
12 @Override
13 public User getUser(String username) {
14 return userRepository.findByUsername(username);
15 }
16 }
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
5、UserDetailsService實作類
這個接口的實作類需要在Security中配置,Security會使用這個類根據使用者名查詢使用者資訊,然後進行使用者名、密碼的驗證。主要就是實作loadUserByUsername方法:
View Code
六、自定義登入頁面
1、controller編寫
編寫LoginController類,添加login方法
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
1 @RestController
2 public class LoginController {
3
4 @GetMapping("/login")
5 public ModelAndView login() {
6 return new ModelAndView("login");
7 }
8
9 @GetMapping("/login-error")
10 public ModelAndView loginError(HttpServletRequest request, Model model) {
11 model.addAttribute("loginError", true);
12 model.addAttribute("errorMsg", "登陸失敗,賬号或者密碼錯誤!");
13 return new ModelAndView("login", "userModel", model);
14 }
15 }
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
2、頁面代碼
頁面代碼使用到了thymeleaf、bootstrap、表單驗證等,具體的js、css引入就不贅述了,隻記錄最主要的内容:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
1 <div>
2 <form th:action="@{/login}" method="post">
3 <div>
4 <label>用 戶 名: </label>
5 <div>
6 <input name="username" />
7 </div>
8 </div>
9 <div>
10 <label>密  碼: </label>
11 <div>
12 <input type="password" name="password" />
13 </div>
14 </div>
15 <div>
16 <div>
17 <button type="submit"> 登 陸 </button>
18 </div>
19 </div>
20 </form>
21 </div>
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
七、自定義授權頁面
1、controller編寫
編寫GrantController類,添加getAccessConfirmation方法
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
1 @Controller
2 @SessionAttributes("authorizationRequest")
3 public class GrantController {
4
5 @RequestMapping("/oauth/confirm_access")
6 public ModelAndView getAccessConfirmation(Map<String, Object> model,
7 HttpServletRequest request) throws Exception {
8
9 AuthorizationRequest authorizationRequest = (AuthorizationRequest) model
10 .get("authorizationRequest");
11
12 ModelAndView view = new ModelAndView("base-grant");
13 view.addObject("clientId", authorizationRequest.getClientId());
14
15 return view;
16 }
17 }
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
此處擷取到申請授權的clientid用于在頁面展示
2、頁面代碼
此處隻寫最主要的部分
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
1 <div>
2 <div>
3 <div>OAUTH-BOOT 授權</div>
4 <div>
5 <a href="javascript:;" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >幫助</a>
6 </div>
7 </div>
8 <h3 th:text="${clientId}+' 請求授權,該應用将擷取您的以下資訊'"></h3>
9 <p>昵稱,頭像和性别</p>
10 授權後表明您已同意 <a href="javascript:;" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" style="color: #E9686B">OAUTH-BOOT 服務協定</a>
11 <form method="post" action="/oauth/authorize">
12 <input type="hidden" name="user_oauth_approval" value="true" />
13 <input type="hidden" name="scope.all" value="true" />
14 <br />
15 <button class="btn" type="submit">同意/授權</button>
16 </form>
17 </div>
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
八、配置類和application.properties配置
1、配置mybatis
配置SqlSessionFactoryBean
- 設定資料源
- 設定包别名
- 設定mapper映射檔案所在的包
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
1 @Configuration
2 public class MyBatisConfiguration {
3
4 @Bean
5 @Autowired
6 @ConditionalOnMissingBean
7 public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource)
8 throws IOException {
9
10 SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
11
12 // 設定資料源
13 sqlSessionFactoryBean.setDataSource(dataSource);
14
15 // 設定别名包
16 sqlSessionFactoryBean.setTypeAliasesPackage("org.net5ijy.oauth2.bean");
17
18 // 設定mapper映射檔案所在的包
19 PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver();
20 String packageSearchPath = "classpath*:org/net5ijy/oauth2/mapper/**.xml";
21 sqlSessionFactoryBean
22 .setMapperLocations(pathMatchingResourcePatternResolver
23 .getResources(packageSearchPath));
24
25 return sqlSessionFactoryBean;
26 }
27 }
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
2、配置AuthorizationServerConfigurer
- 配置使用資料庫儲存cient資訊
- 配置使用資料庫儲存token令牌
- 配置使用資料庫儲存授權碼
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
1 @Configuration
2 public class Oauth2AuthorizationServerConfiguration extends
3 AuthorizationServerConfigurerAdapter {
4
5 @Autowired
6 private UserDetailsService userDetailsService;
7
8 @Autowired
9 private AuthenticationManager authenticationManager;
10
11 @Autowired
12 private DataSource dataSource;
13
14 @Override
15 public void configure(ClientDetailsServiceConfigurer clients)
16 throws Exception {
17
18 // 資料庫管理client
19 clients.withClientDetails(new JdbcClientDetailsService(dataSource));
20 }
21
22 @Override
23 public void configure(AuthorizationServerEndpointsConfigurer endpoints)
24 throws Exception {
25
26 // 使用者資訊查詢服務
27 endpoints.userDetailsService(userDetailsService);
28
29 // 資料庫管理access_token和refresh_token
30 TokenStore tokenStore = new JdbcTokenStore(dataSource);
31
32 endpoints.tokenStore(tokenStore);
33
34 ClientDetailsService clientService = new JdbcClientDetailsService(
35 dataSource);
36
37 DefaultTokenServices tokenServices = new DefaultTokenServices();
38 tokenServices.setTokenStore(tokenStore);
39 tokenServices.setSupportRefreshToken(true);
40 tokenServices.setClientDetailsService(clientService);
41 // tokenServices.setAccessTokenValiditySeconds(180);
42 // tokenServices.setRefreshTokenValiditySeconds(180);
43
44 endpoints.tokenServices(tokenServices);
45
46 endpoints.authenticationManager(authenticationManager);
47
48 // 資料庫管理授權碼
49 endpoints.authorizationCodeServices(new JdbcAuthorizationCodeServices(
50 dataSource));
51 // 資料庫管理授權資訊
52 ApprovalStore approvalStore = new JdbcApprovalStore(dataSource);
53 endpoints.approvalStore(approvalStore);
54 }
55 }
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
3、配置security
- 配置使用資料庫儲存登入使用者資訊
- 配置自定義登入頁面
- 暫時禁用CSRF
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
1 @EnableWebSecurity
2 public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
3
4 @Autowired
5 private UserDetailsService userDetailsService;
6
7 @Autowired
8 private PasswordEncoder passwordEncoder;
9
10 @Bean
11 public PasswordEncoder passwordEncoder() {
12 return new BCryptPasswordEncoder(); // 使用 BCrypt 加密
13 }
14
15 public AuthenticationProvider authenticationProvider() {
16 DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
17 authenticationProvider.setUserDetailsService(userDetailsService);
18 authenticationProvider.setPasswordEncoder(passwordEncoder);
19 authenticationProvider.setHideUserNotFoundExceptions(false);
20 return authenticationProvider;
21 }
22
23 @Override
24 public void configure(WebSecurity web) throws Exception {
25 web.ignoring().antMatchers("/css/**", "/js/**", "/fonts/**",
26 "/icon/**", "/favicon.ico");
27 }
28
29 @Override
30 protected void configure(HttpSecurity http) throws Exception {
31
32 http.requestMatchers()
33 .antMatchers("/login", "/login-error", "/oauth/authorize",
34 "/oauth/token").and().authorizeRequests()
35 .antMatchers("/login").permitAll().anyRequest().authenticated();
36
37 // 登入頁面
38 http.formLogin().loginPage("/login").failureUrl("/login-error");
39
40 // 禁用CSRF
41 http.csrf().disable();
42 }
43
44 @Autowired
45 public void configureGlobal(AuthenticationManagerBuilder auth)
46 throws Exception {
47 auth.userDetailsService(userDetailsService);
48 auth.authenticationProvider(authenticationProvider());
49 }
50
51 public static void main(String[] args) {
52 System.out.println(new BCryptPasswordEncoder().encode("123456"));
53 }
54 }
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
4、application.properties檔案配置
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
1 server.port=7001
2
3 ##### Built-in DataSource #####
4 spring.datasource.url=jdbc:mysql://localhost:3306/test
5 spring.datasource.username=system
6 spring.datasource.password=123456
7 spring.datasource.driver-class-name=com.mysql.jdbc.Driver
8
9 spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource
10 spring.datasource.dbcp2.initial-size=5
11 spring.datasource.dbcp2.max-active=25
12 spring.datasource.dbcp2.max-idle=10
13 spring.datasource.dbcp2.min-idle=5
14 spring.datasource.dbcp2.max-wait-millis=10000
15 spring.datasource.dbcp2.validation-query=SELECT 1
16 spring.datasource.dbcp2.connection-properties=characterEncoding=utf8
17
18 ##### Thymeleaf #####
19 # 編碼
20 spring.thymeleaf.encoding=UTF-8
21 # 熱部署靜态檔案
22 spring.thymeleaf.cache=false
23 # 使用HTML5标準
24 spring.thymeleaf.mode=HTML5
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
九、受保護資源
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
1 @RestController
2 @RequestMapping(value = "/order")
3 public class TestController {
4
5 Logger log = LoggerFactory.getLogger(TestController.class);
6
7 @RequestMapping(value = "/demo")
8 @ResponseBody
9 public ResponseMessage getDemo() {
10 Authentication auth = SecurityContextHolder.getContext()
11 .getAuthentication();
12 log.info(auth.toString());
13 return ResponseMessage.success();
14 }
15 }
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
十、應用啟動類
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
1 @SpringBootApplication
2 @EnableAuthorizationServer
3 @EnableResourceServer
4 @MapperScan("org.net5ijy.oauth2.repository")
5 public class Oauth2Application {
6
7 public static void main(String[] args) {
8
9 // args = new String[] { "--debug" };
10
11 SpringApplication.run(Oauth2Application.class, args);
12 }
13 }
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
十一、測試授權碼模式
1、擷取authorization_code授權碼
使用浏覽器通路:
http://localhost:7001/oauth/authorize?response_type=code&client_id=net5ijy&redirect_uri=http://localhost:8080&scope=all
位址
http://localhost:7001/oauth/authorize
參數
response_type | code |
client_id | 根據實際的client-id填寫,此處寫net5ijy |
redirect_uri | 生成code後的回調位址,http://localhost:8080 |
scope | 權限範圍 |
登入,admin001和123456
允許授權
轉存失敗重新上傳取消
看到浏覽器重定向到了http://localhost:8080并攜帶了code參數,這個code就是授權伺服器生成的授權碼
2、擷取token令牌
使用curl指令擷取token令牌
curl --user net5ijy:123456 -X POST -d "grant_type=authorization_code&scope=all&redirect_uri=http%3a%2f%2flocalhost%3a8080&code=ubtvR4" http://localhost:7001/oauth/token
位址
http://localhost:7001/oauth/token
參數
grant_type | 授權碼模式,寫authorization_code |
scope | 權限範圍 |
redirect_uri | 回調位址,http://localhost:8080需要urlencode |
code | 就是上一步生成的授權碼 |
傳回值
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
1 {
2 "access_token": "c5836918-1924-4b0a-be67-043218c6e7e0",
3 "token_type": "bearer",
4 "refresh_token": "7950b7f9-7d60-41da-9a95-bd2c8b29ada1",
5 "expires_in": 7199,
6 "scope": "all"
7 }
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmWwRWbMxmUyImash0Y25kMMpnVyoFaxcVY2BjMipWN5Nmb5ckYpVjMZVHNyIGdxIjYqlTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
這樣就擷取到了token令牌,該token的通路權限範圍是all權限,在2小時後失效。
3、使用token通路資源
http://localhost:7001/order/demo?access_token=c5836918-1924-4b0a-be67-043218c6e7e0