✨ 添加用户的角色信息
This commit is contained in:
parent
84a053fdb6
commit
5516fdddf9
|
@ -29,6 +29,63 @@ public class SecurityWebConfiguration {
|
||||||
- 使用默认登录页:`.formLogin(Customizer.withDefaults())`
|
- 使用默认登录页:`.formLogin(Customizer.withDefaults())`
|
||||||
- 禁用表单登录:`.formLogin(AbstractHttpConfigurer::disable)`
|
- 禁用表单登录:`.formLogin(AbstractHttpConfigurer::disable)`
|
||||||
|
|
||||||
|
#### 配置示例
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableMethodSecurity
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SecurityWebConfiguration {
|
||||||
|
|
||||||
|
private final DbUserDetailService dbUserDetailService;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
|
||||||
|
http.authorizeHttpRequests(authorizeRequests ->
|
||||||
|
// 访问路径为 /api 时需要进行认证
|
||||||
|
authorizeRequests
|
||||||
|
// 只认证 /api/** 下的所有接口
|
||||||
|
.requestMatchers("/api/**").authenticated()
|
||||||
|
// 其余请求都放行
|
||||||
|
.anyRequest().permitAll()
|
||||||
|
)
|
||||||
|
.formLogin(loginPage -> loginPage
|
||||||
|
// 自定义登录页路径
|
||||||
|
.loginPage("/login-page")
|
||||||
|
// 处理登录的URL(默认就是/login)
|
||||||
|
.loginProcessingUrl("/login")
|
||||||
|
// 登录成功跳转
|
||||||
|
.defaultSuccessUrl("/")
|
||||||
|
// 登录失败跳转
|
||||||
|
.failureUrl("/login-page?error=true")
|
||||||
|
.permitAll()
|
||||||
|
)
|
||||||
|
// 使用默认的登录
|
||||||
|
// .formLogin(Customizer.withDefaults())
|
||||||
|
// 禁用表单登录
|
||||||
|
// .formLogin(AbstractHttpConfigurer::disable)
|
||||||
|
.logout(logout -> logout
|
||||||
|
.logoutSuccessUrl("/login-page?logout=true")
|
||||||
|
.permitAll()
|
||||||
|
)
|
||||||
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
|
.exceptionHandling(configurer -> configurer
|
||||||
|
// 自定无权访问返回内容
|
||||||
|
.accessDeniedHandler(new SecurityAccessDeniedHandler())
|
||||||
|
// 自定义未授权返回内容
|
||||||
|
.authenticationEntryPoint(new SecurityAuthenticationEntryPoint())
|
||||||
|
)
|
||||||
|
|
||||||
|
.userDetailsService(dbUserDetailService)
|
||||||
|
;
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## 认证与授权配置
|
## 认证与授权配置
|
||||||
|
|
||||||
### URL访问控制
|
### URL访问控制
|
||||||
|
@ -476,4 +533,151 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
4. **最佳实践**:
|
4. **最佳实践**:
|
||||||
- 对于REST API,通常使用`authenticated()`配合方法级权限控制
|
- 对于REST API,通常使用`authenticated()`配合方法级权限控制
|
||||||
- 静态资源应明确配置`permitAll()`
|
- 静态资源应明确配置`permitAll()`
|
||||||
- 生产环境不建议使用`anyRequest().permitAll()`
|
- 生产环境不建议使用`anyRequest().permitAll()`
|
||||||
|
|
||||||
|
## 关于UserDetailsService的深入解析
|
||||||
|
|
||||||
|
### 1. UserDetailsService的核心作用
|
||||||
|
|
||||||
|
`UserDetailsService`是Spring Security的核心接口,负责提供用户认证数据。它只有一个核心方法:
|
||||||
|
|
||||||
|
```java
|
||||||
|
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
|
||||||
|
```
|
||||||
|
|
||||||
|
当用户尝试登录时,Spring Security会自动调用这个方法来获取用户详情。
|
||||||
|
|
||||||
|
### 2. 为什么不需要手动校验密码?
|
||||||
|
|
||||||
|
在标准的表单登录流程中,Spring Security的认证流程会自动处理密码校验,这是因为:
|
||||||
|
|
||||||
|
1. **自动集成密码编码器**:
|
||||||
|
Spring Security会自动使用配置的`PasswordEncoder`来比对:
|
||||||
|
|
||||||
|
- 用户提交的明文密码
|
||||||
|
- 数据库中存储的加密密码(通过`UserDetails`返回)
|
||||||
|
|
||||||
|
2. **认证流程内部处理**:
|
||||||
|
认证管理器(`AuthenticationManager`)会自动处理以下流程:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[用户提交凭证] --> B[调用UserDetailsService]
|
||||||
|
B --> C[获取UserDetails]
|
||||||
|
C --> D[PasswordEncoder比对密码]
|
||||||
|
D --> E[认证成功/失败]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 完整的安全配置示例
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
private final DbUserDetailService dbUserDetailService;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.authorizeHttpRequests(auth -> auth
|
||||||
|
.requestMatchers("/api/**").authenticated()
|
||||||
|
.anyRequest().permitAll()
|
||||||
|
)
|
||||||
|
.formLogin(form -> form
|
||||||
|
.loginProcessingUrl("/login")
|
||||||
|
.permitAll()
|
||||||
|
)
|
||||||
|
// 即使不显式设置也会自动生效
|
||||||
|
.userDetailsService(dbUserDetailService)
|
||||||
|
// 必须配置PasswordEncoder
|
||||||
|
.authenticationManager(authenticationManager(http));
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
|
||||||
|
return http.getSharedObject(AuthenticationManagerBuilder.class)
|
||||||
|
.userDetailsService(dbUserDetailService)
|
||||||
|
.passwordEncoder(passwordEncoder)
|
||||||
|
.and()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 关键注意事项
|
||||||
|
|
||||||
|
1. **必须提供PasswordEncoder**:
|
||||||
|
如果没有配置,会出现`There is no PasswordEncoder mapped`错误
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Bean
|
||||||
|
PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **UserDetails实现要求**:
|
||||||
|
你的自定义`UserDetails`实现必须包含:
|
||||||
|
|
||||||
|
- 正确的用户名
|
||||||
|
- 加密后的密码
|
||||||
|
- 账号状态信息(是否过期/锁定等)
|
||||||
|
|
||||||
|
3. **自动发现机制**:
|
||||||
|
当以下条件满足时,Spring Boot会自动配置:
|
||||||
|
|
||||||
|
- 容器中存在唯一的`UserDetailsService`实现
|
||||||
|
- 存在`PasswordEncoder` bean
|
||||||
|
- 没有显式配置`AuthenticationManager`
|
||||||
|
|
||||||
|
### 5. 扩展场景
|
||||||
|
|
||||||
|
如果需要自定义认证逻辑(如增加验证码校验),可以:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class CustomAuthProvider implements AuthenticationProvider {
|
||||||
|
|
||||||
|
private final UserDetailsService userDetailsService;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Authentication authenticate(Authentication auth) {
|
||||||
|
// 自定义逻辑
|
||||||
|
UserDetails user = userDetailsService.loadUserByUsername(auth.getName());
|
||||||
|
// 手动密码比对
|
||||||
|
if (!passwordEncoder.matches(auth.getCredentials().toString(), user.getPassword())) {
|
||||||
|
throw new BadCredentialsException("密码错误");
|
||||||
|
}
|
||||||
|
return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(Class<?> authentication) {
|
||||||
|
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
然后在配置中注册:
|
||||||
|
|
||||||
|
```java
|
||||||
|
http.authenticationProvider(customAuthProvider);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 总结对比表
|
||||||
|
|
||||||
|
| 场景 | 需要手动处理 | 自动处理 |
|
||||||
|
| ------------ | ---------------------------------- | ----------------------- |
|
||||||
|
| 用户查找 | 实现`loadUserByUsername()` | ✅ |
|
||||||
|
| 密码比对 | ❌ | 由`PasswordEncoder`处理 |
|
||||||
|
| 账号状态检查 | 通过`UserDetails`返回的状态 | ✅ |
|
||||||
|
| 权限加载 | 通过`UserDetails.getAuthorities()` | ✅ |
|
||||||
|
|
||||||
|
这样设计的好处是:开发者只需关注业务数据获取(用户信息查询),安全相关的校验逻辑由框架统一处理,既保证了安全性又减少了重复代码。
|
|
@ -29,6 +29,7 @@ import java.util.List;
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/role")
|
@RequestMapping("/api/role")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
|
||||||
public class RoleController {
|
public class RoleController {
|
||||||
|
|
||||||
private final RoleService roleService;
|
private final RoleService roleService;
|
||||||
|
|
|
@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.spring.step2.domain.dto.user.UserDto;
|
import com.spring.step2.domain.dto.user.UserDto;
|
||||||
import com.spring.step2.domain.entity.PermissionEntity;
|
import com.spring.step2.domain.entity.PermissionEntity;
|
||||||
|
import com.spring.step2.domain.entity.RoleEntity;
|
||||||
import com.spring.step2.domain.entity.UserEntity;
|
import com.spring.step2.domain.entity.UserEntity;
|
||||||
import com.spring.step2.domain.vo.UserVo;
|
import com.spring.step2.domain.vo.UserVo;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
@ -47,4 +48,12 @@ public interface UserMapper extends BaseMapper<UserEntity> {
|
||||||
* @return 用户 {@link UserEntity}
|
* @return 用户 {@link UserEntity}
|
||||||
*/
|
*/
|
||||||
UserEntity selectByUsername(String username);
|
UserEntity selectByUsername(String username);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户id查找该用户的角色内容
|
||||||
|
*
|
||||||
|
* @param userId 用户id
|
||||||
|
* @return 当前用户的角色信息
|
||||||
|
*/
|
||||||
|
List<RoleEntity> selectRolesByUserId(Long userId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package com.spring.step2.security.config;
|
||||||
|
|
||||||
import com.spring.step2.security.handler.SecurityAccessDeniedHandler;
|
import com.spring.step2.security.handler.SecurityAccessDeniedHandler;
|
||||||
import com.spring.step2.security.handler.SecurityAuthenticationEntryPoint;
|
import com.spring.step2.security.handler.SecurityAuthenticationEntryPoint;
|
||||||
import com.spring.step2.security.service.DbUserDetailService;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
@ -18,8 +17,6 @@ import org.springframework.security.web.SecurityFilterChain;
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class SecurityWebConfiguration {
|
public class SecurityWebConfiguration {
|
||||||
|
|
||||||
private final DbUserDetailService dbUserDetailService;
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
|
||||||
|
@ -57,7 +54,6 @@ public class SecurityWebConfiguration {
|
||||||
// 自定义未授权返回内容
|
// 自定义未授权返回内容
|
||||||
.authenticationEntryPoint(new SecurityAuthenticationEntryPoint())
|
.authenticationEntryPoint(new SecurityAuthenticationEntryPoint())
|
||||||
)
|
)
|
||||||
.userDetailsService(dbUserDetailService)
|
|
||||||
;
|
;
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
|
|
|
@ -2,20 +2,22 @@ package com.spring.step2.security.service;
|
||||||
|
|
||||||
import com.baomidou.dynamic.datasource.annotation.DS;
|
import com.baomidou.dynamic.datasource.annotation.DS;
|
||||||
import com.spring.step2.domain.entity.PermissionEntity;
|
import com.spring.step2.domain.entity.PermissionEntity;
|
||||||
|
import com.spring.step2.domain.entity.RoleEntity;
|
||||||
import com.spring.step2.domain.entity.UserEntity;
|
import com.spring.step2.domain.entity.UserEntity;
|
||||||
import com.spring.step2.mapper.UserMapper;
|
import com.spring.step2.mapper.UserMapper;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|
||||||
import org.springframework.security.core.userdetails.User;
|
import org.springframework.security.core.userdetails.User;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@DS("testJwt")
|
@DS("testJwt")
|
||||||
@Service
|
@Service
|
||||||
|
@Transactional
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class DbUserDetailService implements UserDetailsService {
|
public class DbUserDetailService implements UserDetailsService {
|
||||||
|
|
||||||
|
@ -31,18 +33,47 @@ public class DbUserDetailService implements UserDetailsService {
|
||||||
throw new UsernameNotFoundException("用户不存在");
|
throw new UsernameNotFoundException("用户不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Long userId = userEntity.getId();
|
||||||
|
|
||||||
|
// 设置用户角色
|
||||||
|
String[] roles = findUserRolesByUserId(userId);
|
||||||
|
|
||||||
// 设置用户权限
|
// 设置用户权限
|
||||||
List<SimpleGrantedAuthority> authorities = findPermissionByUserId(userEntity.getId()).stream()
|
List<String> permissionsByUserId = findPermissionByUserId(userId);
|
||||||
.map(SimpleGrantedAuthority::new)
|
String[] authorities = permissionsByUserId.toArray(String[]::new);
|
||||||
.toList();
|
|
||||||
|
// 也可以转成下面的形式
|
||||||
|
// authorities = permissionsByUserId.stream()
|
||||||
|
// .map(SimpleGrantedAuthority::new)
|
||||||
|
// .toList();
|
||||||
|
|
||||||
return User.builder()
|
return User.builder()
|
||||||
.username(userEntity.getUsername())
|
.username(userEntity.getUsername())
|
||||||
.password(userEntity.getPassword())
|
.password(userEntity.getPassword())
|
||||||
|
// 设置用户角色
|
||||||
|
.roles(roles)
|
||||||
|
// 设置用户权限
|
||||||
.authorities(authorities)
|
.authorities(authorities)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户id查找该用户的角色内容
|
||||||
|
*
|
||||||
|
* @param userId 用户id
|
||||||
|
* @return 当前用户的角色信息
|
||||||
|
*/
|
||||||
|
public String[] findUserRolesByUserId(Long userId) {
|
||||||
|
List<RoleEntity> roleList = userMapper.selectRolesByUserId(userId);
|
||||||
|
return roleList.stream().map(RoleEntity::getRoleCode).toArray(String[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户id查找该用户的权限内容
|
||||||
|
*
|
||||||
|
* @param userId 用户id
|
||||||
|
* @return 当前用户的权限信息
|
||||||
|
*/
|
||||||
public List<String> findPermissionByUserId(Long userId) {
|
public List<String> findPermissionByUserId(Long userId) {
|
||||||
List<PermissionEntity> permissionList = userMapper.selectPermissionByUserId(userId);
|
List<PermissionEntity> permissionList = userMapper.selectPermissionByUserId(userId);
|
||||||
return permissionList.stream().map(PermissionEntity::getPermissionCode).toList();
|
return permissionList.stream().map(PermissionEntity::getPermissionCode).toList();
|
||||||
|
|
|
@ -57,4 +57,11 @@
|
||||||
</where>
|
</where>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<!-- 根据用户id查找该用户的角色内容 -->
|
||||||
|
<select id="selectRolesByUserId" resultType="com.spring.step2.domain.entity.RoleEntity">
|
||||||
|
select *
|
||||||
|
from t_user_role tur
|
||||||
|
join t_role tr on tur.role_id = tr.id
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|
Loading…
Reference in New Issue