shiro
三大组件 :
Subject:包含用户操作信息,
SecurityManager:shiro 的核心心脏,安全管理器,用户的登录校验
Realm:数据库用户信息,权限角色验证等
核心方法:
身份验证(getAuthenticationInfo 方法)验证账户和密码,并返回相关信息
权限获取(getAuthorizationInfo 方法) 获取指定身份的权限,并返回相关信息
令牌支持(supports方法)判断该令牌(Token)是否被支持,令牌有很多种类型,例如:HostAuthenticationToken(主机验证令牌),UsernamePasswordToken(账户密码验证令牌)
采用jwt+redis+shiro+springboot 通过 rbca 实现框架搭建
注意:
1. jwt本身就有超时时间为什么还用redis?
jwt无法控制退出后清除token,导致退出后还能拿token登录,且没有请求延长token时效功能,所以交给redis控制,jwt仅作为工具使用
redis存储方式 <userName,token>
虽然违背了 jwt 使用原则,但是基于业务这是更好的选择。
2. shiro 认证AuthenticationInfo 和授权AuthorizationInfo 抛出异常如何通过全局异常处理返回给前端?
全局异常无法捕捉到shiro的异常,只能通过自己的BasicHttpAuthenticationFilter 捕捉进行处理,例如:
public class JwtFilter extends BasicHttpAuthenticationFilter{
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
try {
........
} catch (java.lang.Exception e) {
this.response401(request,response,401,e.getCause().getMessage());
}
}
.......
/**
* 捕捉shiro 认证授权异常!!!,全局异常无法捕捉,只能从这里捕捉 全部 401
*/
private void response401(ServletRequest req, ServletResponse resp,int code,String msg) {
try {
HttpServletResponse response = (HttpServletResponse) resp;
response.setHeader("Context-type", "application/json;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
JSONObject jsonObject = new JSONObject();
jsonObject.put("data","");
jsonObject.put("code",code);
jsonObject.put("msg",msg);
PrintWriter out = response.getWriter();
out.print(jsonObject.toJSONString());
out.flush();
out.close();
} catch (java.lang.Exception e) {
}
}
}
3. redis 可以在每次请求都给token重置超时,但在filter里无法注入redisTemplate?
因为生命周期 filter --> servlet 所以在servlet之前,无法将redis注入到容器,只能写好工具类主动获取:
RedisUtil redisUtil = SpringContextUtil.getBean(RedisUtil.class);
4. 每次请求都走 shiro 认证授权,角色权限都要查库,耗费资源,可以缓存到redis
结构
pom
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- 阿里连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!-- mysql依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- mybatis-plus 依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<!-- 代码生成器依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<!-- 代码生成器依赖 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.2</version>
</dependency>
<!-- shiro+jwt -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>
<!-- lombok 依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<!-- swagger2 依赖 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!-- Swagger第三方UI依赖 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<!-- 日期处理工具 -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
<!-- common -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<!-- common -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.28</version>
</dependency>
</dependencies>
yml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3307/shiro?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC
username: root
password: phpts
driver-class-name: com.mysql.jdbc.Driver
jackson:
time-zone: Asia/Shanghai
redis:
jedis:
pool:
#最大连接数
max-active: 8
#最大阻塞等待时间(负数表示没限制)
max-wait: -1
#最大空闲
max-idle: 8
#最小空闲
min-idle: 0
#连接超时时间
timeout: 10000
token:
# 在redis中有效时长(分)
life: 10
# 生成token 秘钥
secret: 123
包configs -->mybatisplus
@Configuration
public class MybatisPlusConfig {
/**
* 分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
//针对 update和 delete语句,作用: 阻止恶意的全表更新删除
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}
}
@Component
public class MyBatisPlusFillTime implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
//插入时间
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
//更新时间
this.setFieldValByName("updateTime", new Date(), metaObject);
}
}
---------------------------------
包configs -->RedisConfig
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
//参照StringRedisTemplate内部实现指定序列化器
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(keySerializer());
redisTemplate.setHashKeySerializer(keySerializer());
redisTemplate.setValueSerializer(valueSerializer());
redisTemplate.setHashValueSerializer(valueSerializer());
return redisTemplate;
}
private RedisSerializer<String> keySerializer(){
return new StringRedisSerializer();
}
//使用Jackson序列化器
private RedisSerializer<Object> valueSerializer(){
return new GenericJackson2JsonRedisSerializer();
}
}
---------------------------------
包configs -->shiro
@Configuration
public class ShiroConfig {
@Bean("getJwtRealm")
public JwtRealm getJwtRealm(){return new JwtRealm();}
@Bean("securityManager")
public DefaultWebSecurityManager securityManager(@Qualifier("getJwtRealm") JwtRealm jwtRealm){
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(jwtRealm);
//关闭shiro自带session
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
manager.setSubjectDAO(subjectDAO);
return manager;
}
@Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
//过滤
Map<String, Filter> filters = new HashMap<>();
filters.put("jwt", new JwtFilter());
factoryBean.setFilters(filters);
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/doc.html","anon");
filterMap.put("/webjars/**", "anon");
filterMap.put("/druid/**", "anon");
filterMap.put("/app/**", "anon");
//互挤模式登录
filterMap.put("/sys/login", "anon");
//共享模式登录
filterMap.put("/sys/loginMany", "anon");
filterMap.put("/swagger/**", "anon");
filterMap.put("/v2/api-docs", "anon");
filterMap.put("/swagger-ui.html", "anon");
filterMap.put("/swagger-resources/**", "anon");
filterMap.put("/captcha", "anon");
filterMap.put("/aaa.txt", "anon");
filterMap.put("/**", "jwt");
factoryBean.setFilterChainDefinitionMap(filterMap);
factoryBean.setSecurityManager(securityManager);
return factoryBean;
}
//禁用session,不保存用户登录状态
@Bean
protected SessionStorageEvaluator sessionStorageEvaluator(){
DefaultWebSessionStorageEvaluator sessionStorageEvaluator = new DefaultWebSessionStorageEvaluator();
sessionStorageEvaluator.setSessionStorageEnabled(false);
return sessionStorageEvaluator;
}
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
autoProxyCreator.setProxyTargetClass(true);
return autoProxyCreator;
}
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){return new LifecycleBeanPostProcessor();}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
*
* @param
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("getJwtRealm") JwtRealm jwtRealm) {
SecurityManager manager = securityManager(jwtRealm);
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(manager);
return advisor;
}
}
---------------------------------
包configs -->swagger
@Configuration
@EnableSwagger2
public class Swagger2Config {
@Bean
public Docket createRestApi(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.shirojwt.demo.controller"))
.paths(PathSelectors.any())
.build()
.securityContexts(securityContexts())
.securitySchemes(securitySchemes());
}
private List<SecurityContext> securityContexts() {
List<SecurityContext> res = new ArrayList<>();
// 设置需要登录认证的路径
res.add(getContextByPath("/hello/*"));
return res;
}
private SecurityContext getContextByPath(String pathRegex) {
return SecurityContext.builder().securityReferences(defaultAuthPath())
.forPaths(PathSelectors.regex(pathRegex))
.build();
}
private List<SecurityReference> defaultAuthPath() {
List<SecurityReference> res = new ArrayList<>();
AuthorizationScope scope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] scopes = new AuthorizationScope[1];
scopes[0] = scope;
res.add(new SecurityReference("Authorization",scopes));
return res;
}
private List<ApiKey> securitySchemes() {
List<ApiKey> res = new ArrayList<>();
// 设置请求头信息
ApiKey apiKey = new ApiKey("Auth", "Authorization", "Header");
res.add(apiKey);
return res;
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("springboot+shiro+jwt框架接口文档")
.description("springboot+shiro+jwt框架")
.contact(new Contact("john", "http://localhost:8080/doc.html","11111111@qq.com"))
.version("1.0.0")
.build();
}
}
---------------------------------
包configs -->token
/**
* Created with IntelliJ IDEA
* Time: 15:40
* 读取配置文件
*/
@Data
@Component
@ConfigurationProperties(prefix = "token")
public class TokenLife {
/**
* redis token 时长 (分)
*/
private Integer life;
/**
* 生成token秘钥
*/
private String secret;
}
---------------------------------
包configs -->web
@Configuration
public class CrossOriginConfig {
@Bean
public CorsFilter corsFilter(){
CorsConfiguration config =new CorsConfiguration();
//允许跨越发送cookie
config.addAllowedOrigin("*");
config.addAllowedOrigin("null");
//放行全部原始头信息
config.setAllowCredentials(true);
//允许所有请求方法跨域调用
config.addAllowedHeader("*");
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
---------------------------------
包utils -->exception
/**
* 统一返回的异常信息的格式
*
* @author jl
*
*/
@Component
public class ExceptionResponseEntity {
@Getter
private int code;
@Getter
private String message;
public ExceptionResponseEntity() {
}
public ExceptionResponseEntity(int code, String message) {
this.code = code;
this.message = message;
}
}
/**
* Created with IntelliJ IDEA
* Date: 2022/11/8
* Time: 17:45
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
/**
* 如果需要捕获多个异常 定义如下:@ExceptionHandler({})
*
* @param request
* @param e
* @param response
* @return
*/
// 捕获多个异常的写法
@ExceptionHandler({MyCustomException.class,MyCustomException.class})
public R customExceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
MyCustomException exception = (MyCustomException) e;
log.error("全局异常信息 ex={}", e.getMessage(), e);
return R.fail(exception.getCode(), e.getMessage());
}
/**
* 捕获 RuntimeException 异常
* 如果在一个 exceptionHandler 通过 if (e instanceof xxxException) 太麻烦,
* 可以写多个方法标注@ExceptionHandler处理不同的异常
*
* @param e exception
* @param response response
* @return 响应结果
*/
@ExceptionHandler(RuntimeException.class)
public R runtimeExceptionHandler(final Exception e, HttpServletResponse response) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
if(e.getMessage().contains("com.mysql")){
log.info("\n\n SqlException=========== Exception Start ===== \n"+e+"\n "+"SqlException=========== Exception End =====\n\n");
}else {
log.info("\n\n RuntimeException=========== Exception Start ===== \n"+e+"\n "+"RuntimeException=========== Exception End =====\n\n");
}
return R.fail(HttpStatusCode.ERROR.getCode(),e.getMessage());
}
}
/**
* Created with IntelliJ IDEA
* Date: 2022/11/8
* Time: 17:41
* 自定义异常
*/
@Component
public class MyCustomException extends RuntimeException{
private static final long serialVersionUID = 8863339790253662109L;
@Getter
private int code ;
@Getter
private String message;
public MyCustomException() {
super();
}
public MyCustomException(String message) {
int code =500;
this.message = message;
this.code = code;
}
public MyCustomException(int code,String message) {
this.message = message;
this.code = code;
}
}
---------------------------------
包utils -->init
/**
* Created with IntelliJ IDEA
* Time: 16:11
* 初始化工具类,用于生命周期在servlet前还没有注入到容器中,通过工具主动获取配置类
*/
@Component
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext = null;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if(SpringContextUtil.applicationContext == null){
SpringContextUtil.applicationContext = applicationContext;
}
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public static Object getBean(String name){
return getApplicationContext().getBean(name);
}
public static <T> T getBean(Class<T> clazz){
return getApplicationContext().getBean(clazz);
}
public static <T> T getBean(String name,Class<T> clazz){
return getApplicationContext().getBean(name, clazz);
}
}
---------------------------------
包utils -->jwt
/**
* Created with IntelliJ IDEA
* Time: 17:49
*
* 所有请求都要先走filter 进行校验是否携带token
* 每次请求都走一遍
*/
public class JwtFilter extends BasicHttpAuthenticationFilter {
/**
* 校验是否携带token
* 携带:true
*/
@Override
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
HttpServletRequest req = (HttpServletRequest) request;
String authorization = req.getHeader(AUTHORIZATION_HEADER);
System.out.println("===============filter获取请求头Authorization: "+authorization+"==============================");
return authorization != null;
}
/**
* 将token注入到 shiro 中当做 shiro 的 token
*/
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String authorization = httpServletRequest.getHeader(AUTHORIZATION_HEADER);
//token注入到shiro中
JwtToken token = new JwtToken(authorization);
// 通过shiro登录:此时会去realm(jwtRealm) 通过 AuthenticationInfo doGetAuthenticationInfo 认证
getSubject(request, response).login(token);
// 如果没有抛出异常则代表登入成功,返回true
// 主动获取redis 和 yml配置文件,在filter生命周期,容器未注入redis
RedisUtil redisUtil = SpringContextUtil.getBean(RedisUtil.class);
TokenLife tokenLife = SpringContextUtil.getBean(TokenLife.class);
//redis 续期
redisUtil.expire(JwtTokenUtil.getUsername(authorization),tokenLife.getLife());
return true;
}
/**
* 认证或授权异常捕捉返回给前端
*/
@SneakyThrows
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (isLoginAttempt(request, response)) {
try {
executeLogin(request, response);
} catch (java.lang.Exception e) {
this.response401(request,response,401,e.getCause().getMessage());
}
}else {
//未携带token , 直接抛异常
this.response401(request,response,401,"权限不足,缺失token");
}
//此处仍然返回true 是因为 方法上已经加了 权限规则,即使访问了接口,但是没权限依然走不下去
//或者没有token代表是游客访问
return true;
}
/**
* 对跨域提供支持
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws java.lang.Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}
/**
* 捕捉shiro 认证授权异常!!!,全局异常无法捕捉,只能从这里捕捉 全部 401
*/
private void response401(ServletRequest req, ServletResponse resp,int code,String msg) {
try {
HttpServletResponse response = (HttpServletResponse) resp;
response.setHeader("Context-type", "application/json;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
JSONObject jsonObject = new JSONObject();
jsonObject.put("data","");
jsonObject.put("code",code);
jsonObject.put("msg",msg);
PrintWriter out = response.getWriter();
out.print(jsonObject.toJSONString());
out.flush();
out.close();
} catch (java.lang.Exception e) {
}
}
}
public class JwtRealm extends AuthorizingRealm {
@Autowired
private UserInfoService userInfoService;
@Autowired
private RoleAuthService roleAuthService;
@Autowired
private AuthInfoService authInfoService;
@Autowired
private RedisUtil redisUtil;
@Autowired
private TokenLife tokenLife;
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JwtToken;
}
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("==================用户授权=============");
//存放 角色 、权限信息
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//1 获取用户信息
UserInfo user = (UserInfo) principalCollection.getPrimaryPrincipal();
UserInfo userInfo = userInfoService.findUserByUsername(user.getUsername());
if(ObjectUtils.isEmpty(userInfo)){
throw new MyCustomException(401,"用户不存在!");
}
//2 根据用户获取角色信息
Integer roleId = user.getRoleId();
Set<String> roles = new HashSet<>();
roles.add(String.valueOf(roleId));
//3 根据角色信息获取权限信息
Set<String> authodKeys = new HashSet<>();
// 3.1 去角色权限信息关联表去查所有的角色权限
List<RoleAuth> allAuthorList = roleAuthService.list(new QueryWrapper<RoleAuth>().eq("role_id", roleId));
if(CollectionUtils.isNotEmpty(allAuthorList)){
// 3.2 去重的权限id
List<Integer> distinctAuthorId = allAuthorList.stream().map(e -> e.getAuthId()).collect(Collectors.toList());
// 3.3 所有的权限信息
List<AuthInfo> authInfoList = authInfoService.list(new QueryWrapper<AuthInfo>().in("id",distinctAuthorId ));
if(CollectionUtils.isNotEmpty(authInfoList)){
// 3.4 提取权限key: auth_key
authodKeys = authInfoList.stream().map(e -> e.getAuthKey()).collect(Collectors.toSet());
}
}
//4 角色授权
info.setRoles(roles);
//5 权限授权
info.setStringPermissions(authodKeys);
System.out.println("==================授权成功=============");
return null;
}
/**
* 认证
* 过滤器拦截到请求信息
* 根据token进行认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("==================token认证=============");
//获取前端界面传来的token
String token = (String) authenticationToken.getPrincipal();
if(StringUtils.isBlank(token)){
throw new MyCustomException(401,"token不存在!");
}
//1 根据token 获取 username
String username = JwtTokenUtil.getUsername(token);
if(StringUtils.isBlank(username)){
throw new MyCustomException(401,"token异常!");
}
//2 校验token格式 是否正确
boolean verify = JwtTokenUtil.verify(token, tokenLife.getSecret());
if(!verify) {
throw new MyCustomException(401,"token异常!");
}
//3 根据username 去redis 中查 token ,前端页面传入的token是否和 redis中的token一致
if(!redisUtil.hasKey(username)){
throw new MyCustomException(401,"token失效!");
}else {
if( !StringUtils.equals( token, redisUtil.get(username).toString() ) ){
throw new MyCustomException(401,"token不一致,校验失败!");
}
}
UserInfo userInfo = userInfoService.findUserByUsername(username);
if(ObjectUtils.isEmpty(userInfo)){
throw new MyCustomException(401,"用户不存在!");
}
return new SimpleAuthenticationInfo(userInfo,token,getName());
}
}
/**
* 自定义token传给shiro认证
* @Version 1.0.0
*/
public class JwtToken implements AuthenticationToken {
// 密钥
private String token;
public JwtToken(String token) {
this.token = token;
}
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}
/**
* Created with IntelliJ IDEA
* Time: 10:39
*/
@Component
public class JwtTokenUtil {
//此处jwt 配合redis 使用,jwt只是生成token 和校验 token是否正确,不进行超时校验,超时放到redis中:redis<username,token>
//且每次请求都刷新redis 超时时间
//认证时加一步 通过前端传来的token 获取用户的id,在通过用户id 去redis查,根据redis判断前端传的token 和redis存的token 是否一致,不一致那就是token失效了
//作者
private static final String ISSUSER = "john";
// 签名的主题
private static final String SUBJECT = "subject";
// 签名的观众
private static final String AUDIENCE = "web-Program";
//过期时间12小时
private static final long EXPIRE_TIME = 43200*1000;
/**
* 获得token中的信息无需secret解密也能获得
* @return token中包含的用户名
*/
public static String getUsername(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("username").asString();
} catch (JWTDecodeException e) {
return null;
}
}
/**
* 根据token+secret 校验token是否正确
* @param token 密钥
* @return 是否正确
*/
public static boolean verify(String token, String secret) {
try {
DecodedJWT jwt1 = JWT.decode(token);
String username = jwt1.getClaim("username").asString();
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
.withClaim("username", username )
.build();
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception exception) {
return false;
}
}
/**
* 校验token是否正确
* @param token 密钥
* @return 是否正确
*/
public static boolean verify(String token, String username, String secret) {
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
.withClaim("username", username )
.build();
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception exception) {
return false;
}
}
/**
* 生成签名,12小时后过期
* @param username 用户名
* @return 加密的token
*/
public static String creatToken(String username,String secret) {
long now = System.currentTimeMillis();
Date nowDate = new Date(now);
Date expireDate = new Date(now+EXPIRE_TIME);
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
Map<String, Object> map = new HashMap<String, Object>();
map.put("alg", "HS256");
map.put("typ", "JWT");
String token = JWT.create()
// 设置头部信息 Header
.withHeader(map)
// 设置 载荷 Payload
.withClaim("username", username)
.withClaim("testkey","value")
// 发布者
.withIssuer(ISSUSER)
// 主题
.withSubject(SUBJECT)
//接受者
.withAudience(AUDIENCE)
// 生成签名的时间
.withIssuedAt(nowDate)
// 签名过期的时间
.withExpiresAt(expireDate)
// 签名 Signature
.sign(algorithm);
return token;
} catch (JWTCreationException exception){
exception.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
public static boolean isExpire(String token){
DecodedJWT jwt = JWT.decode(token);
return System.currentTimeMillis() > jwt.getExpiresAt().getTime();
}
}
--------------------------------------------
包utils---->redis
@Component
public class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public RedisUtil(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(分)
* @return
*/
public boolean expire(String key,long time){
try {
if(time>0){
redisTemplate.expire(key, time, TimeUnit.MINUTES);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key){
return redisTemplate.getExpire(key,TimeUnit.SECONDS);
}
/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key){
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String ... key){
if(key!=null&&key.length>0){
if(key.length==1){
redisTemplate.delete(key[0]);
}else{
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
//============================String=============================
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key){
return key==null?null:redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key,Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(分钟) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key,Object value,long time){
try {
if(time>0){
redisTemplate.opsForValue().set(key, value, time, TimeUnit.MINUTES);
}else{
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
* @return
*/
public long incr(String key, long delta){
if(delta<0){
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/
public long decr(String key, long delta){
if(delta<0){
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
//================================Map=================================
/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public Object hget(String key,String item){
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map<Object,Object> hmget(String key){
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public boolean hmset(String key, Map<String,Object> map){
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String,Object> map, long time){
try {
redisTemplate.opsForHash().putAll(key, map);
if(time>0){
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key,String item,Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key,String item,Object value,long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if(time>0){
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item){
redisTemplate.opsForHash().delete(key,item);
}
/**
* 判断hash表中是否有该项的值
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item){
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
* @return
*/
public double hincr(String key, String item,double by){
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
* @return
*/
public double hdecr(String key, String item,double by){
return redisTemplate.opsForHash().increment(key, item,-by);
}
//============================set=============================
/**
* 根据key获取Set中的所有值
* @param key 键
* @return
*/
public Set<Object> sGet(String key){
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key,Object value){
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object...values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key,long time,Object...values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if(time>0) {
expire(key, time);
}
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
* @param key 键
* @return
*/
public long sGetSetSize(String key){
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object ...values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
//===============================list=================================
/**
* 获取list缓存的内容
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return
*/
public List<Object> lGet(String key, long start, long end){
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
* @param key 键
* @return
*/
public long lGetListSize(String key){
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
* @return
*/
public Object lGetIndex(String key,long index){
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index,Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key,long count,Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 模糊查询获取key值
* @param pattern
* @return
*/
public Set keys(String pattern){
return redisTemplate.keys(pattern);
}
/**
* 使用Redis的消息队列
* @param channel
* @param message 消息内容
*/
public void convertAndSend(String channel, Object message){
redisTemplate.convertAndSend(channel,message);
}
/**
* 根据起始结束序号遍历Redis中的list
* @param listKey
* @param start 起始序号
* @param end 结束序号
* @return
*/
public List<Object> rangeList(String listKey, long start, long end) {
//绑定操作
BoundListOperations<String, Object> boundValueOperations = redisTemplate.boundListOps(listKey);
//查询数据
return boundValueOperations.range(start, end);
}
/**
* 弹出右边的值 --- 并且移除这个值
* @param listKey
*/
public Object rifhtPop(String listKey){
//绑定操作
BoundListOperations<String, Object> boundValueOperations = redisTemplate.boundListOps(listKey);
return boundValueOperations.rightPop();
}
}
------------------------------
包utils---->result
public enum HttpStatusCode {
CONTINUE(100, "Continue"),
SWITCHING_PROTOCOLS(101, "Switching Protocols"),
PROCESSING(102, "Processing"),
CHECKPOINT(103, "Checkpoint"),
OK(200, "OK"),
CREATED(201, "Created"),
ACCEPTED(202, "Accepted"),
NON_AUTHORITATIVE_INFORMATION(203, "Non-Authoritative Information"),
NO_CONTENT(204, "No Content"),
RESET_CONTENT(205, "Reset Content"),
PARTIAL_CONTENT(206, "Partial Content"),
MULTI_STATUS(207, "Multi-Status"),
ALREADY_REPORTED(208, "Already Reported"),
IM_USED(226, "IM Used"),
MULTIPLE_CHOICES(300, "Multiple Choices"),
MOVED_PERMANENTLY(301, "Moved Permanently"),
ERROR(500, "Bad Request"),
FOUND(302, "Found");
@Getter
private int code;
@Getter
private String msg;
HttpStatusCode(int code, String msg){
this.code = code;
this.msg = msg;
}
}
@Data
public class R<T> {
// 结果状态码
private int code;
// 响应信息
private String msg;
// 总条数
private Long total;
// 响应数据
private T data;
// 接口请求时间
private long timestamp ;
public R (){
this.timestamp = System.currentTimeMillis();
}
public static <T> R<T> success(T data){
R R1 = new R();
R1.setCode(HttpStatusCode.OK.getCode());
R1.setMsg(HttpStatusCode.OK.getMsg());
R1.setData(data);
return R1;
}
public static <T> R<T> fail(int code, String msg){
R R1 = new R();
R1.setCode(code);
R1.setMsg(msg);
return R1;
}
public static <T> R<T> fail(String msg){
R R1 = new R();
R1.setCode(500);
R1.setMsg(msg);
return R1;
}
}
---------------------------
包----->time
public class DateUtils {
/** 时间格式(yyyy-MM-dd) */
public final static String DATE_PATTERN = "yyyy-MM-dd";
/** 时间格式(yyyy-MM-dd HH:mm:ss) */
public final static String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
/**
* 日期格式化 日期格式为:yyyy-MM-dd
* @param date 日期
* @return 返回yyyy-MM-dd格式日期
*/
public static String format(Date date) {
return format(date, DATE_PATTERN);
}
/**
* 日期格式化 日期格式为:yyyy-MM-dd
* @param date 日期
* @param pattern 格式,如:DateUtils.DATE_TIME_PATTERN
* @return 返回yyyy-MM-dd格式日期
*/
public static String format(Date date, String pattern) {
if(date != null){
SimpleDateFormat df = new SimpleDateFormat(pattern);
return df.format(date);
}
return null;
}
/**
* 字符串转换成日期
* @param strDate 日期字符串
* @param pattern 日期的格式,如:DateUtils.DATE_TIME_PATTERN
*/
public static Date stringToDate(String strDate, String pattern) {
if (StringUtils.isBlank(strDate)){
return null;
}
DateTimeFormatter fmt = DateTimeFormat.forPattern(pattern);
return fmt.parseLocalDateTime(strDate).toDate();
}
/**
* 根据周数,获取开始日期、结束日期
* @param week 周期 0本周,-1上周,-2上上周,1下周,2下下周
* @return 返回date[0]开始日期、date[1]结束日期
*/
public static Date[] getWeekStartAndEnd(int week) {
DateTime dateTime = new DateTime();
LocalDate date = new LocalDate(dateTime.plusWeeks(week));
date = date.dayOfWeek().withMinimumValue();
Date beginDate = date.toDate();
Date endDate = date.plusDays(6).toDate();
return new Date[]{beginDate, endDate};
}
/**
* 对日期的【秒】进行加/减
*
* @param date 日期
* @param seconds 秒数,负数为减
* @return 加/减几秒后的日期
*/
public static Date addDateSeconds(Date date, int seconds) {
DateTime dateTime = new DateTime(date);
return dateTime.plusSeconds(seconds).toDate();
}
/**
* 对日期的【分钟】进行加/减
*
* @param date 日期
* @param minutes 分钟数,负数为减
* @return 加/减几分钟后的日期
*/
public static Date addDateMinutes(Date date, int minutes) {
DateTime dateTime = new DateTime(date);
return dateTime.plusMinutes(minutes).toDate();
}
/**
* 对日期的【小时】进行加/减
*
* @param date 日期
* @param hours 小时数,负数为减
* @return 加/减几小时后的日期
*/
public static Date addDateHours(Date date, int hours) {
DateTime dateTime = new DateTime(date);
return dateTime.plusHours(hours).toDate();
}
/**
* 对日期的【天】进行加/减
*
* @param date 日期
* @param days 天数,负数为减
* @return 加/减几天后的日期
*/
public static Date addDateDays(Date date, int days) {
DateTime dateTime = new DateTime(date);
return dateTime.plusDays(days).toDate();
}
/**
* 对日期的【周】进行加/减
*
* @param date 日期
* @param weeks 周数,负数为减
* @return 加/减几周后的日期
*/
public static Date addDateWeeks(Date date, int weeks) {
DateTime dateTime = new DateTime(date);
return dateTime.plusWeeks(weeks).toDate();
}
/**
* 对日期的【月】进行加/减
*
* @param date 日期
* @param months 月数,负数为减
* @return 加/减几月后的日期
*/
public static Date addDateMonths(Date date, int months) {
DateTime dateTime = new DateTime(date);
return dateTime.plusMonths(months).toDate();
}
/**
* 对日期的【年】进行加/减
*
* @param date 日期
* @param years 年数,负数为减
* @return 加/减几年后的日期
*/
public static Date addDateYears(Date date, int years) {
DateTime dateTime = new DateTime(date);
return dateTime.plusYears(years).toDate();
}
public static void main(String[] args) {
System.out.println(addDateYears(new Date(),1));
}
}
controller
@Api(tags = "登录管理")
@RestController
public class LoginController {
@Resource
private LoginService loginService;
@ApiOperation(value = "用户登录(账号互相挤模式)")
@PostMapping("/sys/login")
public R login(@RequestBody UserInfo userInfo){
return loginService.login(userInfo);
}
@ApiOperation(value = "用户登录(账号共享模式)")
@PostMapping("/sys/loginMany")
public R loginMany(@RequestBody UserInfo userInfo){
return loginService.loginMany(userInfo);
}
}
== service
public interface LoginService {
/**
* 登录成功并返回token
* @return
*/
R login(UserInfo userInfo);
/**
* 多人登录成功并返回token
* @return
*/
R loginMany(UserInfo userInfo);
/**
* 退出登录
* @param userid
* @return
*/
Object logout(Long userid);
}
== impl
@Service
public class LoginServiceImpl implements LoginService {
//盐
private final static String SALT = "123";
@Autowired
private UserInfoService userService;
@Autowired
private RedisUtil redisUtil;
/**
* 角色
*/
@Autowired
private RoleAuthService roleAuthService;
/**
* 权限
*/
@Autowired
private AuthInfoService authInfoService;
/**
* token 生命周期
*/
@Resource
private TokenLife tokenLife;
@Override
public R login(UserInfo userInfo) {
UserInfo user = userService.getOne(new QueryWrapper<UserInfo>()
.eq("username", userInfo.getUsername()));
//用户不存在
if (user == null) {
return R.fail("用户名或密码不正确!");
}
//密码 md5+盐
String pwd = new SimpleHash("md5",userInfo.getPassword(),SALT,2).toHex();
//校验密码是否相等
if(!StringUtils.equals(pwd,user.getPassword())){
return R.fail("用户名或密码不正确!");
}
//校验通过创建token 默认 12个小时超时(从内存中删除),但是控制时间还是在redis这,redis过期了 有token也没用了 得重新登陆
String token = JwtTokenUtil.creatToken(user.getUsername(),tokenLife.getSecret());
//删除redis中的token
redisUtil.del(user.getUsername());
//保存token 到redis,10min 超时
redisUtil.set(user.getUsername(),token,tokenLife.getLife());
user.setPassword(null);
//获取登录用户角色权限信息,前端有可能需要
Map<String, Object> roleAndPermission = this.getRoleAndPermission(user);
return R.success(new HashMap(){{
put("user", user);
put("token", token);
put("role", roleAndPermission.get("role"));
put("permission", roleAndPermission.get("permission"));
}});
}
@Override
public R loginMany(UserInfo userInfo) {
UserInfo user = userService.getOne(new QueryWrapper<UserInfo>()
.eq("username", userInfo.getUsername()));
//用户不存在
if (user == null) {
return R.fail("用户名或密码不正确!");
}
//密码 md5+盐,经过两次hash
String pwd = new SimpleHash("md5",userInfo.getPassword(),SALT,2).toHex();
//校验密码是否相等
if(!StringUtils.equals(pwd,user.getPassword())){
return R.fail("用户名或密码不正确!");
}
//校验通过创建token 默认 12个小时超时(从内存中删除),但是控制时间还是在redis这,redis过期了 有token也没用了 得重新登陆
String token = null;
//多人共享账号,redis存在就获取,不存在就创建
if(redisUtil.hasKey(user.getUsername())){
token = redisUtil.get(user.getUsername()).toString();
}else {
token = JwtTokenUtil.creatToken(user.getUsername(),tokenLife.getSecret());
//保存token 到redis,10min 超时
redisUtil.set(user.getUsername(),token,tokenLife.getLife());
}
String finalToken = token;
user.setPassword(null);
//获取登录用户角色权限信息,前端有可能需要
Map<String, Object> roleAndPermission = this.getRoleAndPermission(user);
return R.success(new HashMap(){{
put("user", user);
put("token", finalToken);
put("role", roleAndPermission.get("role"));
put("permission", roleAndPermission.get("permission"));
}}
);
}
@Override
public Object logout(Long userid) {
return null;
}
/**
* 登录获取权限角色一并返给前端
*/
public Map<String,Object> getRoleAndPermission(UserInfo user) {
//1 存放角色权限
Map<String,Object> info = new HashMap<>();
//2 根据用户获取角色信息
Integer roleId = user.getRoleId();
Set<String> roles = new HashSet<>();
roles.add(String.valueOf(roleId));
//3 根据角色信息获取权限信息
Set<String> authodKeys = new HashSet<>();
// 3.1 去角色权限信息关联表去查所有的角色权限
List<RoleAuth> allAuthorList = roleAuthService.list(new QueryWrapper<RoleAuth>().eq("role_id", roleId));
if(CollectionUtils.isNotEmpty(allAuthorList)){
// 3.2 去重的权限id
List<Integer> distinctAuthorId = allAuthorList.stream().map(e -> e.getAuthId()).collect(Collectors.toList());
// 3.3 所有的权限信息
List<AuthInfo> authInfoList = authInfoService.list(new QueryWrapper<AuthInfo>().in("id",distinctAuthorId ));
if(CollectionUtils.isNotEmpty(authInfoList)){
// 3.4 提取权限key: auth_key
authodKeys = authInfoList.stream().map(e -> e.getAuthKey()).collect(Collectors.toSet());
}
}
//4 角色列表
info.put("role",roles);
//5 权限列表
info.put("permission",authodKeys);
return info;
}
}