📝 添加【获取角色与权限】文档说明

This commit is contained in:
bunny 2025-07-14 20:34:16 +08:00
parent 5516fdddf9
commit 0b3c1f59e5
3 changed files with 189 additions and 24 deletions

View File

@ -1,5 +1,7 @@
# Spring Security 6 入门指南
![image-20250714202213150](./images/image-20250714202213150.png)
## 基本配置
### 添加依赖
@ -403,6 +405,24 @@ public UserDetails getCurrentUserDetail() {
### 角色与权限配置
> [!IMPORTANT]
>
> 1. **角色与权限的区别**
> - `hasRole()`会自动添加"ROLE_"前缀
> - `hasAuthority()`直接使用指定的权限字符串
> 2. **匹配顺序**
> - Spring Security会按照配置的顺序进行匹配
> - 更具体的路径应该放在前面通用规则如anyRequest放在最后
> 3. **方法选择建议**
> - `hasRole()`/`hasAnyRole()`:适合基于角色的访问控制
> - `hasAuthority()`/`hasAnyAuthority()`:适合更细粒度的权限控制
> - `authenticated()`:只需认证通过,不检查具体角色/权限
> - `permitAll()`:完全开放访问
> 4. **最佳实践**
> - 对于REST API通常使用`authenticated()`配合方法级权限控制
> - 静态资源应明确配置`permitAll()`
> - 生产环境不建议使用`anyRequest().permitAll()`
#### 1. 基于角色的URL访问控制
##### 单角色配置
@ -517,27 +537,26 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
}
```
### 重要说明
### 基于方法的授权
1. **角色与权限的区别**
- `hasRole()`会自动添加"ROLE_"前缀
- `hasAuthority()`直接使用指定的权限字符串
2. **匹配顺序**
- Spring Security会按照配置的顺序进行匹配
- 更具体的路径应该放在前面通用规则如anyRequest放在最后
3. **方法选择建议**
- `hasRole()`/`hasAnyRole()`:适合基于角色的访问控制
- `hasAuthority()`/`hasAnyAuthority()`:适合更细粒度的权限控制
- `authenticated()`:只需认证通过,不检查具体角色/权限
- `permitAll()`:完全开放访问
4. **最佳实践**
- 对于REST API通常使用`authenticated()`配合方法级权限控制
- 静态资源应明确配置`permitAll()`
- 生产环境不建议使用`anyRequest().permitAll()`
> [!NOTE]
>
> 通过在任何 `@Configuration` 类上添加 `@EnableMethodSecurity` 注解。
>
> Spring Boot Starter Security 默认情况下不会激活方法级别的授权。
#### 提供的注解
1. @PreAuthorize
2. @PostAuthorize
3. @PreFilter
4. @PostFilter
## 关于UserDetailsService的深入解析
### 1. UserDetailsService的核心作用
### 简单阐述
#### 1. UserDetailsService的核心作用
`UserDetailsService`是Spring Security的核心接口负责提供用户认证数据。它只有一个核心方法
@ -547,7 +566,7 @@ UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
当用户尝试登录时Spring Security会自动调用这个方法来获取用户详情。
### 2. 为什么不需要手动校验密码?
#### 2. 为什么不需要手动校验密码?
在标准的表单登录流程中Spring Security的认证流程会自动处理密码校验这是因为
@ -568,7 +587,7 @@ UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
D --> E[认证成功/失败]
```
### 3. 完整的安全配置示例
#### 3. 完整的安全配置示例
```java
@Configuration
@ -609,7 +628,7 @@ public class SecurityConfig {
}
```
### 4. 关键注意事项
#### 4. 关键注意事项
1. **必须提供PasswordEncoder**
如果没有配置,会出现`There is no PasswordEncoder mapped`错误
@ -635,7 +654,7 @@ public class SecurityConfig {
- 存在`PasswordEncoder` bean
- 没有显式配置`AuthenticationManager`
### 5. 扩展场景
#### 5. 扩展场景
如果需要自定义认证逻辑(如增加验证码校验),可以:
@ -671,7 +690,7 @@ public class CustomAuthProvider implements AuthenticationProvider {
http.authenticationProvider(customAuthProvider);
```
### 总结对比表
#### 总结对比表
| 场景 | 需要手动处理 | 自动处理 |
| ------------ | ---------------------------------- | ----------------------- |
@ -680,4 +699,150 @@ http.authenticationProvider(customAuthProvider);
| 账号状态检查 | 通过`UserDetails`返回的状态 | ✅ |
| 权限加载 | 通过`UserDetails.getAuthorities()` | ✅ |
这样设计的好处是:开发者只需关注业务数据获取(用户信息查询),安全相关的校验逻辑由框架统一处理,既保证了安全性又减少了重复代码。
这样设计的好处是:开发者只需关注业务数据获取(用户信息查询),安全相关的校验逻辑由框架统一处理,既保证了安全性又减少了重复代码。
### 获取角色与权限
#### 1. 角色信息处理
在`UserDetailsService`实现中获取并设置用户角色:
```java
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1. 查询用户基本信息
UserEntity userEntity = userMapper.selectByUsername(username);
if (userEntity == null) {
throw new UsernameNotFoundException("用户不存在");
}
// 2. 获取角色信息自动添加ROLE_前缀
String[] roles = findUserRolesByUserId(userEntity.getId());
// 3. 获取权限信息
List<String> permissions = findPermissionsByUserId(userEntity.getId());
return User.builder()
.username(userEntity.getUsername())
.password(userEntity.getPassword())
// 角色会自动添加ROLE_前缀
.roles(roles)
// 直接作为权限字符串使用
.authorities(permissions.toArray(new String[0]))
.build();
}
```
**关键说明**
- `roles()`方法会自动为角色添加`ROLE_`前缀(如`ADMIN`会变成`ROLE_ADMIN`
- 角色和权限在Spring Security中是不同概念角色本质是带有特殊前缀的权限
#### 2. 权限信息处理(两种方式)
**方式一:直接使用字符串**
```java
List<String> permissions = findPermissionsByUserId(userId);
return User.withUsername(username)
.authorities(permissions.toArray(new String[0]))
// ...
.build();
```
**方式二转换为SimpleGrantedAuthority**
```java
List<GrantedAuthority> authorities = permissions.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
return User.withUsername(username)
.authorities(authorities)
// ...
.build();
```
**权限实现类对比**
| 实现类 | 适用场景 | 特点 |
| ---------------------------- | ------------- | ---------------- |
| `SimpleGrantedAuthority` | 普通权限/角色 | 最常用实现 |
| `SwitchUserGrantedAuthority` | 用户切换场景 | 包含原始用户信息 |
| `JaasGrantedAuthority` | JAAS集成 | 用于Java认证服务 |
#### 3. Mapper配置优化
**角色查询Mapper**
```xml
<select id="selectRolesByUserId" resultType="com.example.entity.Role">
SELECT r.*
FROM t_role r
JOIN t_user_role ur ON r.id = ur.role_id
WHERE ur.user_id = #{userId}
</select>
```
**权限查询Mapper**
```xml
<select id="selectPermissionsByUserId" resultType="java.lang.String">
SELECT p.permission_code
FROM t_permission p
JOIN t_role_permission rp ON p.id = rp.permission_id
JOIN t_user_role ur ON rp.role_id = ur.role_id
WHERE ur.user_id = #{userId}
</select>
```
#### 4. 重要注意事项
1. **角色与权限的存储建议**
- 角色建议存储为`ADMIN`、`USER`等形式
- 权限建议存储为`user:read`、`order:delete`等具体操作
2. **性能优化**
```java
// 使用一次查询获取所有权限信息避免N+1查询
@Select("SELECT p.permission_code FROM ... WHERE ur.user_id = #{userId}")
List<String> selectAllUserPermissions(@Param("userId") Long userId);
```
3. **Spring Security的默认行为**
- 如果同时配置`roles()`和`authorities()`,后者会覆盖前者
- 推荐统一使用`authorities()`方法处理所有授权信息
4. **最佳实践示例**
```java
List<GrantedAuthority> authorities = new ArrayList<>();
// 添加角色手动添加ROLE_前缀
roles.forEach(role ->
authorities.add(new SimpleGrantedAuthority("ROLE_" + role)));
// 添加权限
authorities.addAll(permissions.stream()
.map(SimpleGrantedAuthority::new)
.toList());
return User.builder()
// ...
.authorities(authorities)
.build();
```
#### 5. 完整流程示意图
```mermaid
graph TD
A[登录请求] --> B[调用UserDetailsService]
B --> C[查询用户基本信息]
C --> D[查询用户角色]
C --> E[查询用户权限]
D --> F[构建UserDetails对象]
E --> F
F --> G[返回认证结果]
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -43,7 +43,7 @@ public class DbUserDetailService implements UserDetailsService {
String[] authorities = permissionsByUserId.toArray(String[]::new);
// 也可以转成下面的形式
// authorities = permissionsByUserId.stream()
// List<String> authorities = permissionsByUserId.stream()
// .map(SimpleGrantedAuthority::new)
// .toList();