spring security自定义表结构处理

Spring Security 的默认表结构(如 usersauthorities)可以通过自定义 UserDetailsServiceUserDetails 来完全适配你的数据库设计,无需修改框架本身。


核心扩展点

组件 职责 替换方式
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 的设计就是接口驱动,只要实现对应接口,完全可以脱离它的默认表结构,适配任意数据库设计。

相关推荐
fyakm1 小时前
Spring Cloud Eureka:服务注册与发现(附服务端和客户端代码)
spring·spring cloud·eureka
v***Y891 小时前
SpringCloud 整合 Dubbo
spring·spring cloud·dubbo
S***q3771 小时前
SpringCloud 整合 Dubbo
spring·spring cloud·dubbo
励ℳ1 小时前
【生信绘图】基因组大小与CDS数量关系的可视化
python·信息可视化
hhzz1 小时前
【回顾MySQL的SQL基础开发与应用】SQL分类与数据类型、视图、触发器以及存储过程与事件
数据库·sql·mysql
Jinkxs1 小时前
Java 跨域05-Spring 与 Dubbo 服务整合(协议转换)
java·spring·dubbo
Howie Zphile1 小时前
FRAPPE v16 +postgresql +insight+wiki安装
数据库·postgresql·frappe·全面预算
喵手1 小时前
Python爬虫实战:电商问答/FAQ 语料构建 - 去重、分句、清洗,做检索语料等!
爬虫·python·爬虫实战·faq·零基础python爬虫教学·电商问答·语料构建
Dxy12393102161 小时前
DataFrame条件筛选:从入门到实战的数据清洗利器
python·dataframe