什么是 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<wzxguser, long> {
optional<wzxguser> 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 令牌来传递身份信息。
@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<? extends grantedauthority> buildauthorities(wzxguser.wzxgrole role) {
return collections.singletonlist(new simplegrantedauthority("role_" + role.name()));
}
}
注意 buildauthorities 方法里的 "role_" 前缀,这是 spring security 的约定。角色 admin 会变成 role_admin,这样后面配置 hasrole("admin") 时框架才能正确匹配。
第五步:jwt 认证过滤器
这是整个流程的关键——每个请求进来时,过滤器从请求头里取出 jwt,验证有效性,然后把用户信息塞进 spring security 的上下文。
@component
public class wzxgjwtauthfilter extends onceperrequestfilter {
@autowired
private userdetailsservice wzxguserdetailsservice;
@override
protected void dofilterinternal(httpservletrequest request,
httpservletresponse response,
filterchain filterchain)
throws servletexception, ioexception {
try {
string wzxgtoken = gettoken(request);//从请求头获取jwt token
if (wzxgtoken != null) {
string username = getusername(wzxgtoken);
userdetails wzxguserdetails = wzxguserdetailsservice.loaduserbyusername(username);//根据用户名加载用户详细信息
usernamepasswordauthenticationtoken wzxgauth =
new usernamepasswordauthenticationtoken(
wzxguserdetails, null, wzxguserdetails.getauthorities());
wzxgauth.setdetails(new webauthenticationdetailssource().builddetails(request));
securitycontextholder.getcontext().setauthentication(wzxgauth);
}
} catch (exception e) {
}
filterchain.dofilter(request, response);
}
private string gettoken(httpservletrequest request) {
string wzxgbearer = request.getheader("authorization");
if (wzxgbearer != null && wzxgbearer.startswith("bearer ")) {
return wzxgbearer.substring(7);
}
return null;
}
private string getusername(string token) {
try {
claims claims = extractclaimsignoreexpiration(token);
return claims != null ? claims.getsubject() : null;
} catch (exception e) {
return null;
}
}
}
继承 onceperrequestfilter 保证每个请求只过滤一次。如果 token 无效或不存在,不会报错,只是不设置认证信息,后续的权限检查自然会拦住未认证的请求。
第六步:安全配置——定义规则
最后把所有东西串起来,告诉 spring security:哪些接口谁能访问。
@configuration
@enablewebsecurity
public class wzxgsecurityconfig {
@autowired
private wzxgjwtauthfilter wzxgjwtauthfilter;
@bean
public securityfilterchain wzxgfilterchain(httpsecurity http) throws exception {
http.csrf(csrf -> csrf.disable())
.authorizehttprequests(auth -> auth
// 登录注册不需要认证
.requestmatchers("/api/v1/auth/register", "/api/v1/auth/login").permitall()
// 管理接口只有 admin 能访问
.requestmatchers("/api/v1/manage/**").hasrole("admin")
// 普通业务接口,user 和 admin 都能访问
.requestmatchers("/api/v1/workspace/**").hasanyrole("user", "admin")
// 其余接口都需要认证
.anyrequest().authenticated()
)
.sessionmanagement(session ->
session.sessioncreationpolicy(sessioncreationpolicy.stateless)
)
// 把 jwt 过滤器插到 usernamepasswordauthenticationfilter 前面
.addfilterbefore(wzxgjwtauthfilter, usernamepasswordauthenticationfilter.class);
return http.build();
}
}
csrf.disable():因为用的是 jwt 无状态认证,不需要 csrf 防护sessioncreationpolicy.stateless:不创建 session,完全靠 tokenaddfilterbefore:确保 jwt 过滤器在 spring security 默认的认证过滤器之前执行
完整请求流程
把上面的模块串起来,一个请求的完整生命周期是这样的:
1. 用户注册 → 密码用 bcrypt 加密 → 分配默认角色 user → 存入数据库 2. 用户登录 → 验证用户名密码 → 生成 jwt token → 返回给客户端 3. 客户端请求业务接口 → 请求头带上 authorization: bearer <token> 4. wzxgjwtauthfilter 拦截请求 → 提取 token → 验证签名和有效期 → 解析出用户名 → 加载用户信息和角色 → 写入 securitycontext 5. spring security 根据 wzxgsecurityconfig 中的规则 → 检查当前用户的角色是否匹配目标接口的权限要求 → 通过则放行,否则返回 403
以上就是基于spring security实现rbac权限控制的完整步骤的详细内容,更多关于spring security rbac权限控制的资料请关注代码网其它相关文章!
发表评论