若依(RuoYi)框架采用基于Token的认证授权机制,结合Spring Security和自定义逻辑实现数据权限和接口权限的过滤。以下是详细实现原理:
1. Token生成与识别机制
Token生成流程
用户登录成功时生成JWT Token:
// SysLoginService.java
public String login(String username, String password) {
// 验证成功后生成Token
String token = JwtUtils.createToken(loginUser.getUserId());
// 将用户信息和权限缓存到Redis
redisCache.setCacheObject("login_tokens:"+token, loginUser);
return token;
}
Token结构:
基于JJWT库生成的JWT Token
包含用户ID、登录时间等基本信息
不直接包含权限数据(权限数据存储在Redis)
Token识别流程
请求拦截:
// JwtAuthenticationTokenFilter.java
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
// 从请求头获取Token
String token = getToken(request);
if (StringUtils.isNotEmpty(token)) {
// 解析Token获取用户ID
String userid = JwtUtils.getUserid(token);
// 从Redis获取用户完整信息
LoginUser loginUser = redisCache.getCacheObject("login_tokens:"+token);
// 将认证信息存入SecurityContext
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
2. 接口权限过滤实现
权限注解控制
// 使用Spring Security的PreAuthorize注解
@PreAuthorize("@ss.hasPermi('system:user:list')")
@GetMapping("/list")
public TableDataInfo list(SysUser user) {
// 方法实现
}
权限验证服务
// PermissionService.java (通过@ss引用)
public boolean hasPermi(String permission) {
// 从SecurityContext获取当前用户权限
Set<String> permissions = getLoginUser().getPermissions();
return permissions.contains(permission);
}
3. 数据权限过滤实现
数据权限注解
// 在Mapper接口上添加@DataScope注解
@DataScope(deptAlias = "d", userAlias = "u")
List<SysUser> selectUserList(SysUser user);
实现原理
AOP拦截:
// DataScopeAspect.java
@Before("@annotation(dataScope)")
public void doBefore(DataScope dataScope) {
// 获取当前用户的数据权限范围
SysUser user = SecurityUtils.getLoginUser().getUser();
String permission = StringUtils.format(" OR {}.dept_id IN ({}) ",
dataScope.deptAlias(), dataScopeService.getDeptIdsForDataScope(user));
// 将权限SQL存入ThreadLocal
DataScopeHelper.setDataScope(permission);
}
SQL改写:
// Mybatis拦截器自动将权限SQL拼接到查询中
@Intercepts({
@Signature(type= StatementHandler.class, method="prepare", args={Connection.class, Integer.class})
})
public class DataScopeInterceptor implements Interceptor {
public Object intercept(Invocation invocation) {
// 获取原始SQL
String sql = boundSql.getSql();
// 添加数据权限过滤条件
if (DataScopeHelper.hasDataScope()) {
sql = sql + " AND " + DataScopeHelper.getDataScope();
}
// 执行修改后的SQL
}
}
4. 权限数据加载流程
登录时加载:
// SysLoginService.java
public LoginUser loadUserByUsername(String username) {
// 查询用户基本信息
SysUser user = userMapper.selectUserByUserName(username);
// 查询用户角色和权限
Set<String> permissions = permissionService.getMenuPermission(user);
// 封装到LoginUser对象
return new LoginUser(user, permissions);
}
Redis缓存结构:
login_tokens:token -> {
"userId": 1,
"user": {用户基本信息},
"permissions": ["system:user:list", ...],
"roles": ["admin", ...]
}
5. 安全性设计
Token防篡改:
使用HS512算法签名
签名密钥存储在配置文件中
Token失效机制:
# application.yml
token:
header: Authorization # 请求头名称
secret: abcdefghijklmn # 密钥
expireTime: 720 # 过期时间(分钟)
并发控制:
同一用户多次登录会生成不同Token
旧Token在有效期内仍可使用(可配置强制失效)
这种设计实现了若依框架的细粒度权限控制,既保证了接口访问的安全性,又实现了灵活的数据权限过滤。