inleft
2022-02-09 9bcb19959eeb9da9bde2561e7278f6d0a55eb151
commit | author | age
9bcb19 1 /*
I 2 Copyright [2020] [https://www.xiaonuo.vip]
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8   http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15
16 Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
17
18 1.请不要删除和修改根目录下的LICENSE文件。
19 2.请不要删除和修改Snowy源码头部的版权声明。
20 3.请保留源码和相关描述文件的项目出处,作者声明等。
21 4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy
22 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy
23 6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
24  */
25 package vip.xiaonuo.sys.modular.auth.service.impl;
26
27 import cn.hutool.core.bean.BeanUtil;
28 import cn.hutool.core.convert.Convert;
29 import cn.hutool.core.date.DateTime;
30 import cn.hutool.core.util.ObjectUtil;
31 import cn.hutool.core.util.StrUtil;
32 import cn.hutool.extra.spring.SpringUtil;
33 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
34 import org.springframework.security.core.Authentication;
35 import org.springframework.security.core.context.SecurityContextHolder;
36 import org.springframework.security.core.userdetails.UserDetailsService;
37 import org.springframework.security.core.userdetails.UsernameNotFoundException;
38 import org.springframework.stereotype.Service;
39 import org.springframework.web.context.request.RequestContextHolder;
40 import org.springframework.web.context.request.ServletRequestAttributes;
41 import vip.xiaonuo.core.consts.CommonConstant;
42 import vip.xiaonuo.core.context.constant.ConstantContextHolder;
43 import vip.xiaonuo.core.dbs.CurrentDataSourceContext;
44 import vip.xiaonuo.core.enums.CommonStatusEnum;
45 import vip.xiaonuo.core.exception.AuthException;
46 import vip.xiaonuo.core.exception.ServiceException;
47 import vip.xiaonuo.core.exception.enums.AuthExceptionEnum;
48 import vip.xiaonuo.core.exception.enums.ServerExceptionEnum;
49 import vip.xiaonuo.core.pojo.login.SysLoginUser;
50 import vip.xiaonuo.core.tenant.context.TenantCodeHolder;
51 import vip.xiaonuo.core.tenant.context.TenantDbNameHolder;
52 import vip.xiaonuo.core.tenant.entity.TenantInfo;
53 import vip.xiaonuo.core.tenant.exception.TenantException;
54 import vip.xiaonuo.core.tenant.exception.enums.TenantExceptionEnum;
55 import vip.xiaonuo.core.tenant.service.TenantInfoService;
56 import vip.xiaonuo.core.util.CryptogramUtil;
57 import vip.xiaonuo.core.util.HttpServletUtil;
58 import vip.xiaonuo.core.util.IpAddressUtil;
59 import vip.xiaonuo.sys.core.cache.UserCache;
60 import vip.xiaonuo.sys.core.enums.LogSuccessStatusEnum;
61 import vip.xiaonuo.sys.core.jwt.JwtPayLoad;
62 import vip.xiaonuo.sys.core.jwt.JwtTokenUtil;
63 import vip.xiaonuo.sys.core.log.LogManager;
64 import vip.xiaonuo.sys.modular.auth.factory.LoginUserFactory;
65 import vip.xiaonuo.sys.modular.auth.service.AuthService;
66 import vip.xiaonuo.sys.modular.user.entity.SysUser;
67 import vip.xiaonuo.sys.modular.user.service.SysUserService;
68
69 import javax.annotation.Resource;
70 import javax.servlet.http.HttpServletRequest;
71 import java.util.Map;
72
73 /**
74  * 认证相关service实现类
75  *
76  * @author xuyuxiang
77  * @date 2020/3/11 16:58
78  */
79 @Service
80 public class AuthServiceImpl implements AuthService, UserDetailsService {
81
82     @Resource
83     private SysUserService sysUserService;
84
85     @Resource
86     private UserCache userCache;
87
88     @Override
89     public String login(String account, String password) {
90
91         if (ObjectUtil.hasEmpty(account, password)) {
92             LogManager.me().executeLoginLog(account, LogSuccessStatusEnum.FAIL.getCode(), AuthExceptionEnum.ACCOUNT_PWD_EMPTY.getMessage());
93             throw new AuthException(AuthExceptionEnum.ACCOUNT_PWD_EMPTY);
94         }
95
96         SysUser sysUser = sysUserService.getUserByCount(account);
97
98         //用户不存在,账号或密码错误
99         if (ObjectUtil.isEmpty(sysUser)) {
100             LogManager.me().executeLoginLog(account, LogSuccessStatusEnum.FAIL.getCode(), AuthExceptionEnum.ACCOUNT_PWD_ERROR.getMessage());
101             throw new AuthException(AuthExceptionEnum.ACCOUNT_PWD_ERROR);
102         }
103
104         String passwordHashValue = sysUser.getPwdHashValue();
105
106         // 解密密码(这里是前端传来的,如果不需要可将其干掉,这里不做开关)
107         password = CryptogramUtil.doSm2Decrypt(password);
108
109         //验证账号密码是否正确
110         if (ObjectUtil.isEmpty(passwordHashValue) || !passwordHashValue.equals(CryptogramUtil.doHashValue(password))) {
111             LogManager.me().executeLoginLog(sysUser.getAccount(), LogSuccessStatusEnum.FAIL.getCode(), AuthExceptionEnum.ACCOUNT_PWD_ERROR.getMessage());
112             throw new AuthException(AuthExceptionEnum.ACCOUNT_PWD_ERROR);
113         }
114
115         return doLogin(sysUser);
116     }
117
118     @Override
119     public String doLogin(SysUser sysUser) {
120
121         Integer sysUserStatus = sysUser.getStatus();
122
123         //验证账号是否被冻结
124         if (CommonStatusEnum.DISABLE.getCode().equals(sysUserStatus)) {
125             LogManager.me().executeLoginLog(sysUser.getAccount(), LogSuccessStatusEnum.FAIL.getCode(), AuthExceptionEnum.ACCOUNT_FREEZE_ERROR.getMessage());
126             throw new AuthException(AuthExceptionEnum.ACCOUNT_FREEZE_ERROR);
127         }
128
129         //构造SysLoginUser
130         SysLoginUser sysLoginUser = this.genSysLoginUser(sysUser);
131
132         //构造jwtPayLoad
133         JwtPayLoad jwtPayLoad = new JwtPayLoad(sysUser.getId(), sysUser.getAccount());
134
135         //生成token
136         String token = JwtTokenUtil.generateToken(jwtPayLoad);
137
138         //缓存token与登录用户信息对应, 默认2个小时
139         this.cacheLoginUser(jwtPayLoad, sysLoginUser);
140
141         //设置最后登录ip和时间
142         sysUser.setLastLoginIp(IpAddressUtil.getIp(HttpServletUtil.getRequest()));
143         sysUser.setLastLoginTime(DateTime.now());
144
145         //更新用户登录信息
146         sysUserService.updateById(sysUser);
147
148         //登录成功,记录登录日志
149         LogManager.me().executeLoginLog(sysUser.getAccount(), LogSuccessStatusEnum.SUCCESS.getCode(), null);
150
151         //登录成功,设置SpringSecurityContext上下文,方便获取用户
152         this.setSpringSecurityContextAuthentication(sysLoginUser);
153
154         //如果开启限制单用户登陆,则踢掉原来的用户
155         Boolean enableSingleLogin = ConstantContextHolder.getEnableSingleLogin();
156         if (enableSingleLogin) {
157
158             //获取所有的登陆用户
159             Map<String, SysLoginUser> allLoginUsers = userCache.getAllKeyValues();
160             for (Map.Entry<String, SysLoginUser> loginedUserEntry : allLoginUsers.entrySet()) {
161
162                 String loginedUserKey = loginedUserEntry.getKey();
163                 SysLoginUser loginedUser = loginedUserEntry.getValue();
164
165                 //如果账号名称相同,并且redis缓存key和刚刚生成的用户的uuid不一样,则清除以前登录的
166                 if (loginedUser.getName().equals(sysUser.getName())
167                         && !loginedUserKey.equals(jwtPayLoad.getUuid())) {
168                     this.clearUser(loginedUserKey, loginedUser.getAccount());
169                 }
170             }
171         }
172
173         //返回token
174         return token;
175     }
176
177     @Override
178     public String getTokenFromRequest(HttpServletRequest request) {
179         String authToken = request.getHeader(CommonConstant.AUTHORIZATION);
180         if (ObjectUtil.isEmpty(authToken) || CommonConstant.UNDEFINED.equals(authToken)) {
181             return null;
182         } else {
183             //token不是以Bearer打头,则响应回格式不正确
184             if (!authToken.startsWith(CommonConstant.TOKEN_TYPE_BEARER)) {
185                 throw new AuthException(AuthExceptionEnum.NOT_VALID_TOKEN_TYPE);
186             }
187             try {
188                 authToken = authToken.substring(CommonConstant.TOKEN_TYPE_BEARER.length() + 1);
189             } catch (StringIndexOutOfBoundsException e) {
190                 throw new AuthException(AuthExceptionEnum.NOT_VALID_TOKEN_TYPE);
191             }
192             // 判断是否开启了加密
193             if (ConstantContextHolder.getCryptogramConfigs().getTokenEncDec()) {
194                 // 解密token
195                 authToken = CryptogramUtil.doDecrypt(authToken);
196             }
197         }
198
199         return authToken;
200     }
201
202     @Override
203     public SysLoginUser getLoginUserByToken(String token) {
204
205         //校验token,错误则抛异常
206         this.checkToken(token);
207
208         //根据token获取JwtPayLoad部分
209         JwtPayLoad jwtPayLoad = JwtTokenUtil.getJwtPayLoad(token);
210
211         //从redis缓存中获取登录用户
212         Object cacheObject = userCache.get(jwtPayLoad.getUuid());
213
214         //用户不存在则表示登录已过期
215         if (ObjectUtil.isEmpty(cacheObject)) {
216             throw new AuthException(AuthExceptionEnum.LOGIN_EXPIRED);
217         }
218
219         //转换成登录用户
220         SysLoginUser sysLoginUser = (SysLoginUser) cacheObject;
221
222         //用户存在, 无痛刷新缓存,在登录过期前活动的用户自动刷新缓存时间
223         this.cacheLoginUser(jwtPayLoad, sysLoginUser);
224
225         //返回用户
226         return sysLoginUser;
227     }
228
229     @Override
230     public void logout() {
231
232         HttpServletRequest request = HttpServletUtil.getRequest();
233
234         if (ObjectUtil.isNotNull(request)) {
235
236             //获取token
237             String token = this.getTokenFromRequest(request);
238
239             //如果token为空直接返回
240             if (ObjectUtil.isEmpty(token)) {
241                 return;
242             }
243
244             //校验token,错误则抛异常,待确定
245             this.checkToken(token);
246
247             //根据token获取JwtPayLoad部分
248             JwtPayLoad jwtPayLoad = JwtTokenUtil.getJwtPayLoad(token);
249
250             //获取缓存的key
251             String loginUserCacheKey = jwtPayLoad.getUuid();
252             this.clearUser(loginUserCacheKey, jwtPayLoad.getAccount());
253
254         } else {
255             throw new ServiceException(ServerExceptionEnum.REQUEST_EMPTY);
256         }
257     }
258
259     @Override
260     public void setSpringSecurityContextAuthentication(SysLoginUser sysLoginUser) {
261         UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
262                 new UsernamePasswordAuthenticationToken(
263                         sysLoginUser,
264                         null,
265                         sysLoginUser.getAuthorities());
266         SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
267     }
268
269     @Override
270     public Authentication getAuthentication() {
271         return SecurityContextHolder.getContext().getAuthentication();
272     }
273
274     @Override
275     public void checkToken(String token) {
276         //校验token是否正确
277         Boolean tokenCorrect = JwtTokenUtil.checkToken(token);
278         if (!tokenCorrect) {
279             throw new AuthException(AuthExceptionEnum.REQUEST_TOKEN_ERROR);
280         }
281
282         //校验token是否失效
283         Boolean tokenExpired = JwtTokenUtil.isTokenExpired(token);
284         if (tokenExpired) {
285             throw new AuthException(AuthExceptionEnum.LOGIN_EXPIRED);
286         }
287     }
288
289     @Override
290     public void cacheTenantInfo(String tenantCode) {
291         if (StrUtil.isBlank(tenantCode)) {
292             return;
293         }
294
295         // 从spring容器中获取service,如果没开多租户功能,没引入相关包,这里会报错
296         TenantInfoService tenantInfoService = null;
297         try {
298             tenantInfoService = SpringUtil.getBean(TenantInfoService.class);
299         } catch (Exception e) {
300             throw new TenantException(TenantExceptionEnum.TENANT_MODULE_NOT_ENABLE_ERROR);
301         }
302
303         // 获取租户信息
304         TenantInfo tenantInfo = tenantInfoService.getByCode(tenantCode);
305         if (tenantInfo != null) {
306             String dbName = tenantInfo.getDbName();
307
308             // 租户编码的临时存放
309             TenantCodeHolder.put(tenantCode);
310
311             // 租户的数据库名称临时缓存
312             TenantDbNameHolder.put(dbName);
313
314             // 数据源信息临时缓存
315             CurrentDataSourceContext.setDataSourceType(dbName);
316         } else {
317             throw new TenantException(TenantExceptionEnum.CNAT_FIND_TENANT_ERROR);
318         }
319     }
320
321     @Override
322     public SysLoginUser loadUserByUsername(String account) throws UsernameNotFoundException {
323         SysLoginUser sysLoginUser = new SysLoginUser();
324         SysUser user = sysUserService.getUserByCount(account);
325         BeanUtil.copyProperties(user, sysLoginUser);
326         return sysLoginUser;
327     }
328
329     /**
330      * 根据key清空登陆信息
331      *
332      * @author xuyuxiang
333      * @date 2020/6/19 12:28
334      */
335     private void clearUser(String loginUserKey, String account) {
336         //获取缓存的用户
337         Object cacheObject = userCache.get(loginUserKey);
338
339         //如果缓存的用户存在,清除会话,否则表示该会话信息已失效,不执行任何操作
340         if (ObjectUtil.isNotEmpty(cacheObject)) {
341             //清除登录会话
342             userCache.remove(loginUserKey);
343             //创建退出登录日志
344             LogManager.me().executeExitLog(account);
345         }
346     }
347
348     /**
349      * 构造登录用户信息
350      *
351      * @author xuyuxiang
352      * @date 2020/3/12 17:32
353      */
354     @Override
355     public SysLoginUser genSysLoginUser(SysUser sysUser) {
356         SysLoginUser sysLoginUser = new SysLoginUser();
357         BeanUtil.copyProperties(sysUser, sysLoginUser);
358         LoginUserFactory.fillLoginUserInfo(sysLoginUser);
359         return sysLoginUser;
360     }
361
362     /**
363      * 缓存token与登录用户信息对应, 默认2个小时
364      *
365      * @author xuyuxiang
366      * @date 2020/3/13 14:51
367      */
368     private void cacheLoginUser(JwtPayLoad jwtPayLoad, SysLoginUser sysLoginUser) {
369         String redisLoginUserKey = jwtPayLoad.getUuid();
370         userCache.put(redisLoginUserKey, sysLoginUser, Convert.toLong(ConstantContextHolder.getSessionTokenExpireSec()));
371     }
372
373     @Override
374     public void refreshUserDataScope(Long orgId) {
375         // request获取到token
376         HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
377         String token = this.getTokenFromRequest(request);
378         SysLoginUser sysLoginUser = this.getLoginUserByToken(token);
379         sysLoginUser.getDataScopes().add(orgId);
380         this.cacheLoginUser(JwtTokenUtil.getJwtPayLoad(token), sysLoginUser);
381     }
382 }