第05章 - 认证与授权中心
第05章 - 认证与授权中心
5.1 认证中心概述
5.1.1 认证中心职责
ruoyi-auth认证中心是RuoYi-Cloud的核心安全组件,负责处理系统的身份认证和Token管理:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 认证中心 (ruoyi-auth) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 核心功能 │ │
│ ├─────────────────┬─────────────────┬─────────────────┬──────────────┤ │
│ │ 用户登录 │ 用户登出 │ 用户注册 │ Token刷新 │ │
│ └─────────────────┴─────────────────┴─────────────────┴──────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 安全机制 │ │
│ ├─────────────────┬─────────────────┬─────────────────┬──────────────┤ │
│ │ 密码加密验证 │ 验证码校验 │ 登录限制 │ 日志记录 │ │
│ └─────────────────┴─────────────────┴─────────────────┴──────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Token管理 │ │
│ ├─────────────────┬─────────────────┬─────────────────┬──────────────┤ │
│ │ Token生成 │ Token存储 │ Token验证 │ Token续期 │ │
│ └─────────────────┴─────────────────┴─────────────────┴──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
5.1.2 项目结构
ruoyi-auth/
├── src/main/java/com/ruoyi/auth/
│ ├── RuoYiAuthApplication.java # 启动类
│ ├── controller/
│ │ └── TokenController.java # 认证控制器
│ ├── form/
│ │ ├── LoginBody.java # 登录请求体
│ │ └── RegisterBody.java # 注册请求体
│ └── service/
│ ├── SysLoginService.java # 登录服务
│ ├── SysPasswordService.java # 密码服务
│ ├── SysRecordLogService.java # 日志服务
│ └── TokenService.java # Token服务
└── src/main/resources/
└── bootstrap.yml # 启动配置
5.2 登录认证流程
5.2.1 完整登录流程
┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐
│ Client │ │ Gateway │ │ Auth │ │ System │ │ Redis │
└─────┬──────┘ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘
│ │ │ │ │
│ 1.登录请求 │ │ │ │
│─────────────────>│ │ │ │
│ │ 2.验证码校验 │ │ │
│ │─────────────────>│ │ │
│ │ │ 3.获取验证码 │ │
│ │ │─────────────────────────────────────>│
│ │ │ 4.返回验证码 │ │
│ │ │<─────────────────────────────────────│
│ │ 5.校验结果 │ │ │
│ │<─────────────────│ │ │
│ │ │ │ │
│ │ 6.转发认证请求 │ │ │
│ │─────────────────>│ │ │
│ │ │ 7.查询用户 │ │
│ │ │─────────────────>│ │
│ │ │ 8.返回用户信息 │ │
│ │ │<─────────────────│ │
│ │ │ │ │
│ │ │ 9.验证密码 │ │
│ │ │───────┐ │ │
│ │ │ │ │ │
│ │ │<──────┘ │ │
│ │ │ │ │
│ │ │ 10.生成Token │ │
│ │ │───────┐ │ │
│ │ │ │ │ │
│ │ │<──────┘ │ │
│ │ │ │ │
│ │ │ 11.存储Token │ │
│ │ │─────────────────────────────────────>│
│ │ │ 12.存储成功 │ │
│ │ │<─────────────────────────────────────│
│ │ │ │ │
│ │ 13.返回Token │ │ │
│ │<─────────────────│ │ │
│ 14.返回登录结果 │ │ │ │
│<─────────────────│ │ │ │
│ │ │ │ │
5.2.2 登录控制器
@RestController
public class TokenController {
@Autowired
private TokenService tokenService;
@Autowired
private SysLoginService sysLoginService;
/**
* 登录方法
*/
@PostMapping("login")
public R<?> login(@RequestBody LoginBody form) {
// 用户登录
LoginUser userInfo = sysLoginService.login(form.getUsername(), form.getPassword());
// 获取登录token
return R.ok(tokenService.createToken(userInfo));
}
/**
* 登出方法
*/
@DeleteMapping("logout")
public R<?> logout(HttpServletRequest request) {
String token = SecurityUtils.getToken(request);
if (StringUtils.isNotEmpty(token)) {
String username = JwtUtils.getUserName(token);
// 删除用户缓存记录
AuthUtil.logoutByToken(token);
// 记录用户退出日志
sysLoginService.logout(username);
}
return R.ok();
}
/**
* 刷新方法
*/
@PostMapping("refresh")
public R<?> refresh(HttpServletRequest request) {
LoginUser loginUser = tokenService.getLoginUser(request);
if (loginUser != null) {
// 刷新令牌有效期
tokenService.refreshToken(loginUser);
return R.ok();
}
return R.ok();
}
/**
* 注册方法
*/
@PostMapping("register")
public R<?> register(@RequestBody RegisterBody registerBody) {
// 用户注册
sysLoginService.register(registerBody.getUsername(), registerBody.getPassword());
return R.ok();
}
}
5.2.3 登录服务实现
@Component
public class SysLoginService {
@Autowired
private RemoteUserService remoteUserService;
@Autowired
private SysPasswordService passwordService;
@Autowired
private SysRecordLogService recordLogService;
/**
* 登录
*/
public LoginUser login(String username, String password) {
// 用户名或密码为空 错误
if (StringUtils.isAnyBlank(username, password)) {
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户/密码必须填写");
throw new ServiceException("用户/密码必须填写");
}
// 密码如果不在指定范围内 错误
if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH) {
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户密码不在指定范围");
throw new ServiceException("用户密码不在指定范围");
}
// 用户名不在指定范围内 错误
if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|| username.length() > UserConstants.USERNAME_MAX_LENGTH) {
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户名不在指定范围");
throw new ServiceException("用户名不在指定范围");
}
// IP黑名单校验
String blackStr = Convert.toStr(redisService.getCacheObject(CacheConstants.SYS_LOGIN_BLACKIPLIST));
if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) {
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "很抱歉,访问IP已被列入系统黑名单");
throw new ServiceException("很抱歉,访问IP已被列入系统黑名单");
}
// 查询用户信息
R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER);
if (R.FAIL == userResult.getCode()) {
throw new ServiceException(userResult.getMsg());
}
if (userResult.getData() == null) {
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "登录用户不存在");
throw new ServiceException("登录用户:" + username + " 不存在");
}
LoginUser userInfo = userResult.getData();
SysUser user = userResult.getData().getSysUser();
if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "对不起,您的账号已被删除");
throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
}
if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户已停用,请联系管理员");
throw new ServiceException("对不起,您的账号:" + username + " 已停用");
}
// 密码校验
passwordService.validate(user, password);
// 记录登录信息
recordLogService.recordLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功");
recordLoginInfo(user.getUserId());
return userInfo;
}
/**
* 记录登录信息
*/
public void recordLoginInfo(Long userId) {
SysUser sysUser = new SysUser();
sysUser.setUserId(userId);
sysUser.setLoginIp(IpUtils.getIpAddr());
sysUser.setLoginDate(DateUtils.getNowDate());
remoteUserService.recordUserLogin(sysUser, SecurityConstants.INNER);
}
/**
* 注销登录
*/
public void logout(String loginName) {
recordLogService.recordLogininfor(loginName, Constants.LOGOUT, "退出成功");
}
/**
* 注册
*/
public void register(String username, String password) {
// 用户名或密码为空 错误
if (StringUtils.isAnyBlank(username, password)) {
throw new ServiceException("用户/密码必须填写");
}
if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|| username.length() > UserConstants.USERNAME_MAX_LENGTH) {
throw new ServiceException("账户长度必须在2到20个字符之间");
}
if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH) {
throw new ServiceException("密码长度必须在5到20个字符之间");
}
// 注册用户信息
SysUser sysUser = new SysUser();
sysUser.setUserName(username);
sysUser.setNickName(username);
sysUser.setPassword(SecurityUtils.encryptPassword(password));
R<?> registerResult = remoteUserService.registerUserInfo(sysUser, SecurityConstants.INNER);
if (R.FAIL == registerResult.getCode()) {
throw new ServiceException(registerResult.getMsg());
}
recordLogService.recordLogininfor(username, Constants.REGISTER, "注册成功");
}
}
5.3 密码安全
5.3.1 密码加密
RuoYi-Cloud使用BCrypt算法对密码进行加密:
public class SecurityUtils {
/**
* 生成BCryptPasswordEncoder密码
*/
public static String encryptPassword(String password) {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
return passwordEncoder.encode(password);
}
/**
* 判断密码是否相同
*/
public static boolean matchesPassword(String rawPassword, String encodedPassword) {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
return passwordEncoder.matches(rawPassword, encodedPassword);
}
}
5.3.2 密码验证服务
@Component
public class SysPasswordService {
@Autowired
private RedisService redisService;
@Value(value = "${user.password.maxRetryCount}")
private int maxRetryCount;
@Value(value = "${user.password.lockTime}")
private int lockTime;
/**
* 登录账户密码错误次数缓存键名
*/
private String getCacheKey(String username) {
return CacheConstants.PWD_ERR_CNT_KEY + username;
}
/**
* 验证密码
*/
public void validate(SysUser user, String password) {
String username = user.getUserName();
Integer retryCount = redisService.getCacheObject(getCacheKey(username));
if (retryCount == null) {
retryCount = 0;
}
// 验证是否超过最大重试次数
if (retryCount >= Integer.valueOf(maxRetryCount).intValue()) {
String errMsg = StringUtils.format("密码输入错误{}次,帐户锁定{}分钟", maxRetryCount, lockTime);
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, errMsg);
throw new ServiceException(errMsg);
}
// 密码匹配校验
if (!SecurityUtils.matchesPassword(password, user.getPassword())) {
retryCount = retryCount + 1;
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL,
StringUtils.format("密码输入错误{}次", retryCount));
redisService.setCacheObject(getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES);
throw new ServiceException("用户不存在/密码错误");
} else {
clearLoginRecordCache(username);
}
}
/**
* 清除登录记录缓存
*/
public void clearLoginRecordCache(String loginName) {
if (redisService.hasKey(getCacheKey(loginName))) {
redisService.deleteObject(getCacheKey(loginName));
}
}
}
5.4 Token管理
5.4.1 Token服务实现
@Component
public class TokenService {
@Autowired
private RedisService redisService;
/** 令牌自定义标识 */
@Value("${token.header}")
private String header;
/** 令牌秘钥 */
@Value("${token.secret}")
private String secret;
/** 令牌有效期(默认30分钟) */
@Value("${token.expireTime}")
private int expireTime;
protected static final long MILLIS_SECOND = 1000;
protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
private final static long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;
/**
* 创建令牌
*/
public Map<String, Object> createToken(LoginUser loginUser) {
String token = IdUtils.fastUUID();
Long userId = loginUser.getSysUser().getUserId();
String userName = loginUser.getSysUser().getUserName();
loginUser.setToken(token);
loginUser.setUserid(userId);
loginUser.setUsername(userName);
loginUser.setIpaddr(IpUtils.getIpAddr());
refreshToken(loginUser);
// 生成JWT
Map<String, Object> claims = new HashMap<>();
claims.put(SecurityConstants.USER_KEY, token);
claims.put(SecurityConstants.DETAILS_USER_ID, userId);
claims.put(SecurityConstants.DETAILS_USERNAME, userName);
Map<String, Object> rspMap = new HashMap<>();
rspMap.put("access_token", JwtUtils.createToken(claims));
rspMap.put("expires_in", expireTime);
return rspMap;
}
/**
* 获取用户身份信息
*/
public LoginUser getLoginUser(HttpServletRequest request) {
// 获取请求携带的令牌
String token = getToken(request);
return getLoginUser(token);
}
/**
* 获取用户身份信息
*/
public LoginUser getLoginUser(String token) {
LoginUser user = null;
try {
if (StringUtils.isNotEmpty(token)) {
String userKey = JwtUtils.getUserKey(token);
user = redisService.getCacheObject(getTokenKey(userKey));
}
} catch (Exception e) {
log.error("获取用户信息异常'{}'", e.getMessage());
}
return user;
}
/**
* 设置用户身份信息
*/
public void setLoginUser(LoginUser loginUser) {
if (loginUser != null && StringUtils.isNotEmpty(loginUser.getToken())) {
refreshToken(loginUser);
}
}
/**
* 删除用户缓存信息
*/
public void delLoginUser(String token) {
if (StringUtils.isNotEmpty(token)) {
String userKey = JwtUtils.getUserKey(token);
redisService.deleteObject(getTokenKey(userKey));
}
}
/**
* 刷新令牌有效期
*/
public void refreshToken(LoginUser loginUser) {
loginUser.setLoginTime(System.currentTimeMillis());
loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
// 根据uuid将loginUser缓存
String userKey = getTokenKey(loginUser.getToken());
redisService.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
}
/**
* 验证令牌有效期,相差不足20分钟,自动刷新缓存
*/
public void verifyToken(LoginUser loginUser) {
long expireTime = loginUser.getExpireTime();
long currentTime = System.currentTimeMillis();
if (expireTime - currentTime <= MILLIS_MINUTE_TEN) {
refreshToken(loginUser);
}
}
/**
* 获取请求token
*/
private String getToken(HttpServletRequest request) {
String token = request.getHeader(header);
if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX)) {
token = token.replace(TokenConstants.PREFIX, "");
}
return token;
}
private String getTokenKey(String token) {
return CacheConstants.LOGIN_TOKEN_KEY + token;
}
}
5.4.2 JWT工具类
public class JwtUtils {
public static String secret = TokenConstants.SECRET;
/**
* 从数据声明生成令牌
*/
public static String createToken(Map<String, Object> claims) {
String token = Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
return token;
}
/**
* 从令牌中获取数据声明
*/
public static Claims parseToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
/**
* 根据令牌获取用户标识
*/
public static String getUserKey(String token) {
Claims claims = parseToken(token);
return getValue(claims, SecurityConstants.USER_KEY);
}
/**
* 根据令牌获取用户ID
*/
public static String getUserId(String token) {
Claims claims = parseToken(token);
return getValue(claims, SecurityConstants.DETAILS_USER_ID);
}
/**
* 根据令牌获取用户名
*/
public static String getUserName(String token) {
Claims claims = parseToken(token);
return getValue(claims, SecurityConstants.DETAILS_USERNAME);
}
/**
* 根据身份信息获取键值
*/
public static String getValue(Claims claims, String key) {
return Convert.toStr(claims.get(key), "");
}
}
5.5 验证码机制
5.5.1 验证码配置
# 安全配置
security:
# 验证码
captcha:
enabled: true # 是否开启验证码
type: math # 验证码类型:math-数学计算, char-字符
5.5.2 验证码生成
@RestController
public class CaptchaController {
@Autowired
private Producer captchaProducer;
@Autowired
private Producer captchaProducerMath;
@Autowired
private RedisService redisService;
@Value("${security.captcha.enabled}")
private Boolean captchaEnabled;
@Value("${security.captcha.type}")
private String captchaType;
/**
* 生成验证码
*/
@GetMapping("/captchaImage")
public AjaxResult getCode(HttpServletResponse response) {
AjaxResult ajax = AjaxResult.success();
ajax.put("captchaEnabled", captchaEnabled);
if (!captchaEnabled) {
return ajax;
}
// 保存验证码信息
String uuid = IdUtils.simpleUUID();
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;
String capStr = null, code = null;
BufferedImage image = null;
if ("math".equals(captchaType)) {
String capText = captchaProducerMath.createText();
capStr = capText.substring(0, capText.lastIndexOf("@"));
code = capText.substring(capText.lastIndexOf("@") + 1);
image = captchaProducerMath.createImage(capStr);
} else if ("char".equals(captchaType)) {
capStr = code = captchaProducer.createText();
image = captchaProducer.createImage(capStr);
}
redisService.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
// 转换流信息写出
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
try {
ImageIO.write(image, "jpg", os);
} catch (IOException e) {
return AjaxResult.error(e.getMessage());
}
ajax.put("uuid", uuid);
ajax.put("img", Base64.encode(os.toByteArray()));
return ajax;
}
}
5.5.3 验证码过滤器
@Component
public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object> {
@Autowired
private ValidateCodeService validateCodeService;
@Autowired
private CaptchaProperties captchaProperties;
private static final String[] VALIDATE_URL = new String[] { "/auth/login", "/auth/register" };
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
// 非登录/注册请求或验证码关闭,不处理
if (!StringUtils.equalsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL)
|| !captchaProperties.getEnabled()) {
return chain.filter(exchange);
}
try {
String rspStr = resolveBodyFromRequest(request);
JSONObject obj = JSON.parseObject(rspStr);
validateCodeService.checkCaptcha(obj.getString("code"), obj.getString("uuid"));
} catch (Exception e) {
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage());
}
return chain.filter(exchange);
};
}
}
5.5.4 验证码校验
@Service
public class ValidateCodeServiceImpl implements ValidateCodeService {
@Autowired
private RedisService redisService;
/**
* 校验验证码
*/
@Override
public void checkCaptcha(String code, String uuid) throws CaptchaException {
if (StringUtils.isEmpty(code)) {
throw new CaptchaException("验证码不能为空");
}
if (StringUtils.isEmpty(uuid)) {
throw new CaptchaException("验证码已失效");
}
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;
String captcha = redisService.getCacheObject(verifyKey);
redisService.deleteObject(verifyKey);
if (captcha == null) {
throw new CaptchaException("验证码已失效");
}
if (!code.equalsIgnoreCase(captcha)) {
throw new CaptchaException("验证码错误");
}
}
}
5.6 登录日志记录
5.6.1 日志记录服务
@Component
public class SysRecordLogService {
@Autowired
private RemoteLogService remoteLogService;
/**
* 记录登录信息
*/
public void recordLogininfor(String username, String status, String message) {
SysLogininfor logininfor = new SysLogininfor();
logininfor.setUserName(username);
logininfor.setIpaddr(IpUtils.getIpAddr());
logininfor.setMsg(message);
// 日志状态
if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) {
logininfor.setStatus(Constants.LOGIN_SUCCESS_STATUS);
} else if (Constants.LOGIN_FAIL.equals(status)) {
logininfor.setStatus(Constants.LOGIN_FAIL_STATUS);
}
remoteLogService.saveLogininfor(logininfor, SecurityConstants.INNER);
}
}
5.6.2 登录日志实体
public class SysLogininfor extends BaseEntity {
/** ID */
private Long infoId;
/** 用户账号 */
private String userName;
/** 状态 0成功 1失败 */
private String status;
/** 地址 */
private String ipaddr;
/** 描述 */
private String msg;
/** 访问时间 */
private Date accessTime;
// getter/setter...
}
5.7 权限模型
5.7.1 RBAC权限模型
RuoYi-Cloud采用RBAC(Role-Based Access Control)基于角色的访问控制模型:
┌─────────────────────────────────────────────────────────────────────────────┐
│ RBAC 权限模型 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ 用户 │──────────────┬──────────────┬──────────────┐ │
│ │ (User) │ │ │ │ │
│ └─────────────┘ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 角色1 │ │ 角色2 │ │ 角色3 │ │
│ │ (Role) │ │ (Role) │ │ (Role) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ ┌────────────┴────────────┐ │ │ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 菜单1 │ │ 菜单2 │ │ 菜单3 │ │ 菜单4 │ │
│ │ (Menu) │ │ (Menu) │ │ (Menu) │ │ (Menu) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 按钮1 │ │ 按钮2 │ │ 按钮3 │ │ 按钮4 │ │
│ │(Permission) │ │(Permission) │ │(Permission) │ │(Permission) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
5.7.2 用户权限加载
@Service
public class SysPermissionService {
@Autowired
private ISysRoleService roleService;
@Autowired
private ISysMenuService menuService;
/**
* 获取角色数据权限
*/
public Set<String> getRolePermission(SysUser user) {
Set<String> roles = new HashSet<String>();
// 管理员拥有所有权限
if (user.isAdmin()) {
roles.add("admin");
} else {
roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId()));
}
return roles;
}
/**
* 获取菜单数据权限
*/
public Set<String> getMenuPermission(SysUser user) {
Set<String> perms = new HashSet<String>();
// 管理员拥有所有权限
if (user.isAdmin()) {
perms.add("*:*:*");
} else {
List<SysRole> roles = user.getRoles();
if (!CollectionUtils.isEmpty(roles)) {
// 多角色设置permissions属性,以便数据权限匹配权限
for (SysRole role : roles) {
Set<String> rolePerms = menuService.selectMenuPermsByRoleId(role.getRoleId());
role.setPermissions(rolePerms);
perms.addAll(rolePerms);
}
} else {
perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId()));
}
}
return perms;
}
}
5.7.3 LoginUser结构
public class LoginUser implements Serializable {
/** 用户唯一标识 */
private String token;
/** 用户ID */
private Long userid;
/** 用户名 */
private String username;
/** 登录时间 */
private Long loginTime;
/** 过期时间 */
private Long expireTime;
/** 登录IP地址 */
private String ipaddr;
/** 权限列表 */
private Set<String> permissions;
/** 角色列表 */
private Set<String> roles;
/** 用户信息 */
private SysUser sysUser;
/**
* 是否具有指定权限
*/
public boolean hasPermission(String permission) {
if (StringUtils.isEmpty(permission)) {
return false;
}
if (permissions == null || permissions.isEmpty()) {
return false;
}
for (String perm : permissions) {
if (ALL_PERMISSION.equals(perm) || perm.equals(permission)) {
return true;
}
}
return false;
}
/**
* 是否具有指定角色
*/
public boolean hasRole(String role) {
if (StringUtils.isEmpty(role)) {
return false;
}
if (roles == null || roles.isEmpty()) {
return false;
}
for (String r : roles) {
if (SUPER_ADMIN.equals(r) || r.equals(role)) {
return true;
}
}
return false;
}
}
5.8 安全配置
5.8.1 Nacos配置
# application-dev.yml
security:
# 验证码
captcha:
enabled: true
type: math
# XSS过滤
xss:
enabled: true
excludeUrls:
- /system/notice
# 不校验白名单
ignore:
whites:
- /auth/logout
- /auth/login
- /auth/register
- /*/v3/api-docs
- /csrf
5.8.2 Token配置
# Token配置
token:
# 令牌自定义标识
header: Authorization
# 令牌密钥
secret: abcdefghijklmnopqrstuvwxyz
# 令牌有效期(默认30分钟)
expireTime: 30
5.9 小结
本章详细介绍了RuoYi-Cloud的认证与授权中心,包括:
- 认证中心职责:Token管理、用户认证、安全机制
- 登录流程:完整的登录认证流程
- 密码安全:BCrypt加密、密码验证、登录限制
- Token管理:创建、存储、验证、刷新
- 验证码:生成、校验机制
- 日志记录:登录日志记录
- 权限模型:RBAC权限模型
理解认证授权机制是进行安全开发的基础,下一章我们将深入了解网关服务。

浙公网安备 33010602011771号