# 基礎Web工程的建設
### 創建父項目
> 命名:cloudframe
```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.gosuncn</groupId>
<artifactId>cloudframe</artifactId>
<version>1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/>
</parent>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR1</spring-cloud.version>
<spring.cloud.alibaba.version>2.2.0.RELEASE</spring.cloud.alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
```
### 創建公共子模塊
> 命名:common
```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloudframe</artifactId>
<groupId>com.gosuncn</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common</artifactId>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
```
此時父項目自動更改
```xml
...
<groupId>com.gosuncn</groupId>
<artifactId>cloudframe</artifactId>
<packaging>pom</packaging>
<version>1.0</version>
<modules>
<module>common</module>
</modules>
...
```
##### 創建Main方法函數
```java
package com.gosuncn;
public class MainStarter {
public static void main(String[] args) {
}
}
```
##### 創建封裝響應類
```java
package com.gosuncn.bean;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CommonResult<T> implements Serializable {
/**
* 狀態碼
*/
private Integer code;
/**
* 狀態說明
*/
private String msg;
/**
* 數據內容
*/
private T data;
}
```
其他模塊引入公用模塊可以共用此類來封裝響應。
### 創建認證授權模塊
> 命名:authorize
```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloudframe</artifactId>
<groupId>com.gosuncn</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>authorize</artifactId>
<dependencies>
<dependency>
<groupId>com.gosuncn</groupId>
<artifactId>common</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
```
##### 創建測試控制器
```java
package com.gosuncn.controller;
@RestController
public class UserController {
@GetMapping("/users")
public CommonResult getUsers() {
return new CommonResult(HttpStatus.OK.value(), HttpStatus.OK.getReasonPhrase(), "Hello, I'm Apollo.");
}
}
```
##### 創建配置文件
> application.yml
```yaml
server:
port: 80
```
##### 測試
```http
http://127.0.0.1/users
```
```json
{"code":200,"msg":"OK","data":"Hello, I'm Apollo."}
```
# 創建數據庫表結構
### 創建表結構
創建`sys_rbac`數據庫。在庫中,創建RBAC經典表結構:用戶表、角色表、權限表以及兩張中間表,最后創建一張表`oauth_client_details`,它用來存儲第三方客戶端信息,主要用于OAuth2協議。
```mysql
CREATE DATABASE `sys_rbac` CHARACTER SET 'utf8' COLLATE 'utf8_bin';
USE `sys_rbac`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用戶ID',
`username` char(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '用戶名',
`password` char(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT '密碼',
`enabled` tinyint(1) NULL DEFAULT 1 COMMENT '啟用狀態[1:啟用][0:未啟用]',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `index_user_username`(`username`) USING BTREE COMMENT '用戶名唯一索引'
);
CREATE TABLE `role` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`name` char(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '角色名稱',
`desc` char(64) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '角色描述',
PRIMARY KEY (`id`) USING BTREE
);
CREATE TABLE `access` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '權限ID',
`name` char(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '權限名稱',
`desc` char(64) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '權限描述',
PRIMARY KEY (`id`) USING BTREE
);
CREATE TABLE `user_role` (
`user_id` int(11) NOT NULL COMMENT '用戶ID',
`role_id` int(11) NOT NULL COMMENT '角色ID',
INDEX `fk_user_role_role_id`(`role_id`) USING BTREE,
INDEX `fk_user_role_user_id`(`user_id`) USING BTREE,
CONSTRAINT `fk_user_role_role_id` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_user_role_user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE `role_access` (
`role_id` int(11) NOT NULL COMMENT '角色ID',
`access_id` int(11) NOT NULL COMMENT '權限ID',
INDEX `fk_role_access_role_id`(`role_id`) USING BTREE,
INDEX `fk_role_access_access_id`(`access_id`) USING BTREE,
CONSTRAINT `fk_role_access_access_id` FOREIGN KEY (`access_id`) REFERENCES `access` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_role_access_role_id` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE `oauth_client_details` (
`client_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
`resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
`client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
`scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
`authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
`web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
`authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
`access_token_validity` int(11) NULL DEFAULT NULL,
`refresh_token_validity` int(11) NULL DEFAULT NULL,
`additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
`autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
PRIMARY KEY (`client_id`) USING BTREE
);
```
`authorized_grant_types`授權類型可選值有:`authorization_code`,`implicit`,`password`,`client_credentials`,`refresh_token` 多個值用`,`分割。
### 插入模擬數據
```mysql
INSERT INTO `sys_rbac`.`user` ( `id`, `username`, `password`, `enabled` ) VALUES ( 1, 'user', '$2a$10$1hZj6YmqJ81Dkpuitsl3S.CHVzJXJN2b5srKMgGexLzFQU5jlZbgu', 1 );
INSERT INTO `sys_rbac`.`role`(`id`, `name`, `desc`) VALUES (1, 'ADMIN', '管理員');
INSERT INTO `sys_rbac`.`access`(`id`, `name`, `desc`) VALUES (1, 'all', '測試數據');
INSERT INTO `sys_rbac`.`user_role`(`user_id`, `role_id`) VALUES (1, 1);
INSERT INTO `sys_rbac`.`role_access`(`role_id`, `access_id`) VALUES (1, 1);
INSERT INTO `sys_rbac`.`oauth_client_details` (`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types` , `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information` , `autoapprove`) VALUES ('web', NULL, '$2a$10$fvyMIE99J59EBj1K/huJ0ethuO7LnAfKFwjuUl5Pz36zLUw61UWie', 'all', 'authorization_code,implicit,password,client_credentials,refresh_token' , 'http://www.baidu.com', NULL, NULL, NULL, NULL , 'true');
```
# 引入MyBatis框架
### 添加MyBatis相關依賴
```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloudframe</artifactId>
<groupId>com.gosuncn</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>authorize</artifactId>
<dependencies>
<dependency>
<groupId>com.gosuncn</groupId>
<artifactId>common</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>com/gosuncn/dao/*.xml</include>
</includes>
</resource>
</resources>
</build>
</project>
```
其中,`<build>`···`</build>`這段是為了maven可將mapper的xml文件可與dao接口寫在一起。
### 添加數據源和mybatis配置信息
```yaml
server:
port: 80
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql:///sys_rbac?serverTimezone=UTC
username: root
password: root
mybatis:
mapper-locations: classpath*:/com/gosuncn/dao/*.xml
```
### 創建實體類
##### 創建user表對應實體類User類
```java
package com.gosuncn.entity;
@Data
public class User {
private Integer id;
private String username;
private String password;
private Boolean enabled;
}
```
##### 創建role表對應實體類Role類
```java
package com.gosuncn.entity;
@Data
public class Role {
private Integer id;
private String name;
private String desc;
}
```
##### 創建access表對應實體類Access類
```java
package com.gosuncn.entity;
@Data
public class Access {
private Integer id;
private String name;
private String desc;
}
```
### 創建UserMapper接口
```java
package com.gosuncn.dao;
@Mapper
public interface UserMapper {
User getUserByUsername(@Param("username") String username);
}
```
### 創建UserMapper.xml文件
```xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.gosuncn.dao.UserMapper">
<select id="getUserByUsername" parameterType="java.lang.String" resultType="com.gosuncn.entity.User">
SELECT `id`, `username`, `password`, `enabled` FROM `user` WHERE `username` = #{username}
</select>
</mapper>
```
### 創建UserService類
```java
package com.gosuncn.service;
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
Optional<User> getUserByUsername(String username) {
return Optional.ofNullable(userMapper.getUserByUsername(username));
}
}
```
### 修改UserController類
```java
package com.gosuncn.controller;
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/user")
public CommonResult getUserByUsername(@RequestParam String username) {
return new CommonResult(HttpStatus.OK.value(), HttpStatus.OK.getReasonPhrase(), userService.getUserByUsername(username).orElse(null));
}
}
```
### 啟動項目測試
##### 訪問存在的用戶
```http
http://127.0.0.1/user?username=user
```
```json
{
"code": 200,
"msg": "OK",
"data": {
"id": 1,
"username": "user",
"password": "$2a$10$1hZj6YmqJ81Dkpuitsl3S.CHVzJXJN2b5srKMgGexLzFQU5jlZbgu",
"enabled": true
}
}
```
##### 訪問不存在的用戶
```http
http://127.0.0.1/user?username=Apollo
```
```json
{"code":200,"msg":"OK","data":null}
```
# 生成PrivateKey
### 生成私鑰命令
因為在oauth2協議中,我們用到了非對稱加密算法,因此,我們先生成放在認證服務器端的私鑰:
```shell
keytool -genkeypair -alias cigcjks -keyalg RSA -keypass cigcjks -keystore cigc.jks -storepass cigcjks
```
通過上面命令,我們得到了一個`cigc.jks`文件。
### 具體說明
```shell
keytool -genkeypair -alias keyname -keyalg RSA -keypass keyword -keystore file.jks -storepass fileword
```
- keyname
生成的密鑰對的名稱
- keyword
生成的密鑰對的密碼
- file.jks
生成的密鑰對的文件名
- fileword
生成的密鑰對的文件名的密碼
> 其中,`keyname、keyword、file.jks、fileword`均是可以自己定義修改的,建議遵守常規命名規范。示例:
>
> keytool -genkeypair -alias key0001 -keyalg RSA -keypass wVsE6k -keystore cigc.jks -storepass TX0zAg
---
### 安置私鑰
在項目的`resources`目錄下,新建一個名為`PrivateKey`的目錄。將`cigc.jks`文件放入`PrivateKey`目錄中,以備后用。
# 生成PublicKey
### 生成公鑰命令
公鑰是放在資源服務器上的文件,目前先做出來準備后用。
```shell
keytool -list -rfc --keystore file.jks | openssl x509 -inform pem -pubkey
```
- file.jks
生成的密鑰對的文件名
> 示例:
>
> keytool -list -rfc --keystore cigc.jks | openssl x509 -inform pem -pubkey
---
### 演示例子
接下來,我們就執行命令,記住了,一定在與`jks`文件同級目錄下,否則找不到`jks`文件。
```shell
keytool -list -rfc --keystore cigc.jks | openssl x509 -inform pem -pubkey
```
輸入密碼:cigcjks(生成PrivateKey這一步時,設置的密碼。)得到:
```
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuMQg2pRayMgZD3E8z4iD
ev7FpOk5ljyiF3jUNPLcPtX2fnEYQsQTc8af85Mk+Q8jH5icqlkJOs+0KsBH899m
AjWNU6yppySq5jklp5vg3TMK+ShO4VRC+/b2aJwqbGYoPHDrgMu7xpl3eaFJiwcj
oeNIsPQYTL5UlzROGFjO0CkXaRRq/sRHhJbRL2kBey8i/+gB8GACpgq/GeRREJu0
XzLO5sgSeRG5OyzNeURvCS2D9mtx4o86WDicIjev3hVjubKyhNumILk/ZV4zPQy/
k2T+DnyoapAFg/6xK2NxQdkFEu7/kma9tde6Uj1QDVvzXf89ZWnPSPEzaHVACzZk
pwIDAQAB
-----END PUBLIC KEY-----
-----BEGIN CERTIFICATE-----
MIIDUzCCAjugAwIBAgIEEUTVCjANBgkqhkiG9w0BAQsFADBaMQ0wCwYDVQQGEwRj
aWdjMQ0wCwYDVQQIEwRjaWdjMQ0wCwYDVQQHEwRjaWdjMQ0wCwYDVQQKEwRjaWdj
MQ0wCwYDVQQLEwRjaWdjMQ0wCwYDVQQDEwRjaWdjMB4XDTIwMDQyODA2NTM1MFoX
DTIwMDcyNzA2NTM1MFowWjENMAsGA1UEBhMEY2lnYzENMAsGA1UECBMEY2lnYzEN
MAsGA1UEBxMEY2lnYzENMAsGA1UEChMEY2lnYzENMAsGA1UECxMEY2lnYzENMAsG
A1UEAxMEY2lnYzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALjEINqU
WsjIGQ9xPM+Ig3r+xaTpOZY8ohd41DTy3D7V9n5xGELEE3PGn/OTJPkPIx+YnKpZ
CTrPtCrAR/PfZgI1jVOsqackquY5Jaeb4N0zCvkoTuFUQvv29micKmxmKDxw64DL
u8aZd3mhSYsHI6HjSLD0GEy+VJc0ThhYztApF2kUav7ER4SW0S9pAXsvIv/oAfBg
AqYKvxnkURCbtF8yzubIEnkRuTsszXlEbwktg/ZrceKPOlg4nCI3r94VY7mysoTb
piC5P2VeMz0Mv5Nk/g58qGqQBYP+sStjcUHZBRLu/5JmvbXXulI9UA1b813/PWVp
z0jxM2h1QAs2ZKcCAwEAAaMhMB8wHQYDVR0OBBYEFDJG8MKxT5rgKgtfRIK9iPSx
ZejzMA0GCSqGSIb3DQEBCwUAA4IBAQAE/5ouShSwaOIrq13FZt9VDzAZdyXjZ5Ly
wImfH+bsdtIRB3wbRml3fNClw9gfivwbAuvVZb11nJM3bdrzUcKVnzd7dvXRj9w4
/aaXcyVjBwE6uBCyMn2k8enOdf1BNkAurS/yhmPo4lCrJS70LNAw+y7dw+dQVuKa
wUiJ8330cIRKMhll/kFIVE6np55tPG2jAEY63WnN/bDDHRZIUQplqfe6YGOmG5wu
5ViZdAmRRtaC/g51SzrIvmiJV5CdI5s4FOt8r2QCXKLBbLtYlhSZONc471HWpRz5
P2vb4cg8UjlaUEH0j4u3biCaqtWelHOiT5qJeOkhHveZOwiiwASG
-----END CERTIFICATE-----
```
---
我們將以下內容部分拷貝下來:
```
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuMQg2pRayMgZD3E8z4iD
ev7FpOk5ljyiF3jUNPLcPtX2fnEYQsQTc8af85Mk+Q8jH5icqlkJOs+0KsBH899m
AjWNU6yppySq5jklp5vg3TMK+ShO4VRC+/b2aJwqbGYoPHDrgMu7xpl3eaFJiwcj
oeNIsPQYTL5UlzROGFjO0CkXaRRq/sRHhJbRL2kBey8i/+gB8GACpgq/GeRREJu0
XzLO5sgSeRG5OyzNeURvCS2D9mtx4o86WDicIjev3hVjubKyhNumILk/ZV4zPQy/
k2T+DnyoapAFg/6xK2NxQdkFEu7/kma9tde6Uj1QDVvzXf89ZWnPSPEzaHVACzZk
pwIDAQAB
-----END PUBLIC KEY-----
```
### 保存公鑰
新建一個文件,名為:`public.cert`。將內容拷貝進文件中。以備后用。
# 完善角色和權限數據交互
### 創建RoleMapper接口
```java
package com.gosuncn.dao;
@Mapper
public interface RoleMapper {
List<Role> getRolesByUserId(@Param("userId") Integer userId);
}
```
### 創建RoleMapper.xml文件
```xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.gosuncn.dao.RoleMapper">
<select id="getRolesByUserId" parameterType="java.lang.Integer" resultType="com.gosuncn.entity.Role">
SELECT `role`.`id`, `role`.`name`, `role`.`desc` FROM `user_role` LEFT JOIN `role` ON `user_role`.`role_id` = `role`.`id` WHERE `user_role`.`user_id` = #{userId}
</select>
</mapper>
```
### 創建AccessMapper接口
```java
package com.gosuncn.dao;
@Mapper
public interface AccessMapper {
List<Access> getAccessesByRoleId(@Param("roleId") Integer roleId);
}
```
### 創建AccessMapper.xml文件
```xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.gosuncn.dao.AccessMapper">
<select id="getAccessesByRoleId" parameterType="java.lang.Integer" resultType="com.gosuncn.entity.Access">
SELECT `id`, `name`, `desc` FROM `role_access` LEFT JOIN `access` ON `role_access`.`access_id` = `access`.`id` WHERE `role_access`.`role_id` = #{roleId}
</select>
</mapper>
```
### 創建RoleService類
```java
package com.gosuncn.service;
@Service
public class RoleService {
@Autowired
private RoleMapper roleMapper;
public Optional<List<Role>> getRolesByUserId(Integer userId) {
return Optional.ofNullable(roleMapper.getRolesByUserId(userId));
}
}
```
### 創建AccessService類
```java
package com.gosuncn.service;
@Service
public class AccessService {
@Autowired
private AccessMapper accessMapper;
public Optional<List<Access>> getAccessesByRoleId(Integer roleId) {
return Optional.ofNullable(accessMapper.getAccessesByRoleId(roleId));
}
}
```
### 修改UserController類
```java
package com.gosuncn.controller;
@RestController
public class UserController {
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Autowired
private AccessService accessService;
@GetMapping("/user")
public CommonResult getUserByUsername(@RequestParam String username) {
return new CommonResult(HttpStatus.OK.value(), HttpStatus.OK.getReasonPhrase(), userService.getUserByUsername(username).orElse(null));
}
@GetMapping("/role/userId/{userId}")
public CommonResult getRolesByUserId(@PathVariable("userId") Integer userId) {
return new CommonResult(HttpStatus.OK.value(), HttpStatus.OK.getReasonPhrase(), roleService.getRolesByUserId(userId).orElse(null));
}
@GetMapping("/access/roleId/{roleId}")
public CommonResult getAccessesByRoleId(@PathVariable("roleId") Integer roleId) {
return new CommonResult(HttpStatus.OK.value(), HttpStatus.OK.getReasonPhrase(), accessService.getAccessesByRoleId(roleId).orElse(null));
}
}
```
### 啟動項目測試
```http
http://127.0.0.1/role/userId/1
```
```json
{"code":200,"msg":"OK","data":[{"id":1,"name":"ADMIN","desc":"管理員"}]}
```
```http
http://127.0.0.1/access/roleId/1
```
```json
{"code":200,"msg":"OK","data":[{"id":1,"name":"all","desc":"測試數據"}]}
```
# OAuth2協議防護系統
### 引入依賴
```xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
```
##### pom文件完整版
```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloudframe</artifactId>
<groupId>com.gosuncn</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>authorize</artifactId>
<dependencies>
<dependency>
<groupId>com.gosuncn</groupId>
<artifactId>common</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>com/gosuncn/dao/*.xml</include>
</includes>
</resource>
</resources>
</build>
</project>
```
### 自定義UserDetails
> 類名:AuthUserDetails
```java
package com.gosuncn.entity;
public class AuthUserDetails implements UserDetails {
private Integer id; // 用戶id
private String username; // 用戶名
private String password; // 密碼
private Boolean enabled; // 是否啟用
private List<GrantedAuthority> authorities; // 權限列表
public AuthUserDetails(User user, List<GrantedAuthority> authorities) {
this(user.getId(), user.getUsername(), user.getPassword(), user.getEnabled(), authorities);
}
public AuthUserDetails(Integer id, String username, String password, Boolean enabled, List<GrantedAuthority> authorities) {
this.id = id;
this.username = username;
this.password = password;
this.enabled = enabled;
this.authorities = authorities;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return this.enabled;
}
}
```
### 自定義UserDetailsService
> 類名:AuthUserDetailsService
```java
package com.gosuncn.service;
@Service
@AllArgsConstructor
public class AuthUserDetailsService implements UserDetailsService {
private final static String ROLE_PREFIX = "ROLE_";
private final static String SEPARATED_STRING = ",";
@Autowired
private UserMapper userMapper;
@Autowired
private RoleMapper roleMapper;
@Autowired
private AccessMapper accessMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.getUserByUsername(username);
if (user == null) {
return new AuthUserDetails(-1, username, "unknown", false, Collections.EMPTY_LIST);
}
StringBuilder authorityBuilder = new StringBuilder();
for (Role role : roleMapper.getRolesByUserId(user.getId())) {
authorityBuilder.append(ROLE_PREFIX + role.getName());
for (Access access : accessMapper.getAccessesByRoleId(role.getId())) {
authorityBuilder.append(SEPARATED_STRING).append(access.getName());
}
}
return new AuthUserDetails(user, AuthorityUtils.commaSeparatedStringToAuthorityList(authorityBuilder.toString()));
}
}
```
### 配置認證成功處理
```java
package com.gosuncn.config;
@Component
public class AuthSuccessHandler implements AuthenticationSuccessHandler {
private static final String CONTENT_TYPE = "application/json; charset=UTF-8";
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setContentType(CONTENT_TYPE);
response.getWriter().write(objectMapper.writeValueAsString(new CommonResult<>(HttpStatus.OK.value(), HttpStatus.OK.getReasonPhrase(), authentication)));
}
}
```
> 將authentication暴露只是測試實驗,實際投入生產切不可暴露。
### 配置認證失敗處理
```java
package com.gosuncn.config;
@Component
public class AuthFailureHandler implements AuthenticationFailureHandler {
private static final String CONTENT_TYPE = "application/json; charset=UTF-8";
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setContentType(CONTENT_TYPE);
response.getWriter().write(objectMapper.writeValueAsString(new CommonResult<>(HttpStatus.FORBIDDEN.value(), HttpStatus.FORBIDDEN.getReasonPhrase(), exception.getMessage())));
}
}
```
### 創建SpringSecurity配置文件
```java
@Configuration
@AllArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final PasswordEncoder passwordEncoder;
private final UserDetailsService userDetailsService;
private final AuthSuccessHandler successHandler;
private final AuthFailureHandler failureHandler;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.permitAll()
.successHandler(successHandler)
.failureHandler(failureHandler);
http.authorizeRequests()
.anyRequest().authenticated();
http.csrf()
.disable();
}
}
```
### 配置PasswordEncoder密碼編碼器
```java
package com.gosuncn.config;
@Configuration
public class PasswordEncoderConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
```
### 配置token存儲策略
##### 創建配置TokenStoreConfig類
```java
package com.gosuncn.config;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
@Configuration
public class TokenStoreConfig {
private static final String DIR_NAME = "PrivateKey" + "/";
@Value("${cloudframe.private-key}")
private String privateKey;
@Value("${cloudframe.key-password}")
private String keyPassword;
@Value("${cloudframe.alias}")
private String alias;
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setKeyPair(new KeyStoreKeyFactory(new ClassPathResource(DIR_NAME + privateKey), keyPassword.toCharArray()).getKeyPair(alias));
return converter;
}
}
```
##### 修改配置文件application.yml
```yaml
server:
port: 80
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql:///sys_rbac?serverTimezone=UTC
username: root
password: root
mybatis:
mapper-locations: classpath*:/com/gosuncn/dao/*.xml
cloudframe:
private-key: cigc.jks
key-password: cigcjks
alias: cigcjks
```
### 創建授權服務器配置文件
```java
package com.gosuncn.config;
import javax.sql.DataSource;
@Configuration
@AllArgsConstructor
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
private final TokenStore tokenStore;
private final AuthenticationManager authenticationManager;
private final JwtAccessTokenConverter accessTokenConverter;
private final DataSource dataSource;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(new JdbcClientDetailsService(dataSource));
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.tokenStore(tokenStore)
.accessTokenConverter(accessTokenConverter)
.authenticationManager(authenticationManager)
.allowedTokenEndpointRequestMethods(HttpMethod.POST);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security
.tokenKeyAccess("permitAll()")
.allowFormAuthenticationForClients();
}
}
```