1354 lines
45 KiB
Markdown
1354 lines
45 KiB
Markdown
---
|
||
dir:
|
||
order: 2
|
||
---
|
||
|
||
# 之前SpringSecurity
|
||
**官方文档:**[https://docs.spring.io/spring-security/reference/index.html](https://docs.spring.io/spring-security/reference/index.html)
|
||
|
||
**功能:**
|
||
|
||
+ 身份认证(authentication)
|
||
+ 授权(authorization)
|
||
+ 防御常见攻击(protection against common attacks)
|
||
|
||
**身份认证:**
|
||
|
||
+ 身份认证是验证`谁正在访问系统资源`,判断用户是否为合法用户。认证用户的常见方式是要求用户输入用户名和密码。
|
||
|
||
**授权:**
|
||
|
||
+ 用户进行身份认证后,系统会控制`谁能访问哪些资源`,这个过程叫做授权。用户无法访问没有权限的资源。
|
||
|
||
## 身份认证
|
||
**官方代码示例:**[GitHub - spring-projects/spring-security-samples](https://github.com/spring-projects/spring-security-samples/tree/main)
|
||
|
||
### 项目的基本搭建
|
||
项目搭建完成后,默认端口是8080,直接访问`localhost:8080`即可。
|
||
|
||
**浏览器自动跳转到登录页面:**[http://localhost:8080/login](http://localhost:8080/login)
|
||
|
||
项目结构
|
||
|
||

|
||
|
||
#### 基本包
|
||
这里用到了数据库但是在项目刚开始启动时,是没有配置数据库的,这时候启动肯定会报错,所以我们现在启动类上排出连接数据库的类。
|
||
|
||
```xml
|
||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||
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>
|
||
<parent>
|
||
<groupId>org.springframework.boot</groupId>
|
||
<artifactId>spring-boot-starter-parent</artifactId>
|
||
<version>3.2.0</version>
|
||
</parent>
|
||
<groupId>com.atguigu</groupId>
|
||
<artifactId>security-demo</artifactId>
|
||
<version>1.0-SNAPSHOT</version>
|
||
<packaging>jar</packaging>
|
||
<name>security-demo</name>
|
||
<url>https://maven.apache.org</url>
|
||
<properties>
|
||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||
<maven.compiler.source>21</maven.compiler.source>
|
||
<maven.compiler.target>21</maven.compiler.target>
|
||
</properties>
|
||
<dependencies>
|
||
<dependency>
|
||
<groupId>org.springframework.boot</groupId>
|
||
<artifactId>spring-boot-starter-web</artifactId>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>org.springframework.boot</groupId>
|
||
<artifactId>spring-boot-starter-security</artifactId>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>org.thymeleaf.extras</groupId>
|
||
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>org.springframework.boot</groupId>
|
||
<artifactId>spring-boot-starter-test</artifactId>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>org.springframework.security</groupId>
|
||
<artifactId>spring-security-test</artifactId>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>org.springframework.boot</groupId>
|
||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>mysql</groupId>
|
||
<artifactId>mysql-connector-java</artifactId>
|
||
<version>8.0.30</version>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>com.baomidou</groupId>
|
||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||
<version>3.5.4.1</version>
|
||
<exclusions>
|
||
<exclusion>
|
||
<groupId>org.mybatis</groupId>
|
||
<artifactId>mybatis-spring</artifactId>
|
||
</exclusion>
|
||
</exclusions>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>org.mybatis</groupId>
|
||
<artifactId>mybatis-spring</artifactId>
|
||
<version>3.0.3</version>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>org.projectlombok</groupId>
|
||
<artifactId>lombok</artifactId>
|
||
</dependency>
|
||
</dependencies>
|
||
</project>
|
||
|
||
```
|
||
|
||
#### 创建启动类
|
||
在启动类上排出数据库的类。
|
||
|
||
```java
|
||
import org.springframework.boot.SpringApplication;
|
||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||
|
||
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
|
||
public class SecurityDemoApplication {
|
||
public static void main(String[] args) {
|
||
SpringApplication.run(SecurityDemoApplication.class, args);
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 创建IndexController
|
||
```java
|
||
import org.springframework.stereotype.Controller;
|
||
import org.springframework.web.bind.annotation.GetMapping;
|
||
|
||
@Controller
|
||
public class IndexController {
|
||
@GetMapping("/")
|
||
public String index() {
|
||
return "index";
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 创建index.html
|
||
这里使用的动态标签`th:href="@{/logout}"`目的是可以自动检测路径变化,比如我们在配置文件中配置了全局路径参数。这时候动态标签会自动匹配。
|
||
|
||
这时需要访问根路径`localhost:8080/demo`
|
||
|
||
```yaml
|
||
server:
|
||
servlet:
|
||
context-path: /demo
|
||
```
|
||
|
||
HTML模板
|
||
|
||
```html
|
||
<html lang="zh" xmlns:th="https://www.thymeleaf.org">
|
||
<head>
|
||
<title>Hello Security!</title>
|
||
</head>
|
||
<body>
|
||
<h1>Hello Security</h1>
|
||
<!--通过使用@{/logout},Thymeleaf将自动处理生成正确的URL,以适应当前的上下文路径。
|
||
这样,无论应用程序部署在哪个上下文路径下,生成的URL都能正确地指向注销功能。-->
|
||
<a th:href="@{/logout}">Log Out</a>
|
||
</body>
|
||
</html>
|
||
|
||
```
|
||
|
||
比如点击下面按钮会自动匹配路径并退出。
|
||
|
||

|
||
|
||
## 自定义Security配置
|
||
SecurityProperties修改默认用户和密码。
|
||
|
||
```yaml
|
||
spring:
|
||
security:
|
||
user:
|
||
name: user
|
||
password: admin123
|
||
```
|
||
|
||
### 使用配置类
|
||
```java
|
||
@Configuration
|
||
@EnableWebSecurity//Spring项目总需要添加此注解,SpringBoot项目中不需要
|
||
public class WebSecurityConfig {
|
||
@Bean
|
||
public UserDetailsService userDetailsService() {
|
||
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
|
||
manager.createUser( //此行设置断点可以查看创建的user对象
|
||
User
|
||
.withDefaultPasswordEncoder()
|
||
.username("huan") //自定义用户名
|
||
.password("password") //自定义密码
|
||
.roles("USER") //自定义角色
|
||
.build()
|
||
);
|
||
return manager;
|
||
}
|
||
```
|
||
|
||
## 基于数据库的数据源
|
||
### 环境准备
|
||
创建三个数据库表并插入测试数据
|
||
|
||
```sql
|
||
-- 创建数据库
|
||
CREATE DATABASE `security-demo`;
|
||
USE `security-demo`;
|
||
|
||
-- 创建用户表
|
||
CREATE TABLE `user`(
|
||
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||
`username` VARCHAR(50) DEFAULT NULL ,
|
||
`password` VARCHAR(500) DEFAULT NULL,
|
||
`enabled` BOOLEAN NOT NULL
|
||
);
|
||
-- 唯一索引
|
||
CREATE UNIQUE INDEX `user_username_uindex` ON `user`(`username`);
|
||
|
||
-- 插入用户数据(密码是 "abc" )
|
||
INSERT INTO `user` (`username`, `password`, `enabled`) VALUES
|
||
('admin', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE),
|
||
('Helen', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE),
|
||
('Tom', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE);
|
||
```
|
||
|
||
#### 引入依赖
|
||
这个在之前也引用过了。
|
||
|
||
```xml
|
||
<dependency>
|
||
<groupId>mysql</groupId>
|
||
<artifactId>mysql-connector-java</artifactId>
|
||
<version>8.0.30</version>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>com.baomidou</groupId>
|
||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||
<version>3.5.4.1</version>
|
||
<exclusions>
|
||
<exclusion>
|
||
<groupId>org.mybatis</groupId>
|
||
<artifactId>mybatis-spring</artifactId>
|
||
</exclusion>
|
||
</exclusions>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>org.mybatis</groupId>
|
||
<artifactId>mybatis-spring</artifactId>
|
||
<version>3.0.3</version>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>org.projectlombok</groupId>
|
||
<artifactId>lombok</artifactId>
|
||
</dependency>
|
||
|
||
```
|
||
|
||
#### 配置文件中
|
||
```yaml
|
||
datasource:
|
||
type: com.zaxxer.hikari.HikariDataSource
|
||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||
url: jdbc:mysql://${bunny.datasource.host}:${bunny.datasource.port}/${bunny.datasource.sqlData}?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=utf-8&allowPublicKeyRetrieval=true
|
||
username: ${bunny.datasource.username}
|
||
password: ${bunny.datasource.password}
|
||
```
|
||
|
||
#### 启动类
|
||
启动类记得删除排出数据库源的类。
|
||
|
||
```java
|
||
@SpringBootApplication
|
||
public class SecurityDemoApplication {
|
||
public static void main(String[] args) {
|
||
SpringApplication.run(SecurityDemoApplication.class, args);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 配置数据库查询
|
||
创建`DBUserDetailsManager`类实现UserDetailsManager, UserDetailsPasswordService方法。
|
||
|
||
查询数据库字段进行匹配,如果查询到并且密码正确就可以放行。记得在方法上加上`@Configuration`注解。
|
||
|
||
```java
|
||
@Configuration
|
||
public class DBUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {
|
||
|
||
@Resource
|
||
private UserMapper userMapper;
|
||
|
||
@Override
|
||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||
|
||
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
|
||
queryWrapper.eq("username", username);
|
||
User user = userMapper.selectOne(queryWrapper);
|
||
if (user == null) {
|
||
throw new UsernameNotFoundException(username);
|
||
} else {
|
||
Collection<GrantedAuthority> authorities = new ArrayList<>();
|
||
return new org.springframework.security.core.userdetails.User(
|
||
user.getUsername(),
|
||
user.getPassword(),
|
||
user.getEnabled(),
|
||
true, // 用户账号是否过期
|
||
true, // 用户凭证是否过期
|
||
true, // 用户是否未被锁定
|
||
authorities); // 权限列表
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public UserDetails updatePassword(UserDetails user, String newPassword) {
|
||
return null;
|
||
}
|
||
|
||
@Override
|
||
public void createUser(UserDetails user) {
|
||
|
||
}
|
||
|
||
@Override
|
||
public void updateUser(UserDetails user) {
|
||
|
||
}
|
||
|
||
@Override
|
||
public void deleteUser(String username) {
|
||
|
||
}
|
||
|
||
@Override
|
||
public void changePassword(String oldPassword, String newPassword) {
|
||
|
||
}
|
||
|
||
@Override
|
||
public boolean userExists(String username) {
|
||
return false;
|
||
}
|
||
}
|
||
```
|
||
|
||
## 配置Security默认配置
|
||
+ `formLogin(withDefaults())`提供默认的登录模拟页面。
|
||
+ 如果开启了`formLogin(withDefaults())`可以`httpBasic(withDefaults())`屏蔽。
|
||
|
||
```java
|
||
import org.springframework.context.annotation.Bean;
|
||
import org.springframework.context.annotation.Configuration;
|
||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||
import org.springframework.security.web.SecurityFilterChain;
|
||
|
||
import static org.springframework.security.config.Customizer.withDefaults;
|
||
|
||
@Configuration
|
||
@EnableWebSecurity// Spring项目总需要添加此注解,SpringBoot项目中不需要
|
||
public class WebSecurityConfig {
|
||
@Bean
|
||
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
|
||
// authorizeRequests():开启授权保护
|
||
// anyRequest():对所有请求开启授权保护
|
||
// authenticated():已认证请求会自动被授权
|
||
httpSecurity
|
||
.authorizeRequests(authorize -> authorize
|
||
.anyRequest()
|
||
.authenticated())
|
||
// .formLogin(withDefaults())// 表单授权方式
|
||
.httpBasic(withDefaults());// 基本授权方式
|
||
|
||
return httpSecurity.build();
|
||
}
|
||
}
|
||
```
|
||
|
||
## 添加用户
|
||
在Controller层添加接口,写入添加用户的方法,之后在实现接口中添加这个方法接口。
|
||
|
||
```java
|
||
@RestController
|
||
@RequestMapping("/user")
|
||
@Tag(name = "用户请求接口")
|
||
public class UserController {
|
||
@Autowired
|
||
private UserService userService;
|
||
|
||
@Operation(summary = "添加用户")
|
||
@PostMapping("/add")
|
||
public void addUser(@RequestBody User user) {
|
||
userService.addUserDetails(user);
|
||
}
|
||
}
|
||
```
|
||
|
||
在实现接口中实现这个方法。
|
||
|
||
1. 注入`DBUserDetailsManager`之后创建这个user。
|
||
|
||
```java
|
||
import com.atguigu.security.config.DBUserDetailsManager;
|
||
import com.atguigu.security.entity.User;
|
||
import com.atguigu.security.mapper.UserMapper;
|
||
import com.atguigu.security.service.UserService;
|
||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||
import org.springframework.beans.factory.annotation.Autowired;
|
||
import org.springframework.security.core.userdetails.UserDetails;
|
||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||
import org.springframework.stereotype.Service;
|
||
|
||
@Service
|
||
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
|
||
@Autowired
|
||
private DBUserDetailsManager manager;
|
||
|
||
/**
|
||
* 添加用户
|
||
*
|
||
* @param user 用户信息
|
||
*/
|
||
@Override
|
||
public void addUserDetails(User user) {
|
||
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
|
||
|
||
// 这里的User使用的是springSecurity中的User
|
||
UserDetails userDetails = org.springframework.security.core.userdetails.User
|
||
.withDefaultPasswordEncoder()
|
||
.username(user.getUsername())
|
||
.password(user.getPassword())
|
||
.build();
|
||
|
||
manager.createUser(userDetails);
|
||
}
|
||
}
|
||
```
|
||
|
||
在方法中添加以下内容`createUser`这个方法
|
||
|
||
1. 导入了一些需要使用的类,包括`User`实体类和`UserMapper`接口。
|
||
2. 声明了一个`UserMapper`类型的字段`userMapper`。
|
||
3. 实现了`createUser`方法,该方法用于创建用户。在这个示例中,该方法将传入的`UserDetails`对象中的用户名和密码插入到数据库中。
|
||
4. 实现了`userExists`方法,该方法用于检查用户是否存在。在这个示例中,该方法始终返回`false`。
|
||
|
||
```java
|
||
import com.atguigu.security.entity.User;
|
||
import com.atguigu.security.mapper.UserMapper;
|
||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||
import jakarta.annotation.Resource;
|
||
import org.springframework.context.annotation.Configuration;
|
||
import org.springframework.security.core.GrantedAuthority;
|
||
import org.springframework.security.core.userdetails.UserDetails;
|
||
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
|
||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||
import org.springframework.security.provisioning.UserDetailsManager;
|
||
|
||
import java.util.ArrayList;
|
||
import java.util.Collection;
|
||
|
||
@Configuration
|
||
public class DBUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {
|
||
|
||
@Resource
|
||
private UserMapper userMapper;
|
||
|
||
@Override
|
||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||
|
||
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
|
||
queryWrapper.eq("username", username);
|
||
User user = userMapper.selectOne(queryWrapper);
|
||
if (user == null) {
|
||
throw new UsernameNotFoundException(username);
|
||
} else {
|
||
Collection<GrantedAuthority> authorities = new ArrayList<>();
|
||
return new org.springframework.security.core.userdetails.User(
|
||
user.getUsername(),
|
||
user.getPassword(),
|
||
user.getEnabled(),
|
||
true, // 用户账号是否过期
|
||
true, // 用户凭证是否过期
|
||
true, // 用户是否未被锁定
|
||
authorities); // 权限列表
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void createUser(UserDetails userDetails) {
|
||
// 插入数据库方法
|
||
User user = new User();
|
||
user.setUsername(userDetails.getUsername());
|
||
user.setPassword(userDetails.getPassword());
|
||
user.setEnabled(true);
|
||
userMapper.insert(user);
|
||
}
|
||
|
||
@Override
|
||
public boolean userExists(String username) {
|
||
return false;
|
||
}
|
||
// 略...
|
||
}
|
||
```
|
||
|
||
> 为了方便调试,springSecurity默认开启了csrf(这要求请求参数中必须有一个隐藏的**_csrf**字段),为了测试这里就暂时关闭。
|
||
>
|
||
> 在filterChain方法中添加如下代码,关闭csrf攻击防御
|
||
>
|
||
> 代码示例:
|
||
>
|
||
|
||
```java
|
||
//关闭csrf攻击防御
|
||
http.csrf((csrf) -> {
|
||
csrf.disable();
|
||
});
|
||
```
|
||
|
||
```java
|
||
@Configuration
|
||
@EnableWebSecurity// Spring项目总需要添加此注解,SpringBoot项目中不需要
|
||
public class WebSecurityConfig {
|
||
@Bean
|
||
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
|
||
// authorizeRequests():开启授权保护
|
||
// anyRequest():对所有请求开启授权保护
|
||
// authenticated():已认证请求会自动被授权
|
||
httpSecurity
|
||
.authorizeRequests(authorize -> authorize
|
||
.anyRequest()
|
||
.authenticated())
|
||
.formLogin(withDefaults())// 表单授权方式
|
||
.httpBasic(withDefaults());// 基本授权方式
|
||
|
||
// 关闭csrf攻击
|
||
httpSecurity.csrf(AbstractHttpConfigurer::disable);
|
||
return httpSecurity.build();
|
||
}
|
||
}
|
||
```
|
||
|
||
## 密码加密测试
|
||
创建测试方法:
|
||
|
||
```java
|
||
@Slf4j
|
||
public class PasswordTest {
|
||
@Test
|
||
void testPassword() throws Exception {
|
||
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
|
||
|
||
String password = encoder.encode("password");
|
||
log.info("Password===>:{}", password);
|
||
|
||
// 密码校验
|
||
Assert.isTrue(encoder.matches("password", password), "密码不一致");
|
||
}
|
||
}
|
||
```
|
||
|
||
## 自定义登录页面
|
||
### 创建登录页
|
||

|
||
|
||
#### 第一步:创建Controller
|
||
```java
|
||
import org.springframework.stereotype.Controller;
|
||
import org.springframework.web.bind.annotation.GetMapping;
|
||
|
||
@Controller
|
||
public class IndexController {
|
||
@GetMapping("/login")
|
||
public String login() {
|
||
return "login";
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 第二步:创建HTML
|
||
```html
|
||
<!DOCTYPE html>
|
||
<html xmlns:th="https://www.thymeleaf.org">
|
||
<head>
|
||
<title>登录</title>
|
||
</head>
|
||
<body>
|
||
<h1>登录</h1>
|
||
<div th:if="${param.error}">
|
||
错误的用户名和密码.
|
||
</div>
|
||
<!--method必须为"post"-->
|
||
<!--th:action="@{/login}" ,
|
||
使用动态参数,表单中会自动生成_csrf隐藏字段,用于防止csrf攻击
|
||
login: 和登录页面保持一致即可,SpringSecurity自动进行登录认证-->
|
||
<form method="post" th:action="@{/login}">
|
||
<div>
|
||
<!--name必须为"username"-->
|
||
<input name="username" placeholder="用户名" type="text"/>
|
||
</div>
|
||
<div>
|
||
<!--name必须为"password"-->
|
||
<input name="password" placeholder="密码" type="password"/>
|
||
</div>
|
||
<input type="submit" value="登录"/>
|
||
</form>
|
||
</body>
|
||
</html>
|
||
|
||
```
|
||
|
||
#### 第三步:配置Security
|
||
```java
|
||
import org.springframework.context.annotation.Bean;
|
||
import org.springframework.context.annotation.Configuration;
|
||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||
import org.springframework.security.web.SecurityFilterChain;
|
||
|
||
import static org.springframework.security.config.Customizer.withDefaults;
|
||
|
||
@Configuration
|
||
@EnableWebSecurity// Spring项目总需要添加此注解,SpringBoot项目中不需要
|
||
public class WebSecurityConfig {
|
||
@Bean
|
||
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
|
||
// authorizeRequests():开启授权保护
|
||
// anyRequest():对所有请求开启授权保护
|
||
// authenticated():已认证请求会自动被授权
|
||
httpSecurity
|
||
.authorizeRequests(authorize -> authorize
|
||
.anyRequest()
|
||
.authenticated())
|
||
.formLogin(withDefaults())// 表单授权方式
|
||
.httpBasic(withDefaults());// 基本授权方式
|
||
|
||
// 关闭csrf攻击
|
||
httpSecurity.csrf(AbstractHttpConfigurer::disable);
|
||
|
||
// 配置SecurityFilterChain-自定义登录页
|
||
httpSecurity.formLogin(form -> {
|
||
form.loginPage("/login").permitAll()// 登录页面无需授权即可访问
|
||
.usernameParameter("username")// 自定义表单用户名参数,默认是username
|
||
.passwordParameter("password")// 自定义表单密码参数,默认是password
|
||
.failureUrl("/login?error"); // 登录失败的返回地址
|
||
});
|
||
|
||
return httpSecurity.build();
|
||
}
|
||
}
|
||
```
|
||
|
||

|
||
|
||
### 登录页的细节
|
||
在`WebSecurityConfig`中自定义前端传递值,默认传递用户名和密码为`username`和`password`,在下面示例中可以修改为自定义的用户名和密码参数。
|
||
|
||
```java
|
||
// 配置SecurityFilterChain-自定义登录页
|
||
httpSecurity.formLogin(form -> {
|
||
form.loginPage("/login").permitAll()// 登录页面无需授权即可访问
|
||
.usernameParameter("username")// 自定义表单用户名参数,默认是username
|
||
.passwordParameter("password")// 自定义表单密码参数,默认是password
|
||
.failureUrl("/login?error"); // 登录失败的返回地址
|
||
});
|
||
```
|
||
|
||
#### 自定义前端传递参数
|
||
```java
|
||
// 配置SecurityFilterChain-自定义登录页
|
||
httpSecurity.formLogin(form -> {
|
||
form.loginPage("自定义登录页").permitAll()// 登录页面无需授权即可访问
|
||
.usernameParameter("自定义用户名")// 自定义表单用户名参数,默认是username
|
||
.passwordParameter("自定义密码")// 自定义表单密码参数,默认是password
|
||
.failureUrl("自定义错误页"); // 登录失败的返回地址
|
||
});
|
||
```
|
||
|
||
## 认证响应结果
|
||
移入fastjson
|
||
|
||
```xml
|
||
<dependency>
|
||
<groupId>com.alibaba.fastjson2</groupId>
|
||
<artifactId>fastjson2</artifactId>
|
||
<version>2.0.37</version>
|
||
</dependency>
|
||
|
||
```
|
||
|
||
### 认证成功返回
|
||
```java
|
||
import com.alibaba.fastjson2.JSON;
|
||
import com.atguigu.security.result.Result;
|
||
import jakarta.servlet.ServletException;
|
||
import jakarta.servlet.http.HttpServletRequest;
|
||
import jakarta.servlet.http.HttpServletResponse;
|
||
import org.springframework.security.core.Authentication;
|
||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||
|
||
import java.io.IOException;
|
||
|
||
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
|
||
@Override
|
||
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
|
||
// 获取用户身份信息
|
||
Object principal = authentication.getPrincipal();
|
||
// 获取用户凭证信息
|
||
// Object credentials = authentication.getCredentials();
|
||
// 获取用户权限信息
|
||
// Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
|
||
|
||
Result<Object> result = Result.success(principal);
|
||
|
||
// 返回
|
||
response.setContentType("application/json;charset=UTF-8");
|
||
response.getWriter().println(JSON.toJSON(result));
|
||
}
|
||
}
|
||
```
|
||
|
||

|
||
|
||
在`WebSecurityConfig`类中添加以下`.successHandler(new MyAuthenticationSuccessHandler());`表示成功的返回结果,自定义结果。
|
||
|
||
如果想看Result的类翻到最下面附录。
|
||
|
||
```java
|
||
// 配置SecurityFilterChain-自定义登录页
|
||
httpSecurity.formLogin(form -> {
|
||
form.loginPage("/login").permitAll()// 登录页面无需授权即可访问
|
||
.usernameParameter("username")// 自定义表单用户名参数,默认是username
|
||
.passwordParameter("password")// 自定义表单密码参数,默认是password
|
||
.failureUrl("/login?error") // 登录失败的返回地址
|
||
.successHandler(new MyAuthenticationSuccessHandler());// 认证成功时的处理
|
||
});
|
||
```
|
||
|
||
### 认证失败返回
|
||
和成功的返回相似,只需要修改两个地方即可。
|
||
|
||
认证失败的类
|
||
|
||
```java
|
||
import com.alibaba.fastjson2.JSON;
|
||
import com.atguigu.security.result.Result;
|
||
import jakarta.servlet.ServletException;
|
||
import jakarta.servlet.http.HttpServletRequest;
|
||
import jakarta.servlet.http.HttpServletResponse;
|
||
import org.springframework.security.core.AuthenticationException;
|
||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||
|
||
import java.io.IOException;
|
||
|
||
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
|
||
@Override
|
||
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
|
||
// 错误消息
|
||
String localizedMessage = exception.getLocalizedMessage();
|
||
Result<String> result = Result.error(localizedMessage);
|
||
|
||
// 转成JSON
|
||
Object json = JSON.toJSON(result);
|
||
|
||
// 返回响应
|
||
response.setContentType("application/json;charset=UTF-8");
|
||
response.getWriter().println(json);
|
||
}
|
||
}
|
||
```
|
||
|
||
在WebSecurityConfig类中添加`failureHandler`即可`.failureHandler(new MyAuthenticationFailureHandler());`
|
||
|
||
```java
|
||
// 配置SecurityFilterChain-自定义登录页
|
||
httpSecurity.formLogin(form -> {
|
||
form.loginPage("/login").permitAll()// 登录页面无需授权即可访问
|
||
.usernameParameter("username")// 自定义表单用户名参数,默认是username
|
||
.passwordParameter("password")// 自定义表单密码参数,默认是password
|
||
.failureUrl("/login?error") // 登录失败的返回地址
|
||
.successHandler(new MyAuthenticationSuccessHandler())// 认证成功时的处理
|
||
.failureHandler(new MyAuthenticationFailureHandler());// 认证失败的处理
|
||
});
|
||
```
|
||
|
||

|
||
|
||
### 注销响应
|
||
和前面成功和失败过程相似,只需要在`from`中再添加即可。
|
||
|
||
```java
|
||
import com.alibaba.fastjson2.JSON;
|
||
import com.atguigu.security.result.Result;
|
||
import jakarta.servlet.ServletException;
|
||
import jakarta.servlet.http.HttpServletRequest;
|
||
import jakarta.servlet.http.HttpServletResponse;
|
||
import org.springframework.security.core.Authentication;
|
||
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
|
||
|
||
import java.io.IOException;
|
||
|
||
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
|
||
@Override
|
||
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
|
||
// 创建结果对象
|
||
Result<Object> result = Result.success();
|
||
|
||
// 转为JSON
|
||
Object json = JSON.toJSON(result);
|
||
|
||
// 返回响应
|
||
response.setContentType("application/json;charset=UTF-8");
|
||
response.getWriter().println(json);
|
||
}
|
||
}
|
||
```
|
||
|
||
在`WebSecurityConfig`类中添加以下。
|
||
|
||
```java
|
||
// 注销响应
|
||
httpSecurity.logout(logout -> {
|
||
logout.logoutSuccessHandler(new MyLogoutSuccessHandler());// 注销成功时的处理
|
||
});
|
||
```
|
||
|
||

|
||
|
||
之后访问:[http://localhost/demo](http://localhost/demo)
|
||
|
||
### 请求未认证接口
|
||
```java
|
||
import com.alibaba.fastjson2.JSON;
|
||
import com.atguigu.security.result.Result;
|
||
import com.atguigu.security.result.ResultCodeEnum;
|
||
import jakarta.servlet.ServletException;
|
||
import jakarta.servlet.http.HttpServletRequest;
|
||
import jakarta.servlet.http.HttpServletResponse;
|
||
import lombok.extern.slf4j.Slf4j;
|
||
import org.springframework.security.core.AuthenticationException;
|
||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||
|
||
import java.io.IOException;
|
||
|
||
/**
|
||
* 请求未认证接口
|
||
*/
|
||
@Slf4j
|
||
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||
@Override
|
||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
|
||
// 错误信息
|
||
String localizedMessage = authException.getLocalizedMessage();
|
||
log.error("请求未认证接口:{}", localizedMessage);
|
||
|
||
// 创建结果对象
|
||
Result<Object> result = Result.error(ResultCodeEnum.FAIL_NEED_LOGIN);
|
||
|
||
// 返回响应
|
||
response.setContentType("application/json;charset=UTF-8");
|
||
response.getWriter().println(JSON.toJSON(result));
|
||
}
|
||
}
|
||
```
|
||
|
||
在`WebSecurityConfig`类中添加以下。
|
||
|
||
```java
|
||
// 请求未认证接口
|
||
httpSecurity.exceptionHandling(exception -> {
|
||
// 请求未认证的接口
|
||
exception.authenticationEntryPoint(new MyAuthenticationEntryPoint());
|
||
});
|
||
```
|
||
|
||
访问:[http://localhost/demo/](http://localhost/demo/)
|
||
|
||

|
||
|
||
登录后
|
||
|
||

|
||
|
||

|
||
|
||
### 跨域访问
|
||
在`WebSecurityConfig`类中添加以下。
|
||
|
||
```java
|
||
// 跨域访问权限
|
||
httpSecurity.cors(withDefaults());
|
||
```
|
||
|
||
## 获取用户认证信息
|
||
USerVo类见附录。
|
||
|
||
```java
|
||
@RestController
|
||
public class IndexController {
|
||
@GetMapping("/")
|
||
public Result<UserVo> index() {
|
||
SecurityContext context = SecurityContextHolder.getContext();
|
||
Authentication authentication = context.getAuthentication();
|
||
|
||
// 用户名
|
||
String username = authentication.getName();
|
||
// 身份
|
||
Object principal = authentication.getPrincipal();
|
||
// 凭证(脱敏)
|
||
Object credentials = authentication.getCredentials();
|
||
// 权限
|
||
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
|
||
|
||
// 整理返回参数
|
||
UserVo userVo = UserVo.builder().authorities(authorities).credentials(credentials)
|
||
.principal(principal).username(username).build();
|
||
|
||
return Result.success(userVo);
|
||
}
|
||
}
|
||
```
|
||
|
||
## 会话并发处理
|
||
后登录的账号会使先登录的账号失效。实现方式和之前的差不多也是实现一个接口。
|
||
|
||
```java
|
||
import com.alibaba.fastjson2.JSON;
|
||
import com.atguigu.security.result.Result;
|
||
import com.atguigu.security.result.ResultCodeEnum;
|
||
import jakarta.servlet.ServletException;
|
||
import jakarta.servlet.http.HttpServletResponse;
|
||
import org.springframework.security.web.session.SessionInformationExpiredEvent;
|
||
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
|
||
|
||
import java.io.IOException;
|
||
|
||
public class MySessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {
|
||
@Override
|
||
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
|
||
// 创建结果对象
|
||
Result<Object> result = Result.error(ResultCodeEnum.LOGGED_IN_FROM_ANOTHER_DEVICE);
|
||
|
||
// 转为JSON
|
||
Object json = JSON.toJSON(result);
|
||
|
||
// 返回响应
|
||
HttpServletResponse response = event.getResponse();
|
||
response.setContentType("application/json;charset=UTF-8");
|
||
response.getWriter().println(json);
|
||
}
|
||
}
|
||
```
|
||
|
||
在`WebSecurityConfig`类中添加以下。
|
||
|
||
```java
|
||
// 后登录的账号会使先登录的账号失效
|
||
httpSecurity.sessionManagement(session -> {
|
||
session.maximumSessions(1)
|
||
.expiredSessionStrategy(new MySessionInformationExpiredStrategy());
|
||
});
|
||
```
|
||
|
||
## 授权访问
|
||
授权管理的实现在SpringSecurity中非常灵活,可以帮助应用程序实现以下两种常见的授权需求:
|
||
|
||
+ 用户-权限-资源:例如张三的权限是添加用户、查看用户列表,李四的权限是查看用户列表
|
||
+ 用户-角色-权限-资源:例如 张三是角色是管理员、李四的角色是普通用户,管理员能做所有操作,普通用户只能查看信息
|
||
|
||
### 基于request的授权
|
||
**需求:**
|
||
|
||
+ 具有USER_LIST权限的用户可以访问/user/list接口
|
||
+ 具有USER_ADD权限的用户可以访问/user/add接口
|
||
|
||
在`WebSecurityConfig`添加下面内容。
|
||
|
||
```java
|
||
// authorizeRequests():开启授权保护
|
||
httpSecurity.authorizeRequests(authorize -> {
|
||
// 具有USER_LIST权限的用户可以访问/user/list,访问路径是Controller中的路径
|
||
authorize.requestMatchers("/user/list").hasAuthority("USER_LIST")
|
||
// 具有USER_ADD权限的用户可以访问/user/add
|
||
.requestMatchers("/user/add").hasAuthority("USER_ADD")
|
||
// 对所有请求开启授权保护
|
||
.anyRequest()
|
||
// 已认证请求会自动被授权
|
||
.authenticated();
|
||
});
|
||
```
|
||
|
||
之后在`DBUserDetailsManager`中授予访问权限。
|
||
|
||
在其中注释`authorities.add(() -> "USER_LIST")`会发现没有权限访问。
|
||
|
||
```java
|
||
// 授予访问权限
|
||
authorities.add(() -> "USER_LIST");
|
||
authorities.add(() -> "USER_ADD");
|
||
```
|
||
|
||
完整代码
|
||
|
||
```java
|
||
import com.atguigu.security.mapper.UserMapper;
|
||
import com.atguigu.security.model.entity.User;
|
||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||
import jakarta.annotation.Resource;
|
||
import org.springframework.context.annotation.Configuration;
|
||
import org.springframework.security.core.GrantedAuthority;
|
||
import org.springframework.security.core.userdetails.UserDetails;
|
||
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
|
||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||
import org.springframework.security.provisioning.UserDetailsManager;
|
||
|
||
import java.util.ArrayList;
|
||
import java.util.Collection;
|
||
|
||
@Configuration
|
||
public class DBUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {
|
||
|
||
@Resource
|
||
private UserMapper userMapper;
|
||
|
||
@Override
|
||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
|
||
queryWrapper.eq("username", username);
|
||
User user = userMapper.selectOne(queryWrapper);
|
||
if (user == null) {
|
||
throw new UsernameNotFoundException(username);
|
||
} else {
|
||
Collection<GrantedAuthority> authorities = new ArrayList<>();
|
||
// 授予访问权限
|
||
authorities.add(() -> "USER_LIST");
|
||
authorities.add(() -> "USER_ADD");
|
||
return new org.springframework.security.core.userdetails.User(
|
||
user.getUsername(),
|
||
user.getPassword(),
|
||
user.getEnabled(),
|
||
true, // 用户账号是否过期
|
||
true, // 用户凭证是否过期
|
||
true, // 用户是否未被锁定
|
||
authorities); // 权限列表
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
请求未授权的接口
|
||
|
||
创建未授权访问类,返回对象。
|
||
|
||
```java
|
||
import com.alibaba.fastjson2.JSON;
|
||
import com.atguigu.security.result.Result;
|
||
import com.atguigu.security.result.ResultCodeEnum;
|
||
import jakarta.servlet.ServletException;
|
||
import jakarta.servlet.http.HttpServletRequest;
|
||
import jakarta.servlet.http.HttpServletResponse;
|
||
import org.springframework.security.access.AccessDeniedException;
|
||
import org.springframework.security.web.access.AccessDeniedHandler;
|
||
|
||
import java.io.IOException;
|
||
|
||
public class MyAccessDeniedHandler implements AccessDeniedHandler {
|
||
@Override
|
||
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
|
||
Result<Object> result = Result.error(ResultCodeEnum.FAIL_NO_ACCESS_DENIED);
|
||
|
||
Object json = JSON.toJSON(result);
|
||
|
||
// 返回响应
|
||
response.setContentType("application/json;charset=UTF-8");
|
||
response.getWriter().println(json);
|
||
}
|
||
}
|
||
```
|
||
|
||
在`WebSecurityConfig`添加下面内容。
|
||
|
||
```java
|
||
// 请求未授权的接口
|
||
httpSecurity.exceptionHandling(exception -> {
|
||
exception.authenticationEntryPoint(new MyAuthenticationEntryPoint());
|
||
// 没有权限访问
|
||
exception.accessDeniedHandler(new MyAccessDeniedHandler());
|
||
});
|
||
```
|
||
|
||
**更多的例子:**[Method Security :: Spring Security](https://docs.spring.io/spring-security/reference/servlet/authorization/method-security.html)
|
||
|
||
#### 用户-角色-资源
|
||
**需求:**角色为ADMIN的用户才可以访问/user/**路径下的资源
|
||
|
||
##### 配置角色
|
||
在`filterChain`方法中配置;将之前的手动添加的路径注释。
|
||
|
||
`hasRole("ADMIN")`可以自定义内容。
|
||
|
||
```java
|
||
// authorizeRequests():开启授权保护
|
||
httpSecurity.authorizeRequests(authorize -> {
|
||
// 具有USER_LIST权限的用户可以访问/user/list,访问路径是Controller中的路径
|
||
authorize
|
||
// 具有管理员角色的用户可以访问/user/**
|
||
.requestMatchers("/user/**").hasRole("ADMIN")
|
||
// .requestMatchers("/user/list").hasAuthority("USER_LIST")
|
||
// 具有USER_ADD权限的用户可以访问/user/add
|
||
// .requestMatchers("/user/add").hasAuthority("USER_ADD")
|
||
// 对所有请求开启授权保护
|
||
.anyRequest()
|
||
// 已认证请求会自动被授权
|
||
.authenticated();
|
||
});
|
||
```
|
||
|
||
配置DBUserDetailsManager,改变`roles("USER")`
|
||
|
||
+ 如果为`roles("ADMIN")`表示都可以访问
|
||
+ 如果为`roles("USER")`表示只是普通用户。
|
||
|
||
```java
|
||
@Override
|
||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
|
||
queryWrapper.eq("username", username);
|
||
User user = userMapper.selectOne(queryWrapper);
|
||
if (user == null) {
|
||
throw new UsernameNotFoundException(username);
|
||
} else {
|
||
// Collection<GrantedAuthority> authorities = new ArrayList<>();
|
||
// // 授予访问权限
|
||
// authorities.add(() -> "USER_LIST");
|
||
// authorities.add(() -> "USER_ADD");
|
||
// return new org.springframework.security.core.userdetails.User(
|
||
// user.getUsername(),
|
||
// user.getPassword(),
|
||
// user.getEnabled(),
|
||
// true, // 用户账号是否过期
|
||
// true, // 用户凭证是否过期
|
||
// true, // 用户是否未被锁定
|
||
// authorities); // 权限列表
|
||
|
||
return org.springframework.security.core.userdetails.User
|
||
.withUsername(user.getUsername())
|
||
.password(user.getPassword())
|
||
.disabled(!user.getEnabled())
|
||
.credentialsExpired(false)
|
||
.accountLocked(false)
|
||
// SecurityFilterChain中配置hasRole("ADMIN")来判断的
|
||
.roles("USER")
|
||
.build();
|
||
}
|
||
}
|
||
```
|
||
|
||
为`roles("ADMIN")`时全部可以访问。
|
||
|
||

|
||
|
||
为普通用户时。
|
||
|
||

|
||
|
||
|
||
|
||
### RBAC设置权限表方式
|
||
RBAC(Role-Based Access Control,基于角色的访问控制)是一种常用的数据库设计方案,它将用户的权限分配和管理与角色相关联。以下是一个基本的RBAC数据库设计方案的示例:
|
||
|
||
1. 用户表(User table):包含用户的基本信息,例如用户名、密码和其他身份验证信息。
|
||
|
||
| 列名 | 数据类型 | 描述 |
|
||
| --- | --- | --- |
|
||
| user_id | int | 用户ID |
|
||
| username | varchar | 用户名 |
|
||
| password | varchar | 密码 |
|
||
| email | varchar | 电子邮件地址 |
|
||
| ... | ... | ... |
|
||
|
||
|
||
2. 角色表(Role table):存储所有可能的角色及其描述。
|
||
|
||
| 列名 | 数据类型 | 描述 |
|
||
| --- | --- | --- |
|
||
| role_id | int | 角色ID |
|
||
| role_name | varchar | 角色名称 |
|
||
| description | varchar | 角色描述 |
|
||
| ... | ... | ... |
|
||
|
||
|
||
3. 权限表(Permission table):定义系统中所有可能的权限。
|
||
|
||
| 列名 | 数据类型 | 描述 |
|
||
| --- | --- | --- |
|
||
| permission_id | int | 权限ID |
|
||
| permission_name | varchar | 权限名称 |
|
||
| description | varchar | 权限描述 |
|
||
| ... | ... | ... |
|
||
|
||
|
||
4. 用户角色关联表(User-Role table):将用户与角色关联起来。
|
||
|
||
| 列名 | 数据类型 | 描述 |
|
||
| --- | --- | --- |
|
||
| user_role_id | int | 用户角色关联ID |
|
||
| user_id | int | 用户ID |
|
||
| role_id | int | 角色ID |
|
||
| ... | ... | ... |
|
||
|
||
|
||
5. 角色权限关联表(Role-Permission table):将角色与权限关联起来。
|
||
|
||
| 列名 | 数据类型 | 描述 |
|
||
| --- | --- | --- |
|
||
| role_permission_id | int | 角色权限关联ID |
|
||
| role_id | int | 角色ID |
|
||
| permission_id | int | 权限ID |
|
||
| ... | ... | ... |
|
||
|
||
|
||
在这个设计方案中,用户可以被分配一个或多个角色,而每个角色又可以具有一个或多个权限。通过对用户角色关联和角色权限关联表进行操作,可以实现灵活的权限管理和访问控制。
|
||
|
||
当用户尝试访问系统资源时,系统可以根据用户的角色和权限决定是否允许访问。这样的设计方案使得权限管理更加简单和可维护,因为只需调整角色和权限的分配即可,而不需要针对每个用户进行单独的设置。
|
||
|
||
```sql
|
||
CREATE TABLE admin_power (
|
||
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '权限ID',
|
||
power_name VARCHAR(50) NOT NULL COMMENT '权限名称',
|
||
power_code VARCHAR(255) NOT NULL COMMENT '权限编码',
|
||
description VARCHAR(100) COMMENT '描述',
|
||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||
update_user VARCHAR(50) COMMENT '更新用户',
|
||
is_delete TINYINT(1) DEFAULT 0 COMMENT '是否删除,0-未删除,1-已删除'
|
||
);
|
||
|
||
CREATE TABLE admin_user_role (
|
||
id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID',
|
||
user_id VARCHAR(50) NOT NULL COMMENT '用户id',
|
||
role_id VARCHAR(255) NOT NULL COMMENT '角色id',
|
||
description VARCHAR(100) COMMENT '描述',
|
||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||
update_user VARCHAR(50) COMMENT '更新用户',
|
||
is_delete TINYINT(1) DEFAULT 0 COMMENT '是否删除,0-未删除,1-已删除'
|
||
);
|
||
|
||
CREATE TABLE admin_role_power (
|
||
id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID',
|
||
role_id VARCHAR(50) NOT NULL COMMENT '角色id',
|
||
power_id VARCHAR(255) NOT NULL COMMENT '权限id',
|
||
description VARCHAR(100) COMMENT '描述',
|
||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||
update_user VARCHAR(50) COMMENT '更新用户',
|
||
is_delete TINYINT(1) DEFAULT 0 COMMENT '是否删除,0-未删除,1-已删除'
|
||
);
|
||
|
||
```
|
||
|
||
### 基于方法的授权
|
||
#### 开启方法授权
|
||
在配置文件中添加如下注解,或者在启动类上添加都可以。
|
||
|
||
+ 默认如果开启了方法授权访问同时也配置了全局角色为`roles("ADMIN")`那么所有接口都是可以访问的。
|
||
|
||
```java
|
||
@EnableMethodSecurity
|
||
```
|
||
|
||
#### 给用户授予角色和权限
|
||
DBUserDetailsManager中的loadUserByUsername方法,添加`authorities("USER_ADD", "USER_UPDATE")`
|
||
|
||
+ `roles("ADMIN")`:与authorities不能同时使用
|
||
|
||
```java
|
||
@Override
|
||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
|
||
queryWrapper.eq("username", username);
|
||
User user = userMapper.selectOne(queryWrapper);
|
||
if (user == null) {
|
||
throw new UsernameNotFoundException(username);
|
||
} else {
|
||
// Collection<GrantedAuthority> authorities = new ArrayList<>();
|
||
// // 授予访问权限
|
||
// authorities.add(() -> "USER_LIST");
|
||
// authorities.add(() -> "USER_ADD");
|
||
// return new org.springframework.security.core.userdetails.User(
|
||
// user.getUsername(),
|
||
// user.getPassword(),
|
||
// user.getEnabled(),
|
||
// true, // 用户账号是否过期
|
||
// true, // 用户凭证是否过期
|
||
// true, // 用户是否未被锁定
|
||
// authorities); // 权限列表
|
||
|
||
return org.springframework.security.core.userdetails.User
|
||
.withUsername(user.getUsername())
|
||
.password(user.getPassword())
|
||
.disabled(!user.getEnabled())
|
||
.credentialsExpired(false)
|
||
.accountLocked(false)
|
||
// SecurityFilterChain中配置hasRole("ADMIN")来判断的
|
||
// .roles("ADMIN")// 与authorities不能同时使用
|
||
// 给用户授予角色和权限
|
||
.authorities("USER_ADD", "USER_UPDATE")
|
||
.build();
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 常用授权注解
|
||
+ `hasAnyRole('ADMIN')`当前角色权限为ADMIN同时访问用户也要为`authentication.name == 'admin'`
|
||
|
||
```java
|
||
@RestController
|
||
@RequestMapping("/user")
|
||
@Tag(name = "用户请求接口")
|
||
public class UserController {
|
||
@Autowired
|
||
private UserService userService;
|
||
|
||
@Operation(summary = "查询所有用户")
|
||
// @PreAuthorize("hasAnyRole('ADMIN')")
|
||
@PreAuthorize("hasAnyRole('ADMIN') and authentication.name == 'admin'")// 编写逻辑表达式
|
||
@GetMapping("/list")
|
||
public List<User> getList() {
|
||
return userService.list();
|
||
}
|
||
|
||
// 用户必须有 USER_ADD 权限 才能访问此方法
|
||
@Operation(summary = "添加用户")
|
||
@PreAuthorize("hasAuthority('USER_ADD')")
|
||
@PostMapping("/add")
|
||
public void addUser(@RequestBody User user) {
|
||
userService.addUserDetails(user);
|
||
}
|
||
}
|
||
```
|
||
|
||
> 如果当前用户不是admin用户访问不了。
|
||
>
|
||
|
||

|
||
|