|
@@ -0,0 +1,210 @@
|
|
|
+package com.gis.admin.shiro;
|
|
|
+
|
|
|
+import com.gis.common.constant.ConfigConstant;
|
|
|
+import com.gis.common.util.JwtUtil;
|
|
|
+import com.gis.admin.entity.po.SysUserEntity;
|
|
|
+import com.gis.admin.service.SysResourceService;
|
|
|
+import com.gis.admin.service.SysUserService;
|
|
|
+import com.gis.common.util.RedisUtil;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+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.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.data.redis.core.RedisTemplate;
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
+import java.util.HashSet;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Set;
|
|
|
+
|
|
|
+/**
|
|
|
+ * @Description: 用户登录鉴权和获取用户授权
|
|
|
+ * @Author: owen
|
|
|
+ * @Date: 2021-07-07
|
|
|
+ * @Version: 1.1
|
|
|
+ */
|
|
|
+@Component
|
|
|
+@Slf4j
|
|
|
+public class ShiroRealm extends AuthorizingRealm {
|
|
|
+// @Lazy
|
|
|
+// @Resource
|
|
|
+// private CommonAPI commonAPI;
|
|
|
+
|
|
|
+// @Lazy
|
|
|
+// @Resource
|
|
|
+// private RedisUtil redisUtil;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ HttpServletRequest request;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ SysResourceService sysResourceService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ SysUserService sysUserService;
|
|
|
+
|
|
|
+// @Autowired
|
|
|
+// RedisTemplate<String, String> redisTemplate;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ ConfigConstant configConstant;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ RedisUtil redisUtil;
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 必须重写此方法,不然Shiro会报错
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public boolean supports(AuthenticationToken token) {
|
|
|
+ return token instanceof JwtToken;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 权限信息认证(包括角色以及权限)是用户访问controller的时候才进行验证(redis存储的此处权限信息)
|
|
|
+ * 触发检测用户权限时才会调用此方法,例如checkRole,checkPermission
|
|
|
+ *
|
|
|
+ * 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的
|
|
|
+ *
|
|
|
+ * @param principals 身份信息
|
|
|
+ * @return AuthorizationInfo 权限信息
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
|
|
|
+ log.info("===============Shiro权限认证开始============ [ roles、permissions]==========");
|
|
|
+ Long userId = null;
|
|
|
+
|
|
|
+ SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
|
|
|
+
|
|
|
+ String token = request.getHeader("token");
|
|
|
+ log.info("token: {}", token);
|
|
|
+ if (StringUtils.isNotBlank(token)){
|
|
|
+ List userRole = JwtUtil.getUserRole(token);
|
|
|
+ userId = JwtUtil.getUserId(token);
|
|
|
+ // list to set 添加角色sys_admin,sys
|
|
|
+ info.setRoles(new HashSet<>(userRole));
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 设置用户拥有的权限集合,比如“sys:role:add,sys:user:add”
|
|
|
+ Set<String> permissions = sysResourceService.getPermissionByUserId(userId);
|
|
|
+ info.addStringPermissions(permissions);
|
|
|
+ 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();
|
|
|
+ log.info("token: {}", token);
|
|
|
+ if (token == null) {
|
|
|
+ throw new JwtAuthenticationException(5001, "header token is null");
|
|
|
+ }
|
|
|
+ // 校验token有效性
|
|
|
+ SysUserEntity sysUserEntity = this.checkUserTokenIsEffect(token);
|
|
|
+ return new SimpleAuthenticationInfo(sysUserEntity, token, getName());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验token的有效性
|
|
|
+ *
|
|
|
+ * @param token
|
|
|
+ */
|
|
|
+ private SysUserEntity checkUserTokenIsEffect(String token) throws AuthenticationException {
|
|
|
+ // 解密获得username,用于和数据库进行对比
|
|
|
+ String username = JwtUtil.getUsername(token);
|
|
|
+ if (username == null) {
|
|
|
+ throw new JwtAuthenticationException(5001, "token invalid");
|
|
|
+ }
|
|
|
+
|
|
|
+// String redisToken = redisTemplate.opsForValue().get(configConstant.redisPrefix + token);
|
|
|
+ String redisToken = (String)redisUtil.get(configConstant.redisPrefix + token);
|
|
|
+
|
|
|
+ if (!token.equals(redisToken)) {
|
|
|
+ log.error("redis token is null");
|
|
|
+ throw new JwtAuthenticationException(5001, "redis token is null");
|
|
|
+
|
|
|
+ }
|
|
|
+ // 查询用户信息
|
|
|
+ if (! JwtUtil.isVerify(token, username)) {
|
|
|
+ log.error("error token username or password");
|
|
|
+ throw new JwtAuthenticationException(5001, "token invalid");
|
|
|
+ }
|
|
|
+
|
|
|
+ SysUserEntity userEntity = sysUserService.findByUserName(username);
|
|
|
+ if (userEntity == null) {
|
|
|
+ log.error("error token userEntity");
|
|
|
+ throw new JwtAuthenticationException(5001, "User didn't existed!");
|
|
|
+ }
|
|
|
+
|
|
|
+ return userEntity;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 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);
|
|
|
+ }
|
|
|
+
|
|
|
+}
|