| 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@Slf4jpublic 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);    }}
 |