Spring Security 的默认表结构(如 users、authorities)可以通过自定义 UserDetailsService 和 UserDetails 来完全适配你的数据库设计,无需修改框架本身。
核心扩展点
| 组件 | 职责 | 替换方式 |
|---|---|---|
UserDetailsService |
加载用户基本信息 | 实现接口,自定义 SQL |
UserDetails |
封装用户认证信息 | 自定义实现类 |
GrantedAuthority |
用户权限标识 | 自定义转换逻辑 |
PasswordEncoder |
密码加密验证 | 选择或自定义算法 |
实战:完全自定义表结构
假设你的表结构如下:
sql
-- 用户表(与 Spring Security 默认不同)
CREATE TABLE sys_user (
user_id BIGINT PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL, -- 注意字段名不同
status TINYINT DEFAULT 1, -- 0-禁用 1-启用 2-锁定
email VARCHAR(100),
phone VARCHAR(20),
dept_id BIGINT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 角色表
CREATE TABLE sys_role (
role_id BIGINT PRIMARY KEY,
role_code VARCHAR(50) UNIQUE NOT NULL, -- 如:ADMIN, MANAGER
role_name VARCHAR(100)
);
-- 用户角色关联
CREATE TABLE sys_user_role (
user_id BIGINT,
role_id BIGINT,
PRIMARY KEY (user_id, role_id)
);
-- 权限表(资源+操作)
CREATE TABLE sys_privilege (
privilege_id BIGINT PRIMARY KEY,
resource_type VARCHAR(50), -- MENU, BUTTON, API
resource_url VARCHAR(255), -- /api/users/**
action VARCHAR(50) -- READ, WRITE, DELETE
);
-- 角色权限关联
CREATE TABLE sys_role_privilege (
role_id BIGINT,
privilege_id BIGINT,
PRIMARY KEY (role_id, privilege_id)
);
步骤 1:自定义 UserDetails
java
@Data
public class CustomUserDetails implements UserDetails {
private Long userId;
private String username;
private String passwordHash; // 对应你的字段
private Integer status;
private String email;
private String phone;
private Long deptId;
private LocalDateTime createdAt;
// 权限列表(可以是角色或具体操作权限)
private List<GrantedAuthority> authorities;
@Override
public String getPassword() {
return this.passwordHash; // 映射到正确的字段
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isEnabled() {
return this.status != null && this.status == 1;
}
@Override
public boolean isAccountNonLocked() {
return this.status == null || this.status != 2; // 2表示锁定
}
@Override
public boolean isAccountNonExpired() {
return true; // 如需支持过期,需扩展字段
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
// 业务扩展方法
public boolean hasRole(String roleCode) {
return authorities.stream()
.anyMatch(a -> a.getAuthority().equals("ROLE_" + roleCode));
}
}
步骤 2:自定义 UserDetailsService
java
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleMapper roleMapper;
@Override
public CustomUserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1. 查询用户基本信息(你的表结构)
SysUser sysUser = userMapper.selectByUsername(username);
if (sysUser == null) {
throw new UsernameNotFoundException("用户不存在: " + username);
}
// 2. 查询用户权限(支持多种权限模型)
List<GrantedAuthority> authorities = loadAuthorities(sysUser.getUserId());
// 3. 组装自定义 UserDetails
CustomUserDetails userDetails = new CustomUserDetails();
BeanUtils.copyProperties(sysUser, userDetails);
userDetails.setAuthorities(authorities);
return userDetails;
}
private List<GrantedAuthority> loadAuthorities(Long userId) {
List<GrantedAuthority> authorities = new ArrayList<>();
// 方案A:加载角色(如 ROLE_ADMIN)
List<String> roleCodes = roleMapper.selectRoleCodesByUserId(userId);
roleCodes.forEach(role ->
authorities.add(new SimpleGrantedAuthority("ROLE_" + role))
);
// 方案B:加载具体权限(如 user:read, user:write)
List<String> privileges = roleMapper.selectPrivilegesByUserId(userId);
privileges.forEach(privilege ->
authorities.add(new SimpleGrantedAuthority(privilege))
);
// 方案C:加载数据范围权限(自定义实现)
List<DataScope> dataScopes = roleMapper.selectDataScopesByUserId(userId);
// 可以存入 ThreadLocal 或自定义 Authentication 对象
return authorities;
}
}
步骤 3:配置 Spring Security
java
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true) // 启用方法级权限
public class SecurityConfig {
@Autowired
private CustomUserDetailsService userDetailsService;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
// 使用自定义 UserDetailsService
.userDetailsService(userDetailsService)
// JWT 认证过滤器(如果使用 Token)
.addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class)
// 授权规则
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN") // 对应 ROLE_ADMIN
.requestMatchers(HttpMethod.DELETE, "/api/**").hasAuthority("resource:delete")
.anyRequest().authenticated()
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
// 匹配你的密码加密方式
return new BCryptPasswordEncoder(); // 或你的自定义实现
}
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
}
步骤 4:自定义 AuthenticationProvider(高级)
如果需要完全控制认证逻辑(如多因素认证、短信登录):
java
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Autowired
private CustomUserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
// 1. 加载用户(走你的自定义逻辑)
CustomUserDetails user = userDetailsService.loadUserByUsername(username);
// 2. 密码验证
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BadCredentialsException("密码错误");
}
// 3. 额外校验(如账户状态、登录IP限制等)
if (!user.isEnabled()) {
throw new DisabledException("账户已禁用");
}
// 4. 可以在这里扩展:短信验证码校验、图形验证码等
// 5. 返回认证成功的 Token
return new UsernamePasswordAuthenticationToken(
user, // principal 使用你的 CustomUserDetails
null, // 擦除凭证
user.getAuthorities()
);
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
配置使用:
java
@Bean
public AuthenticationManager authManager(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder builder =
http.getSharedObject(AuthenticationManagerBuilder.class);
builder.authenticationProvider(customAuthenticationProvider);
return builder.build();
}
步骤 5:方法级权限适配
如果你的权限模型是"资源+操作"而非简单角色:
java
@RestController
@RequestMapping("/api/users")
public class UserController {
// 使用自定义权限表达式
@PreAuthorize("hasAuthority('user:read')")
@GetMapping
public List<User> list() {
return userService.list();
}
// 复杂权限判断(调用 CustomUserDetails 的方法)
@PreAuthorize("@securityService.hasPermission(authentication, 'user:write')")
@PostMapping
public User create(@RequestBody User user) {
return userService.save(user);
}
// 数据范围权限(只能看本部门数据)
@PreAuthorize("hasRole('MANAGER') and @dataScopeService.checkDept(#userId)")
@GetMapping("/{userId}")
public User get(@PathVariable Long userId) {
return userService.get(userId);
}
}
自定义 PermissionEvaluator:
java
@Component("securityService")
public class CustomPermissionEvaluator implements PermissionEvaluator {
@Override
public boolean hasPermission(Authentication auth, Object targetDomain, Object permission) {
CustomUserDetails user = (CustomUserDetails) auth.getPrincipal();
// 从数据库实时查询,或从 userDetails 缓存中获取
return user.getAuthorities().stream()
.anyMatch(a -> a.getAuthority().equals(permission.toString()));
}
@Override
public boolean hasPermission(Authentication auth, Serializable targetId,
String targetType, Object permission) {
// 基于目标ID的权限判断(如:只能修改自己的数据)
return false;
}
}
多认证方式扩展(如短信登录)
java
// 自定义认证 Token
public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {
private final String phone;
private String smsCode;
// 构造方法...
}
// 自定义 Provider
@Component
public class SmsCodeAuthenticationProvider implements AuthenticationProvider {
@Autowired
private UserMapper userMapper;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public Authentication authenticate(Authentication authentication) {
String phone = authentication.getPrincipal().toString();
String code = authentication.getCredentials().toString();
// 1. 校验验证码
String cachedCode = redisTemplate.opsForValue().get("sms:" + phone);
if (!code.equals(cachedCode)) {
throw new BadCredentialsException("验证码错误");
}
// 2. 根据手机号查用户(你的表结构)
SysUser user = userMapper.selectByPhone(phone);
if (user == null) {
throw new UsernameNotFoundException("手机号未注册");
}
// 3. 加载权限
CustomUserDetails userDetails = loadUserDetails(user);
return new SmsCodeAuthenticationToken(userDetails, null, userDetails.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);
}
}
总结:适配不同表结构的关键
| 你的表结构特点 | 对应扩展点 |
|---|---|
字段名不同(如 password_hash) |
自定义 UserDetails.getPassword() 映射 |
| 用户状态逻辑复杂 | 自定义 UserDetails 的 4 个布尔方法 |
| 多表关联(用户-角色-权限) | 在 UserDetailsService 中自定义 SQL 组装 |
| 非用户名登录(手机号/邮箱) | 自定义 AuthenticationFilter 解析登录标识 |
| 多因素认证 | 自定义 AuthenticationProvider |
| 数据范围权限 | 扩展 UserDetails 字段 + 自定义 PermissionEvaluator |
Spring Security 的设计就是接口驱动,只要实现对应接口,完全可以脱离它的默认表结构,适配任意数据库设计。