一文讲懂如何基于 Spring Security 实现 RBAC 权限控制

刷到一个挺有意思的话题,结合自己之前的经验,整理了一下核心要点。

文章目录

什么是 RBAC

RBAC,全称 Role-Based Access Control,基于角色的访问控制。核心思路很简单:不直接给用户分配权限,而是引入"角色"作为中间层。

用户角色权限

举个例子:一家公司里,"实习生"能查看文档,"正式员工"能查看和编辑文档,"部门经理"能查看、编辑、删除文档。你入职的时候,HR 不会一条条给你勾权限,而是直接给你一个角色,权限就跟着来了。

这里面有几个关键关系:

  • 一个用户能够拥有多个角色
  • 一个角色能够分配给多个用户
  • 一个角色能够包含多个权限
  • 一个权限可以属于多个角色
还有一种模型叫角色继承——上层角色自动继承下层角色的所有权限。比如"经理"自动拥有"员工"的全部权限,再额外拥有管理权限。这在组织架构繁琐的系统中非常实用。

Spring Security 是什么

Spring Security 本质上是一个基于 Filter 的安全框架。请求进入应用时,会经过一系列过滤器链,每个过滤器各司其职:有的负责认证,有的负责授权,有的负责 CSRF 防护。

它解决两个核心问题:

  • 认证(Authentication):你是谁?验证用户名密码是否正确。
  • 授权(Authorization):你能干什么?检查你有没有权限访问某个接口。
用 RBAC 的思路来说,认证就是确认你的身份,授权就是根据你的角色判断你能不能进这扇门。

整体架构设计

要基于 Spring Security 实现 RBAC,需要这几个核心模块:

config/          → 安全配置、JWT 过滤器
controller/      → 接收请求的入口
service/         → 业务逻辑、用户详情加载
repository/      → 数据库访问
model/           → 用户实体、角色定义
utils/           → 密码加密、JWT 工具

下面按照请求的生命周期来讲,从注册到登录到鉴权,一步步走通。


第一步:定义用户模型和角色

@Data
@Entity
@Table(name = "wzxg_users", uniqueConstraints = @UniqueConstraint(columnNames = "username"))
public class WzxgUser {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String username;

    @Column(nullable = false)
    private String password;

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private WzxgRole role;

    @CreationTimestamp
    private LocalDateTime createdTime;

    @UpdateTimestamp
    private LocalDateTime updatedTime;

    public enum WzxgRole {
        USER, ADMIN
    }
}

@Enumerated(EnumType.STRING) 让角色以字符串形式存到数据库里,比如存的是 "ADMIN" 而不是 0,可读性好,也不容易出错。

数据访问层很简单,一个根据用户名查询的办法就够了:

public interface WzxgUserRepository extends JpaRepository {
    Optional findByUsername(String username);
}


第二步:密码加密

密码绝对不能明文存储。BCrypt 是目前主流的选择,它内置盐值机制,同一个密码每次加密结果都不同,能有效防御彩虹表攻击。

public class WzxgPasswordUtil {
    private static final BCryptPasswordEncoder wzxgEncoder = new BCryptPasswordEncoder();

    public static String encode(String rawPassword) {
        return wzxgEncoder.encode(rawPassword);
    }

    public static boolean matches(String rawPassword, String encodedPassword) {
        return wzxgEncoder.matches(rawPassword, encodedPassword);
    }
}

注册时调 encode(),登录时调 matches()


第三步:JWT 工具类

系统采用无状态认证,服务端不存 session,而是通过 JWT 令牌来传递身份信息。
(JWT相关可看Cookie、Session、Token、JWT开发流程详解

@Component
public class WzxgJwtUtils {
    private static final String WZXG_SECRET_KEY = "wzxg_jwt_secret_key_2024";
    private static final long WZXG_EXPIRATION = 86400000; // 24小时

    public String generateToken(String username) {
        return Jwts.builder()
                .setSubject(username)
                .setExpiration(new Date(System.currentTimeMillis() + WZXG_EXPIRATION))
                .signWith(SignatureAlgorithm.HS256, WZXG_SECRET_KEY)
                .compact();
    }

    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(WZXG_SECRET_KEY).parseClaimsJws(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    public String extractUsername(String token) {
        Claims wzxgClaims = Jwts.parser()
                .setSigningKey(WZXG_SECRET_KEY)
                .parseClaimsJws(token)
                .getBody();
        return wzxgClaims.getSubject();
    }
}

JWT 是一个自包含的令牌,里面可以塞用户名、角色、组织信息等。这样每次请求过来,解析 token 就能知道用户是谁、有什么权限,不用查数据库。


第四步:让 Spring Security 认识你的用户

Spring Security 有自己的一套用户模型 UserDetails,我们需要实现 UserDetailsService 接口,把数据库里的用户转换成它能搞懂的格式。

```
@Service
public class WzxgUserDetailsService implements UserDetailsService {

@Autowired
private WzxgUserRepository wzxgUserRepository;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
WzxgUser wzxgUser = wzxgUserRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("用户不存在"));

return new org.springframework.security.core.userdetails.User(
wzxgUser.getUsername(),
wzxgUser.getPassword(),
buildAuthorities(wzxgUser.getRole())
);
}

private Collection


暂时整理到这里。以上都是个人理解,可能有疏漏,欢迎指正。

评论 (0)

暂无评论