diff --git a/orion-ops-module-infra/orion-ops-module-infra-provider/src/main/java/com/orion/ops/module/infra/entity/dto/SystemUserDTO.java b/orion-ops-module-infra/orion-ops-module-infra-provider/src/main/java/com/orion/ops/module/infra/entity/dto/SystemUserDTO.java index f341f340..bf70d24b 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-provider/src/main/java/com/orion/ops/module/infra/entity/dto/SystemUserDTO.java +++ b/orion-ops-module-infra/orion-ops-module-infra-provider/src/main/java/com/orion/ops/module/infra/entity/dto/SystemUserDTO.java @@ -44,7 +44,7 @@ public class SystemUserDTO implements Serializable { private String email; @Schema(description = "用户状态 0正常 1停用 2锁定") - private Byte status; + private Integer status; @Schema(description = "最后登录时间") private Date lastLoginTime; diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/controller/AuthenticationController.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/controller/AuthenticationController.java new file mode 100644 index 00000000..74de4ed2 --- /dev/null +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/controller/AuthenticationController.java @@ -0,0 +1,58 @@ +package com.orion.ops.module.infra.controller; + +import com.orion.lang.define.wrapper.HttpWrapper; +import com.orion.ops.framework.common.annotation.IgnoreLog; +import com.orion.ops.framework.common.annotation.RestWrapper; +import com.orion.ops.module.infra.entity.request.UserLoginRequest; +import com.orion.ops.module.infra.entity.vo.UserLoginVO; +import com.orion.ops.module.infra.service.AuthenticationService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.annotation.security.PermitAll; +import javax.servlet.http.HttpServletRequest; + +/** + * 认证服务 + * + * @author Jiahang Li + * @version 1.0.0 + * @since 2023/7/14 11:20 + */ +@Tag(name = "infra - 认证服务") +@Slf4j +@Validated +@RestWrapper +@RestController +@RequestMapping("/infra/auth") +@SuppressWarnings({"ELValidationInJSP", "SpringElInspection"}) +public class AuthenticationController { + + @Resource + private AuthenticationService authenticationService; + + @PermitAll + @Operation(summary = "登陆") + @PostMapping("/login") + public UserLoginVO login(@Validated @RequestBody UserLoginRequest request, + HttpServletRequest servletRequest) { + // 验证登陆 + String token = authenticationService.login(request, servletRequest); + return UserLoginVO.builder().token(token).build(); + } + + @IgnoreLog + @PermitAll + @Operation(summary = "登出") + @GetMapping("/logout") + public HttpWrapper logout(HttpServletRequest servletRequest) { + // 登出 + authenticationService.logout(servletRequest); + return HttpWrapper.ok(); + } + +} diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/convert/SystemUserConvert.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/convert/SystemUserConvert.java index 0d9664f7..61cb2492 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/convert/SystemUserConvert.java +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/convert/SystemUserConvert.java @@ -1,12 +1,14 @@ package com.orion.ops.module.infra.convert; +import com.orion.ops.framework.common.security.LoginUser; +import com.orion.ops.module.infra.entity.domain.SystemUserDO; +import com.orion.ops.module.infra.entity.request.SystemUserCreateRequest; +import com.orion.ops.module.infra.entity.request.SystemUserQueryRequest; +import com.orion.ops.module.infra.entity.request.SystemUserUpdateRequest; +import com.orion.ops.module.infra.entity.vo.SystemUserVO; import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; -import com.orion.ops.module.infra.entity.domain.*; -import com.orion.ops.module.infra.entity.vo.*; -import com.orion.ops.module.infra.entity.dto.*; -import com.orion.ops.module.infra.entity.request.*; -import com.orion.ops.module.infra.convert.*; + import java.util.List; /** @@ -17,9 +19,8 @@ import java.util.List; * @since 2023-7-13 18:42 */ @Mapper -@SuppressWarnings("ALL") public interface SystemUserConvert { - + SystemUserConvert MAPPER = Mappers.getMapper(SystemUserConvert.class); SystemUserDO to(SystemUserCreateRequest request); @@ -28,8 +29,11 @@ public interface SystemUserConvert { SystemUserDO to(SystemUserQueryRequest request); - SystemUserVO to(SystemUserDO request); + SystemUserVO to(SystemUserDO domain); List to(List list); + LoginUser toLoginUser(SystemUserDO domain); + + } diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/convert/SystemUserProviderConvert.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/convert/SystemUserProviderConvert.java index da8c72f4..9ff97e20 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/convert/SystemUserProviderConvert.java +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/convert/SystemUserProviderConvert.java @@ -17,14 +17,13 @@ import java.util.List; * @since 2023-7-13 18:42 */ @Mapper -@SuppressWarnings("ALL") public interface SystemUserProviderConvert { SystemUserProviderConvert MAPPER = Mappers.getMapper(SystemUserProviderConvert.class); SystemUserDO to(SystemUserDTO dto); - SystemUserDTO to(SystemUserDO dto); + SystemUserDTO to(SystemUserDO domain); List toDO(List list); diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/define/InfraCacheKeyDefine.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/define/InfraCacheKeyDefine.java deleted file mode 100644 index c061f1ed..00000000 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/define/InfraCacheKeyDefine.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.orion.ops.module.infra.define; - -import com.orion.lang.define.cache.CacheKeyDefine; - -import java.util.concurrent.TimeUnit; - -/** - * 基建模块缓存 key - * - * @author Jiahang Li - * @version 1.0.0 - * @since 2023/7/13 21:54 - */ -public interface InfraCacheKeyDefine { - - CacheKeyDefine USER_INFO = new CacheKeyDefine("user:info:{}", "用户信息", 30, TimeUnit.DAYS); - - CacheKeyDefine USER_TOKEN = new CacheKeyDefine("user:token:{}", "用户认证 authenticationToken", 48, TimeUnit.HOURS); - - CacheKeyDefine USER_REFRESH = new CacheKeyDefine("user:refresh:{}", "用户认证 refreshToken", 54, TimeUnit.HOURS); - -} diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/define/UserCacheKeyDefine.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/define/UserCacheKeyDefine.java new file mode 100644 index 00000000..9429ee93 --- /dev/null +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/define/UserCacheKeyDefine.java @@ -0,0 +1,24 @@ +package com.orion.ops.module.infra.define; + +import com.orion.lang.define.cache.CacheKeyDefine; + +import java.util.concurrent.TimeUnit; + +/** + * 用户模块缓存 key + * + * @author Jiahang Li + * @version 1.0.0 + * @since 2023/7/13 21:54 + */ +public interface UserCacheKeyDefine { + + CacheKeyDefine USER_INFO = new CacheKeyDefine("user:info:{}", "用户信息 ${id}", 30, TimeUnit.DAYS); + + CacheKeyDefine LOGIN_FAILED_COUNT = new CacheKeyDefine("user:failed:{}", "用户登陆失败次数 ${username}", 3, TimeUnit.DAYS); + + CacheKeyDefine LOGIN_TOKEN = new CacheKeyDefine("user:token:{}:{}", "用户登陆 token ${id} ${time}", 24, TimeUnit.HOURS); + + CacheKeyDefine LOGIN_REFRESH = new CacheKeyDefine("user:refresh:{}:{}", "用户刷新 token ${id} ${time}", 28, TimeUnit.HOURS); + +} diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/domain/SystemUserDO.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/domain/SystemUserDO.java index fcda49a4..2a8da603 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/domain/SystemUserDO.java +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/domain/SystemUserDO.java @@ -55,7 +55,7 @@ public class SystemUserDO extends BaseDO { @Schema(description = "用户状态 0正常 1停用 2锁定") @TableField("status") - private Byte status; + private Integer status; @Schema(description = "最后登录时间") @TableField("last_login_time") diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/dto/LoginTokenDTO.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/dto/LoginTokenDTO.java new file mode 100644 index 00000000..785e19e6 --- /dev/null +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/dto/LoginTokenDTO.java @@ -0,0 +1,41 @@ +package com.orion.ops.module.infra.entity.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 登陆 token 缓存 + * + * @author Jiahang Li + * @version 1.0.0 + * @since 2023/7/14 16:11 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class LoginTokenDTO { + + /** + * 状态 + */ + private Integer status; + + /** + * 登陆时间 + */ + private Long loginTime; + + /** + * 登陆 ip + */ + private String ip; + + /** + * 登陆地址 + */ + private String location; + +} diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/request/SystemUserCreateRequest.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/request/SystemUserCreateRequest.java index 3833d0bb..fd14bb04 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/request/SystemUserCreateRequest.java +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/request/SystemUserCreateRequest.java @@ -52,7 +52,7 @@ public class SystemUserCreateRequest implements Serializable { @NotNull @Schema(description = "用户状态 0正常 1停用 2锁定") - private Byte status; + private Integer status; @NotNull @Schema(description = "最后登录时间") diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/request/SystemUserQueryRequest.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/request/SystemUserQueryRequest.java index a09df5c0..c7f64950 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/request/SystemUserQueryRequest.java +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/request/SystemUserQueryRequest.java @@ -43,7 +43,7 @@ public class SystemUserQueryRequest extends PageRequest { private String email; @Schema(description = "用户状态 0正常 1停用 2锁定") - private Byte status; + private Integer status; @Schema(description = "最后登录时间") private Date lastLoginTime; diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/request/SystemUserUpdateRequest.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/request/SystemUserUpdateRequest.java index 284ea6e5..679532a1 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/request/SystemUserUpdateRequest.java +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/request/SystemUserUpdateRequest.java @@ -56,7 +56,7 @@ public class SystemUserUpdateRequest implements Serializable { @NotNull @Schema(description = "用户状态 0正常 1停用 2锁定") - private Byte status; + private Integer status; @NotNull @Schema(description = "最后登录时间") diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/vo/SystemUserVO.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/vo/SystemUserVO.java index b4193af7..0914a6a7 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/vo/SystemUserVO.java +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/vo/SystemUserVO.java @@ -44,7 +44,7 @@ public class SystemUserVO implements Serializable { private String email; @Schema(description = "用户状态 0正常 1停用 2锁定") - private Byte status; + private Integer status; @Schema(description = "最后登录时间") private Date lastLoginTime; diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/vo/UserLoginVO.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/vo/UserLoginVO.java new file mode 100644 index 00000000..d949103f --- /dev/null +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/vo/UserLoginVO.java @@ -0,0 +1,26 @@ +package com.orion.ops.module.infra.entity.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 用户登陆响应 + * + * @author Jiahang Li + * @version 1.0.0 + * @since 2023/7/14 11:23 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Schema(name = "SystemUserVO", description = "用户 视图响应对象") +public class UserLoginVO { + + @Schema(description = "登陆 token") + private String token; + +} diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/enums/LoginTokenStatusEnum.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/enums/LoginTokenStatusEnum.java new file mode 100644 index 00000000..8ca91ebd --- /dev/null +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/enums/LoginTokenStatusEnum.java @@ -0,0 +1,40 @@ +package com.orion.ops.module.infra.enums; + +import lombok.Getter; + +/** + * 登陆 token 状态 + * + * @author Jiahang Li + * @version 1.0.0 + * @since 2023/7/14 16:15 + */ +@Getter +public enum LoginTokenStatusEnum { + + /** + * 正常 + */ + OK(0), + + /** + * 已在其他设备登陆 + */ + OTHER_DEVICE(1, "已在其他设备登陆"), + + ; + + LoginTokenStatusEnum(Integer status) { + this(status, null); + } + + LoginTokenStatusEnum(Integer status, String message) { + this.status = status; + this.message = message; + } + + private final Integer status; + + private final String message; + +} diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/enums/UserStatusEnum.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/enums/UserStatusEnum.java new file mode 100644 index 00000000..a67fcdc7 --- /dev/null +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/enums/UserStatusEnum.java @@ -0,0 +1,36 @@ +package com.orion.ops.module.infra.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 用户状态枚举 + * + * @author Jiahang Li + * @version 1.0.0 + * @since 2023/7/14 11:35 + */ +@Getter +@AllArgsConstructor +public enum UserStatusEnum { + + /** + * 0 正常 + */ + NORMAL(0), + + /** + * 1 停用 + */ + DISABLED(1), + + /** + * 2 锁定 + */ + LOCKED(2), + + ; + + private final Integer status; + +} diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/AuthenticationService.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/AuthenticationService.java index 89fdc355..ef577369 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/AuthenticationService.java +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/AuthenticationService.java @@ -1,7 +1,10 @@ package com.orion.ops.module.infra.service; +import com.orion.lang.define.wrapper.Pair; import com.orion.ops.module.infra.entity.request.UserLoginRequest; +import javax.servlet.http.HttpServletRequest; + /** * 认证服务 * @@ -14,16 +17,25 @@ public interface AuthenticationService { /** * 登陆 * - * @param request request + * @param request request + * @param servletRequest servletRequest * @return token */ - String login(UserLoginRequest request); + String login(UserLoginRequest request, HttpServletRequest servletRequest); /** * 登出 * - * @param token token + * @param servletRequest servletRequest */ - void logout(String token); + void logout(HttpServletRequest servletRequest); + + /** + * 获取 token pair + * + * @param loginToken loginToken + * @return pair + */ + Pair getLoginTokenPair(String loginToken); } diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/impl/AuthenticationServiceImpl.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/impl/AuthenticationServiceImpl.java index c3ee660b..d85d4fca 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/impl/AuthenticationServiceImpl.java +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/impl/AuthenticationServiceImpl.java @@ -1,18 +1,41 @@ package com.orion.ops.module.infra.service.impl; +import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.orion.lang.constant.StandardHttpHeader; +import com.orion.lang.define.wrapper.Pair; +import com.orion.lang.utils.Exceptions; import com.orion.lang.utils.Valid; +import com.orion.lang.utils.collect.Lists; import com.orion.lang.utils.crypto.Signatures; +import com.orion.ops.framework.common.constant.Const; import com.orion.ops.framework.common.constant.ErrorMessage; +import com.orion.ops.framework.common.crypto.ValueCrypto; +import com.orion.ops.framework.common.security.LoginUser; +import com.orion.ops.framework.common.utils.CryptoUtils; +import com.orion.ops.framework.common.utils.IpUtils; +import com.orion.ops.framework.common.utils.Kits; +import com.orion.ops.framework.redis.core.utils.RedisUtils; +import com.orion.ops.module.infra.convert.SystemUserConvert; import com.orion.ops.module.infra.dao.SystemUserDAO; +import com.orion.ops.module.infra.define.UserCacheKeyDefine; import com.orion.ops.module.infra.entity.domain.SystemUserDO; +import com.orion.ops.module.infra.entity.dto.LoginTokenDTO; import com.orion.ops.module.infra.entity.request.UserLoginRequest; +import com.orion.ops.module.infra.enums.LoginTokenStatusEnum; +import com.orion.ops.module.infra.enums.UserStatusEnum; import com.orion.ops.module.infra.service.AuthenticationService; +import com.orion.web.servlet.web.Servlets; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import javax.annotation.Resource; -import java.util.Optional; +import javax.servlet.http.HttpServletRequest; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; /** * 认证服务实现 @@ -24,10 +47,18 @@ import java.util.Optional; @Service public class AuthenticationServiceImpl implements AuthenticationService { - /** - * 允许多端登陆 - */ - private final boolean allowMultiPlatform = false; + // TODO 想想看 如何配置化 + // 允许多端登陆 + private final boolean allowMultiDevice = true; + // 允许凭证续签 + private final boolean allowRefresh = true; + // 凭证续签最大次数 + private final int maxRefreshCount = 5; + // 失败锁定次数 + private final int maxFailedLoginCount = 5; + + @Resource + private ValueCrypto valueCrypto; @Resource private SystemUserDAO systemUserDAO; @@ -36,32 +67,253 @@ public class AuthenticationServiceImpl implements AuthenticationService { private RedisTemplate redisTemplate; @Override - public String login(UserLoginRequest request) { - // 检查登陆 - LambdaQueryWrapper wrapper = systemUserDAO.wrapper() - .eq(SystemUserDO::getUsername, request.getUsername()) - .eq(SystemUserDO::getPassword, Signatures.md5(request.getPassword())); + public String login(UserLoginRequest request, HttpServletRequest servletRequest) { + // 登陆前检查 + this.preCheckLogin(request); // 获取登陆用户 - Optional systemUserDO = systemUserDAO.of(wrapper).only().get(); - Valid.isTrue(systemUserDO.isPresent(), ErrorMessage.USERNAME_PASSWORD_ERROR); + LambdaQueryWrapper wrapper = systemUserDAO.wrapper() + .eq(SystemUserDO::getUsername, request.getUsername()); + SystemUserDO user = systemUserDAO.of(wrapper).only().get(); + // 检查密码 + boolean passwordCorrect = this.checkPassword(request, user); + Valid.isTrue(passwordCorrect, ErrorMessage.USERNAME_PASSWORD_ERROR); // 检查用户状态 - + this.checkUserStatus(user.getStatus()); + // 设置上次登录时间 + this.setLastLoginTime(user.getId()); // 设置缓存 - - // 不允许多端登陆删除缓存 - - // 生成 authenticationToken - - // 生成 refreshToken - - // - - return null; + this.setUserCache(user); + // 删除登陆失败次数缓存 + redisTemplate.delete(UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(request.getUsername())); + // 获取登陆 ip + String remoteAddr = Servlets.getRemoteAddr(servletRequest); + String location = IpUtils.getLocation(remoteAddr); + long current = System.currentTimeMillis(); + // 不允许多端登陆 + if (!false) { + // 无效化其他缓存 + this.invalidOtherDeviceToken(user.getId(), current, remoteAddr, location); + } + // 生成 loginToken + return this.generatorLoginToken(user, current, remoteAddr, location); } @Override - public void logout(String token) { + public void logout(HttpServletRequest request) { + // 获取登陆 token + String loginToken = Kits.getAuthorization(request.getHeader(StandardHttpHeader.AUTHORIZATION)); + if (loginToken == null) { + return; + } + Pair pair = this.getLoginTokenPair(loginToken); + if (pair == null) { + return; + } + Long id = pair.getKey(); + Long current = pair.getValue(); + // 删除 loginToken & refreshToken + String loginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(id, current); + String refreshKey = UserCacheKeyDefine.LOGIN_REFRESH.format(id, current); + redisTemplate.delete(Lists.of(loginKey, refreshKey)); + } + @Override + public Pair getLoginTokenPair(String loginToken) { + if (loginToken == null) { + return null; + } + try { + String value = CryptoUtils.decryptBase62(loginToken); + String[] pair = value.split(":"); + return Pair.of(Long.valueOf(pair[0]), Long.valueOf(pair[1])); + } catch (Exception e) { + return null; + } + } + + /** + * 登陆预检查 + * + * @param request request + */ + private void preCheckLogin(UserLoginRequest request) { + // 检查密码长度是否正确 MD5 长度为 32 + if (request.getPassword().length() != Const.MD5_LEN) { + throw Exceptions.argument(ErrorMessage.USERNAME_PASSWORD_ERROR); + } + // 检查登陆失败次数 + String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(request.getUsername()); + String failedCount = redisTemplate.opsForValue().get(failedCountKey); + if (failedCount != null && Integer.parseInt(failedCount) >= maxFailedLoginCount) { + throw Exceptions.argument(ErrorMessage.MAX_LOGIN_FAILED); + } + } + + /** + * 检查密码 + * + * @param request request + * @param user user + * @return 是否正确 + */ + private boolean checkPassword(UserLoginRequest request, SystemUserDO user) { + // 密码正确 + if (user != null && user.getPassword().equals(Signatures.md5(request.getPassword()))) { + return true; + } + // 刷新登陆失败缓存 + String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(user.getUsername()); + Long failedLoginCount = redisTemplate.opsForValue().increment(failedCountKey); + // 用户不存在 + if (user == null) { + return false; + } + // 锁定用户 + if (failedLoginCount >= maxFailedLoginCount) { + // 更新用户表 + SystemUserDO updateUser = new SystemUserDO(); + updateUser.setId(user.getId()); + updateUser.setStatus(UserStatusEnum.LOCKED.getStatus()); + systemUserDAO.updateById(updateUser); + // 更新缓存 + String userInfoKey = UserCacheKeyDefine.USER_INFO.format(user.getId()); + String userInfoCache = redisTemplate.opsForValue().get(userInfoKey); + // 缓存不存在 + if (userInfoCache == null) { + return false; + } + // 修改缓存状态 + LoginUser loginUser = JSON.parseObject(userInfoCache, LoginUser.class); + loginUser.setStatus(UserStatusEnum.LOCKED.getStatus()); + redisTemplate.opsForValue().set(userInfoKey, JSON.toJSONString(loginUser), + UserCacheKeyDefine.USER_INFO.getTimeout(), + UserCacheKeyDefine.USER_INFO.getUnit()); + } + return false; + } + + /** + * 检查用户状态 + * + * @param status status + */ + private void checkUserStatus(Integer status) { + if (UserStatusEnum.DISABLED.getStatus().equals(status)) { + // 禁用状态 + throw Exceptions.argument(ErrorMessage.USER_DISABLED); + } else if (UserStatusEnum.LOCKED.getStatus().equals(status)) { + // 锁定状态 + throw Exceptions.argument(ErrorMessage.USER_LOCKED); + } + } + + /** + * 设置最后登录时间 + * + * @param id id + */ + private void setLastLoginTime(Long id) { + SystemUserDO update = new SystemUserDO(); + update.setId(id); + update.setLastLoginTime(new Date()); + systemUserDAO.updateById(update); + } + + /** + * 设置用户缓存 + * + * @param user user + */ + private void setUserCache(SystemUserDO user) { + String userInfoKey = UserCacheKeyDefine.USER_INFO.format(user.getId()); + String userInfoCache = redisTemplate.opsForValue().get(userInfoKey); + // 缓存存在 + if (userInfoCache != null) { + return; + } + // 设置缓存 + LoginUser loginUser = SystemUserConvert.MAPPER.toLoginUser(user); + // TODO 查询角色 + redisTemplate.opsForValue().set(userInfoKey, JSON.toJSONString(loginUser), + UserCacheKeyDefine.USER_INFO.getTimeout(), + UserCacheKeyDefine.USER_INFO.getUnit()); + } + + /** + * 无效化其他登陆信息 + * + * @param id id + * @param loginTime loginTime + * @param remoteAddr remoteAddr + * @param location location + */ + private void invalidOtherDeviceToken(Long id, long loginTime, String remoteAddr, String location) { + String loginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(id, "*"); + // 获取登陆信息 + Set loginKeyList = RedisUtils.scanKeys(redisTemplate, loginKey, 100); + if (!loginKeyList.isEmpty()) { + // 获取有效登陆信息 + List loginTokenInfoList = redisTemplate.opsForValue() + .multiGet(loginKeyList) + .stream() + .filter(Objects::nonNull) + .map(s -> JSON.parseObject(s, LoginTokenDTO.class)) + .filter(s -> LoginTokenStatusEnum.OK.getStatus().equals(s.getStatus())) + .collect(Collectors.toList()); + // 修改登陆信息 + for (LoginTokenDTO loginTokenInfo : loginTokenInfoList) { + String deviceLoginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(id, loginTokenInfo.getLoginTime()); + loginTokenInfo.setStatus(LoginTokenStatusEnum.OTHER_DEVICE.getStatus()); + loginTokenInfo.setLoginTime(loginTime); + loginTokenInfo.setIp(remoteAddr); + loginTokenInfo.setLocation(location); + redisTemplate.opsForValue().set(deviceLoginKey, JSON.toJSONString(loginTokenInfo), + UserCacheKeyDefine.LOGIN_TOKEN.getTimeout(), + UserCacheKeyDefine.LOGIN_TOKEN.getUnit()); + } + } + // 删除续签信息 + if (allowRefresh) { + String refreshKey = UserCacheKeyDefine.LOGIN_REFRESH.format(id, "*"); + Set refreshKeyList = RedisUtils.scanKeys(redisTemplate, refreshKey, 100); + if (!refreshKeyList.isEmpty()) { + redisTemplate.delete(refreshKeyList); + } + } + } + + /** + * 生成 loginToken + * + * @param user user + * @param loginTime loginTime + * @param remoteAddr remoteAddr + * @param location location + * @return loginToken + */ + private String generatorLoginToken(SystemUserDO user, long loginTime, + String remoteAddr, String location) { + Long id = user.getId(); + // 生成 loginToken + String loginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(id, loginTime); + LoginTokenDTO loginValue = LoginTokenDTO.builder() + .status(LoginTokenStatusEnum.OK.getStatus()) + .ip(remoteAddr) + .loginTime(loginTime) + .location(location) + .build(); + redisTemplate.opsForValue().set(loginKey, JSON.toJSONString(loginValue), + UserCacheKeyDefine.LOGIN_TOKEN.getTimeout(), + UserCacheKeyDefine.LOGIN_TOKEN.getUnit()); + // 生成 refreshToken + if (allowRefresh) { + String refreshKey = UserCacheKeyDefine.LOGIN_REFRESH.format(id, loginTime); + redisTemplate.opsForValue().set(refreshKey, "1", + UserCacheKeyDefine.LOGIN_REFRESH.getTimeout(), + UserCacheKeyDefine.LOGIN_REFRESH.getUnit()); + } + // 返回token + return CryptoUtils.encryptBase62(id + ":" + loginTime); } }