d3df35b76ebea388a70ac393f0cdc5f323b3296a.svn-base 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. package org.jeecg.config.shiro;
  2. import cn.hutool.crypto.SecureUtil;
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.apache.shiro.authc.AuthenticationException;
  5. import org.apache.shiro.authc.AuthenticationInfo;
  6. import org.apache.shiro.authc.AuthenticationToken;
  7. import org.apache.shiro.authc.SimpleAuthenticationInfo;
  8. import org.apache.shiro.authz.AuthorizationInfo;
  9. import org.apache.shiro.authz.SimpleAuthorizationInfo;
  10. import org.apache.shiro.realm.AuthorizingRealm;
  11. import org.apache.shiro.subject.PrincipalCollection;
  12. import org.jeecg.common.api.CommonAPI;
  13. import org.jeecg.common.constant.CacheConstant;
  14. import org.jeecg.common.constant.CommonConstant;
  15. import org.jeecg.common.system.util.JwtUtil;
  16. import org.jeecg.common.system.vo.LoginUser;
  17. import org.jeecg.common.util.RedisUtil;
  18. import org.jeecg.common.util.SpringContextUtils;
  19. import org.jeecg.common.util.oConvertUtils;
  20. import org.jeecg.config.mybatis.TenantContext;
  21. import org.springframework.context.annotation.Lazy;
  22. import org.springframework.stereotype.Component;
  23. import javax.annotation.Resource;
  24. import java.util.Set;
  25. /**
  26. * @Description: 用户登录鉴权和获取用户授权
  27. * @Author: Scott
  28. * @Date: 2019-4-23 8:13
  29. * @Version: 1.1
  30. */
  31. @Component
  32. @Slf4j
  33. public class ShiroRealm extends AuthorizingRealm {
  34. @Lazy
  35. @Resource
  36. private CommonAPI commonAPI;
  37. @Lazy
  38. @Resource
  39. private RedisUtil redisUtil;
  40. /**
  41. * 必须重写此方法,不然Shiro会报错
  42. */
  43. @Override
  44. public boolean supports(AuthenticationToken token) {
  45. return token instanceof JwtToken;
  46. }
  47. /**
  48. * 权限信息认证(包括角色以及权限)是用户访问controller的时候才进行验证(redis存储的此处权限信息)
  49. * 触发检测用户权限时才会调用此方法,例如checkRole,checkPermission
  50. *
  51. * @param principals 身份信息
  52. * @return AuthorizationInfo 权限信息
  53. */
  54. @Override
  55. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  56. log.info("===============Shiro权限认证开始============ [ roles、permissions]==========");
  57. String username = null;
  58. if (principals != null) {
  59. LoginUser sysUser = (LoginUser) principals.getPrimaryPrincipal();
  60. username = sysUser.getUsername();
  61. }
  62. SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
  63. // 设置用户拥有的角色集合,比如“admin,test”
  64. Set<String> roleSet = commonAPI.queryUserRoles(username);
  65. System.out.println(roleSet.toString());
  66. info.setRoles(roleSet);
  67. // 设置用户拥有的权限集合,比如“sys:role:add,sys:user:add”
  68. Set<String> permissionSet = commonAPI.queryUserAuths(username);
  69. info.addStringPermissions(permissionSet);
  70. System.out.println(permissionSet);
  71. log.info("===============Shiro权限认证成功==============");
  72. return info;
  73. }
  74. /**
  75. * 用户信息认证是在用户进行登录的时候进行验证(不存redis)
  76. * 也就是说验证用户输入的账号和密码是否正确,错误抛出异常
  77. *
  78. * @param auth 用户登录的账号密码信息
  79. * @return 返回封装了用户信息的 AuthenticationInfo 实例
  80. * @throws AuthenticationException
  81. */
  82. @Override
  83. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
  84. log.debug("===============Shiro身份认证开始============doGetAuthenticationInfo==========");
  85. String token = (String) auth.getCredentials();
  86. if (token == null) {
  87. log.info("————————身份认证失败——————————IP地址: "+ oConvertUtils.getIpAddrByRequest(SpringContextUtils.getHttpServletRequest()));
  88. throw new AuthenticationException("token为空!");
  89. }
  90. // 校验token有效性
  91. LoginUser loginUser = this.checkUserTokenIsEffect(token);
  92. return new SimpleAuthenticationInfo(loginUser, token, getName());
  93. }
  94. /**
  95. * 校验token的有效性
  96. *
  97. * @param token
  98. */
  99. public LoginUser checkUserTokenIsEffect(String token) throws AuthenticationException {
  100. // 解密获得username,用于和数据库进行对比
  101. String username = JwtUtil.getUsername(token);
  102. if (username == null) {
  103. throw new AuthenticationException("token非法无效!");
  104. }
  105. // 查询用户信息
  106. log.debug("———校验token是否有效————checkUserTokenIsEffect——————— "+ token);
  107. LoginUser loginUser = commonAPI.getUserByName(username);
  108. if (loginUser == null) {
  109. throw new AuthenticationException("用户不存在!");
  110. }
  111. // 判断用户状态
  112. if (loginUser.getStatus() != 1) {
  113. throw new AuthenticationException("账号已被锁定,请联系管理员!");
  114. }
  115. // 校验token是否超时失效 & 或者账号密码是否错误
  116. if (!jwtTokenRefresh(token, username, loginUser.getPassword())) {
  117. throw new AuthenticationException("Token失效,请重新登录!");
  118. }
  119. //update-begin-author:taoyan date:20210609 for:校验用户的tenant_id和前端传过来的是否一致
  120. String userTenantIds = loginUser.getRelTenantIds();
  121. if(oConvertUtils.isNotEmpty(userTenantIds)){
  122. String contextTenantId = TenantContext.getTenant();
  123. if(oConvertUtils.isNotEmpty(contextTenantId) && !"0".equals(contextTenantId)){
  124. if(String.join(",",userTenantIds).indexOf(contextTenantId)<0){
  125. throw new AuthenticationException("用户租户信息变更,请重新登陆!");
  126. }
  127. }
  128. }
  129. //update-end-author:taoyan date:20210609 for:校验用户的tenant_id和前端传过来的是否一致
  130. return loginUser;
  131. }
  132. /**
  133. * JWTToken刷新生命周期 (实现: 用户在线操作不掉线功能)
  134. * 1、登录成功后将用户的JWT生成的Token作为k、v存储到cache缓存里面(这时候k、v值一样),缓存有效期设置为Jwt有效时间的2倍
  135. * 2、当该用户再次请求时,通过JWTFilter层层校验之后会进入到doGetAuthenticationInfo进行身份验证
  136. * 3、当该用户这次请求jwt生成的token值已经超时,但该token对应cache中的k还是存在,则表示该用户一直在操作只是JWT的token失效了,程序会给token对应的k映射的v值重新生成JWTToken并覆盖v值,该缓存生命周期重新计算
  137. * 4、当该用户这次请求jwt在生成的token值已经超时,并在cache中不存在对应的k,则表示该用户账户空闲超时,返回用户信息已失效,请重新登录。
  138. * 注意: 前端请求Header中设置Authorization保持不变,校验有效性以缓存中的token为准。
  139. * 用户过期时间 = Jwt有效时间 * 2。
  140. *
  141. * @param userName
  142. * @param passWord
  143. * @return
  144. */
  145. public boolean jwtTokenRefresh(String token, String userName, String passWord) {
  146. String cacheToken = String.valueOf(redisUtil.get(CommonConstant.PREFIX_USER_TOKEN + token));
  147. if (oConvertUtils.isNotEmpty(cacheToken)) {
  148. // 校验token有效性
  149. if (!JwtUtil.verify(cacheToken, userName, passWord)) {
  150. String newAuthorization = JwtUtil.sign(userName, passWord);
  151. // 设置超时时间
  152. redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, newAuthorization);
  153. redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME *2 / 1000);
  154. log.debug("——————————用户在线操作,更新token保证不掉线—————————jwtTokenRefresh——————— "+ token);
  155. }
  156. //update-begin--Author:scott Date:20191005 for:解决每次请求,都重写redis中 token缓存问题
  157. // else {
  158. // // 设置超时时间
  159. // redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, cacheToken);
  160. // redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME / 1000);
  161. // }
  162. //update-end--Author:scott Date:20191005 for:解决每次请求,都重写redis中 token缓存问题
  163. return true;
  164. }
  165. return false;
  166. }
  167. /**
  168. * 清除当前用户的权限认证缓存
  169. *
  170. * @param principals 权限信息
  171. */
  172. @Override
  173. public void clearCache(PrincipalCollection principals) {
  174. super.clearCache(principals);
  175. }
  176. }