ShiroRealm.java
8.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
package com.skua.modules.shiro.authc;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.skua.core.api.ISysBaseAPI;
import com.skua.core.api.vo.LoginUser;
import com.skua.core.cache.RedisUtil;
import com.skua.core.constant.CommonConstant;
import com.skua.core.context.BaseContextHandler;
import com.skua.core.context.SpringContextUtils;
import com.skua.core.util.ConvertUtils;
import com.skua.core.util.JwtUtil;
import com.skua.modules.system.entity.SysDepart;
import com.skua.modules.system.entity.SysUser;
import com.skua.modules.system.service.ISysDepartService;
import com.skua.modules.system.service.ISysUserService;
import liquibase.util.CollectionUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.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.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 用户登录鉴权和获取用户授权
*/
@Component
@Slf4j
public class ShiroRealm extends AuthorizingRealm {
@Autowired
@Lazy
private ISysUserService sysUserService;
@Autowired
@Lazy
private ISysDepartService sysDepartService;
@Autowired
@Lazy
private RedisUtil redisUtil;
@Autowired
private ISysBaseAPI sysBaseAPI;
/**
* 必须重写此方法,不然Shiro会报错
*/
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JwtToken;
}
/**
* 功能: 获取用户权限信息,包括角色以及权限。只有当触发检测用户权限时才会调用此方法,例如checkRole,checkPermission
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
log.info("————权限认证 [ roles、permissions]————");
LoginUser sysUser = null;
String username = null;
if (principals != null) {
sysUser = (LoginUser) principals.getPrimaryPrincipal();
username = sysUser.getUsername();
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 设置用户拥有的角色集合,比如“admin,test”
Set<String> roleSet = sysUserService.getUserRolesSet(username);
info.setRoles(roleSet);
// 设置用户拥有的权限集合,比如“sys:role:add,sys:user:add”
Set<String> permissionSet = sysUserService.getUserPermissionsSet(username);
info.addStringPermissions(permissionSet);
return info;
}
/**
* 功能: 用来进行身份认证,也就是说验证用户输入的账号和密码是否正确,获取身份验证信息,错误抛出异常
* @return 返回封装了用户信息的 AuthenticationInfo 实例
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
String token = (String) auth.getCredentials();
if (token == null) {
log.info("————————身份认证失败——————————IP地址: "+ ConvertUtils.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 userLoginName = JwtUtil.getUsername(token);
if (userLoginName == null) {
throw new AuthenticationException("token非法无效!");
}
// 查询用户信息
LoginUser loginUser = new LoginUser();
SysUser sysUser = sysUserService.getUserByName(userLoginName);
if (sysUser == null) {
throw new AuthenticationException("用户不存在!");
}
// 校验token是否超时失效 & 或者账号密码是否错误
if (!jwtTokenRefresh(token, userLoginName, sysUser.getPassword())) {
throw new AuthenticationException("Token失效,请重新登录!");
}
// 判断用户状态
if (sysUser.getStatus() != 1) {
throw new AuthenticationException("账号已被锁定,请联系管理员!");
}
// 获取部门信息集合
Map<String,String> departInfoMap = sysDepartService.getAllDepartByUserId(sysUser.getId());
// 用户分管部门id集合
String departIds = departInfoMap.get("departIds");
String orgCode = departInfoMap.get("orgCode");
// 用户真实部门id集合
String realDepartId = departInfoMap.get("realDepartId");
//获取用户角色编码
List<String> roleList = sysBaseAPI.getRolesByUserId(sysUser.getId());
String roles = roleList.stream().map(s -> s.trim()).collect(Collectors.joining(","));
//将登陆用户加入到线程共享遍历
BaseContextHandler.setUserLoginName(userLoginName);//登录名
BaseContextHandler.setUserId(sysUser.getId());//用户id
BaseContextHandler.setDeparts(departIds);
BaseContextHandler.setUserName(sysUser.getRealname());//真实名称
BaseContextHandler.setRoles(roles);
BaseContextHandler.setRealDepartId(realDepartId);
BaseContextHandler.setToken(token);
BaseContextHandler.setOrgCode(orgCode);
BaseContextHandler.set("userType",sysUser.getUserType());
LambdaQueryWrapper<SysDepart> sysDepartLambdaQueryWrapper = new LambdaQueryWrapper<>();
if(!"admin".equals(sysUser.getUsername())){
sysDepartLambdaQueryWrapper.in(StringUtils.isNotBlank(departIds), SysDepart::getId, Arrays.asList(departIds.split(",")));
}
sysDepartLambdaQueryWrapper.isNotNull(SysDepart::getSv30OrgCode).last(" and sv30_org_code != ''");
List<SysDepart> list = sysDepartService.list(sysDepartLambdaQueryWrapper);
if(!CollectionUtils.isEmpty(list)){
BaseContextHandler.set("sv30OrgCodes",list.stream().map(SysDepart::getSv30OrgCode).collect(Collectors.joining(",")));
}
//资源属性复制
BeanUtils.copyProperties(sysUser, loginUser);
return loginUser;
}
/**
* JWTToken刷新生命周期 (解决用户一直在线操作,提供Token失效问题)
* 1、登录成功后将用户的JWT生成的Token作为k、v存储到cache缓存里面(这时候k、v值一样)
* 2、当该用户再次请求时,通过JWTFilter层层校验之后会进入到doGetAuthenticationInfo进行身份验证
* 3、当该用户这次请求JWTToken值还在生命周期内,则会通过重新PUT的方式k、v都为Token值,缓存中的token值生命周期时间重新计算(这时候k、v值一样)
* 4、当该用户这次请求jwt生成的token值已经超时,但该token对应cache中的k还是存在,则表示该用户一直在操作只是JWT的token失效了,程序会给token对应的k映射的v值重新生成JWTToken并覆盖v值,该缓存生命周期重新计算
* 5、当该用户这次请求jwt在生成的token值已经超时,并在cache中不存在对应的k,则表示该用户账户空闲超时,返回用户信息已失效,请重新登录。
* 6、每次当返回为true情况下,都会给Response的Header中设置Authorization,该Authorization映射的v为cache对应的v值。
* 7、注:当前端接收到Response的Header中的Authorization值会存储起来,作为以后请求token使用
* 参考方案:https://blog.csdn.net/qq394829044/article/details/82763936
*
* @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 (ConvertUtils.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 / 1000);
} else {
redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, cacheToken);
// 设置超时时间
redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME / 1000);
}
return true;
}
return false;
}
}