1.加密方式:BCrypt
SysUserController.add
user.setPassword(BCrypt.hashpw(user.getPassword()));
BCrypt.hashpw
public static String hashpw(String password) {
return hashpw(password, gensalt());//随机创建盐
}
依然是加盐,不过和传统加盐区别有二:
每次的hash值都不一样
运算时间长
有文章指出bcrypt一个密码出来的时间比较长,需要0.3秒,而MD5只需要一微秒(百万分之一秒),一个40秒可以穷举得到明文的MD5,在bcrypt需要12年,时间成本太高。所以对于被保护者相对安全。况且登录事件不是频繁发生,每次使用系统只需一次登录即可,所以推荐使用BCrypt.
2.Sa-Token
参考目录
- Sa-Token 官方文档 (https://sa-token.dev33.cn/doc/index.html#/)
- Sa-Token 官方文档
框架集成
基于 Sa-Token 最新版本:
根pom中
ruoyi-common-satoken的pom中
ruoyi-common-security的pom中
geteway中,注意由于gateway给予webflux所以引入的依赖和之前的不同:
[https://sa-token.dev33.cn/doc/index-backup.html#/micro/gateway-auth?id=%e5%be%ae%e6%9c%8d%e5%8a%a1-%e7%bd%91%e5%85%b3%e7%bb%9f%e4%b8%80%e9%89%b4%e6%9d%83]
由于原作者写的已经非常详细了,所以只做部分补充说明
登录认证流程
https://blog.csdn.net/Michelle_Zhong/article/details/122480703
补充:LoginUser.getLoginId返回的是拼合后的字符串,包含了id和用户类型(sys_user/app_user)
在监听器UserActionListener中通过判断不同登录设备类型用户,赋予对应的数据
补充:rouyi-common-satoken/SaPermissionImpl
配置用户的菜单权限列表与角色权限列表
package com.ruoyi.common.satoken.core.service;
import cn.dev33.satoken.stp.StpInterface;
import com.ruoyi.common.core.enums.UserType;
import com.ruoyi.common.satoken.utils.LoginHelper;
import com.ruoyi.system.api.model.LoginUser;
import java.util.ArrayList;
import java.util.List;
/**
* sa-token 权限管理实现类
*
* @author Lion Li
*/
public class SaPermissionImpl implements StpInterface {
/**
* 获取菜单权限列表
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
LoginUser loginUser = LoginHelper.getLoginUser();
UserType userType = UserType.getUserType(loginUser.getUserType());
if (userType == UserType.SYS_USER) {
return new ArrayList<>(loginUser.getMenuPermission());
} else if (userType == UserType.APP_USER) {
// 其他端 自行根据业务编写
}
return new ArrayList<>();
}
/**
* 获取角色权限列表
*/
@Override
public List<String> getRoleList(Object loginId, String loginType) {
LoginUser loginUser = LoginHelper.getLoginUser();
UserType userType = UserType.getUserType(loginUser.getUserType());
if (userType == UserType.SYS_USER) {
return new ArrayList<>(loginUser.getRolePermission());
} else if (userType == UserType.APP_USER) {
// 其他端 自行根据业务编写
}
return new ArrayList<>();
}
}
通过注解校验用户权限
https://blog.csdn.net/Michelle_Zhong/article/details/122526722
Token 有效期及其续签
https://blog.csdn.net/Michelle_Zhong/article/details/126071871
在gateway中通过AuthFilter进行服务间鉴权
https://sa-token.dev33.cn/doc/index-backup.html#/micro/gateway-auth?id=%e5%be%ae%e6%9c%8d%e5%8a%a1-%e7%bd%91%e5%85%b3%e7%bb%9f%e4%b8%80%e9%89%b4%e6%9d%83
package com.ruoyi.gateway.filter;
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.ruoyi.common.core.constant.HttpStatus;
import com.ruoyi.gateway.config.properties.IgnoreWhiteProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* [Sa-Token 权限认证] 拦截器
*
* @author Lion Li
*/
@Configuration
public class AuthFilter {
/**
* 注册 Sa-Token 全局过滤器
*/
@Bean
public SaReactorFilter getSaReactorFilter(IgnoreWhiteProperties ignoreWhite) {
return new SaReactorFilter()
// 拦截地址
.addInclude("/**")
.addExclude("/favicon.ico", "/actuator/**")
// 鉴权方法:每次访问进入
.setAuth(obj -> {
// 登录校验 -- 拦截所有路由
SaRouter.match("/**")
.notMatch(ignoreWhite.getWhites())
.check(r -> {
// 检查是否登录 是否有token
StpUtil.checkLogin();
// 有效率影响 用于临时测试
// if (log.isDebugEnabled()) {
// log.debug("剩余有效时间: {}", StpUtil.getTokenTimeout());
// log.debug("临时有效时间: {}", StpUtil.getTokenActivityTimeout());
// }
});
}).setError(e -> SaResult.error("认证失败,无法访问系统资源").setCode(HttpStatus.UNAUTHORIZED));
}
}
其中的白名单配置:ignoreWhite.getWhites()
/**
* 放行白名单配置
*
* @author ruoyi
*/
@Data
@NoArgsConstructor
@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "security.ignore")
public class IgnoreWhiteProperties {
/**
* 放行白名单配置,网关不校验此处的白名单
*/
private List<String> whites = new ArrayList<>();
}
其中的security.ignore
ruoyi-gateway.yml中