# 基础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();
}
}
```