123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- package org.jeecg.config.shiro;
- import cn.hutool.crypto.SecureUtil;
- import lombok.extern.slf4j.Slf4j;
- import org.apache.shiro.authc.AuthenticationException;
- import org.apache.shiro.authc.AuthenticationInfo;
- import org.apache.shiro.authc.AuthenticationToken;
- import org.apache.shiro.authc.SimpleAuthenticationInfo;
- import org.apache.shiro.authz.AuthorizationInfo;
- import org.apache.shiro.authz.SimpleAuthorizationInfo;
- import org.apache.shiro.realm.AuthorizingRealm;
- import org.apache.shiro.subject.PrincipalCollection;
- import org.jeecg.common.api.CommonAPI;
- import org.jeecg.common.constant.CacheConstant;
- import org.jeecg.common.constant.CommonConstant;
- import org.jeecg.common.system.util.JwtUtil;
- import org.jeecg.common.system.vo.LoginUser;
- import org.jeecg.common.util.RedisUtil;
- import org.jeecg.common.util.SpringContextUtils;
- import org.jeecg.common.util.oConvertUtils;
- import org.jeecg.config.mybatis.TenantContext;
- import org.springframework.context.annotation.Lazy;
- import org.springframework.stereotype.Component;
- import javax.annotation.Resource;
- import java.util.Set;
- /**
- * @Description: 用户登录鉴权和获取用户授权
- * @Author: Scott
- * @Date: 2019-4-23 8:13
- * @Version: 1.1
- */
- @Component
- @Slf4j
- public class ShiroRealm extends AuthorizingRealm {
- @Lazy
- @Resource
- private CommonAPI commonAPI;
- @Lazy
- @Resource
- private RedisUtil redisUtil;
- /**
- * 必须重写此方法,不然Shiro会报错
- */
- @Override
- public boolean supports(AuthenticationToken token) {
- return token instanceof JwtToken;
- }
- /**
- * 权限信息认证(包括角色以及权限)是用户访问controller的时候才进行验证(redis存储的此处权限信息)
- * 触发检测用户权限时才会调用此方法,例如checkRole,checkPermission
- *
- * @param principals 身份信息
- * @return AuthorizationInfo 权限信息
- */
- @Override
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
- log.info("===============Shiro权限认证开始============ [ roles、permissions]==========");
- String username = null;
- if (principals != null) {
- LoginUser sysUser = (LoginUser) principals.getPrimaryPrincipal();
- username = sysUser.getUsername();
- }
- SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
- // 设置用户拥有的角色集合,比如“admin,test”
- Set<String> roleSet = commonAPI.queryUserRoles(username);
- System.out.println(roleSet.toString());
- info.setRoles(roleSet);
- // 设置用户拥有的权限集合,比如“sys:role:add,sys:user:add”
- Set<String> permissionSet = commonAPI.queryUserAuths(username);
- info.addStringPermissions(permissionSet);
- System.out.println(permissionSet);
- log.info("===============Shiro权限认证成功==============");
- return info;
- }
- /**
- * 用户信息认证是在用户进行登录的时候进行验证(不存redis)
- * 也就是说验证用户输入的账号和密码是否正确,错误抛出异常
- *
- * @param auth 用户登录的账号密码信息
- * @return 返回封装了用户信息的 AuthenticationInfo 实例
- * @throws AuthenticationException
- */
- @Override
- protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
- log.debug("===============Shiro身份认证开始============doGetAuthenticationInfo==========");
- String token = (String) auth.getCredentials();
- if (token == null) {
- log.info("————————身份认证失败——————————IP地址: "+ oConvertUtils.getIpAddrByRequest(SpringContextUtils.getHttpServletRequest()));
- throw new AuthenticationException("token为空!");
- }
- // 校验token有效性
- LoginUser loginUser = this.checkUserTokenIsEffect(token);
- return new SimpleAuthenticationInfo(loginUser, token, getName());
- }
- /**
- * 校验token的有效性
- *
- * @param token
- */
- public LoginUser checkUserTokenIsEffect(String token) throws AuthenticationException {
- // 解密获得username,用于和数据库进行对比
- String username = JwtUtil.getUsername(token);
- if (username == null) {
- throw new AuthenticationException("token非法无效!");
- }
- // 查询用户信息
- log.debug("———校验token是否有效————checkUserTokenIsEffect——————— "+ token);
- LoginUser loginUser = commonAPI.getUserByName(username);
- if (loginUser == null) {
- throw new AuthenticationException("用户不存在!");
- }
- // 判断用户状态
- if (loginUser.getStatus() != 1) {
- throw new AuthenticationException("账号已被锁定,请联系管理员!");
- }
- // 校验token是否超时失效 & 或者账号密码是否错误
- if (!jwtTokenRefresh(token, username, loginUser.getPassword())) {
- throw new AuthenticationException("Token失效,请重新登录!");
- }
- //update-begin-author:taoyan date:20210609 for:校验用户的tenant_id和前端传过来的是否一致
- String userTenantIds = loginUser.getRelTenantIds();
- if(oConvertUtils.isNotEmpty(userTenantIds)){
- String contextTenantId = TenantContext.getTenant();
- if(oConvertUtils.isNotEmpty(contextTenantId) && !"0".equals(contextTenantId)){
- if(String.join(",",userTenantIds).indexOf(contextTenantId)<0){
- throw new AuthenticationException("用户租户信息变更,请重新登陆!");
- }
- }
- }
- //update-end-author:taoyan date:20210609 for:校验用户的tenant_id和前端传过来的是否一致
- return loginUser;
- }
- /**
- * JWTToken刷新生命周期 (实现: 用户在线操作不掉线功能)
- * 1、登录成功后将用户的JWT生成的Token作为k、v存储到cache缓存里面(这时候k、v值一样),缓存有效期设置为Jwt有效时间的2倍
- * 2、当该用户再次请求时,通过JWTFilter层层校验之后会进入到doGetAuthenticationInfo进行身份验证
- * 3、当该用户这次请求jwt生成的token值已经超时,但该token对应cache中的k还是存在,则表示该用户一直在操作只是JWT的token失效了,程序会给token对应的k映射的v值重新生成JWTToken并覆盖v值,该缓存生命周期重新计算
- * 4、当该用户这次请求jwt在生成的token值已经超时,并在cache中不存在对应的k,则表示该用户账户空闲超时,返回用户信息已失效,请重新登录。
- * 注意: 前端请求Header中设置Authorization保持不变,校验有效性以缓存中的token为准。
- * 用户过期时间 = Jwt有效时间 * 2。
- *
- * @param userName
- * @param passWord
- * @return
- */
- public boolean jwtTokenRefresh(String token, String userName, String passWord) {
- String cacheToken = String.valueOf(redisUtil.get(CommonConstant.PREFIX_USER_TOKEN + token));
- if (oConvertUtils.isNotEmpty(cacheToken)) {
- // 校验token有效性
- if (!JwtUtil.verify(cacheToken, userName, passWord)) {
- String newAuthorization = JwtUtil.sign(userName, passWord);
- // 设置超时时间
- redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, newAuthorization);
- redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME *2 / 1000);
- log.debug("——————————用户在线操作,更新token保证不掉线—————————jwtTokenRefresh——————— "+ token);
- }
- //update-begin--Author:scott Date:20191005 for:解决每次请求,都重写redis中 token缓存问题
- // else {
- // // 设置超时时间
- // redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, cacheToken);
- // redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME / 1000);
- // }
- //update-end--Author:scott Date:20191005 for:解决每次请求,都重写redis中 token缓存问题
- return true;
- }
- return false;
- }
- /**
- * 清除当前用户的权限认证缓存
- *
- * @param principals 权限信息
- */
- @Override
- public void clearCache(PrincipalCollection principals) {
- super.clearCache(principals);
- }
- }
|