From 5895d7a32c6897afd7ca001974e4101690edbb9c Mon Sep 17 00:00:00 2001 From: zhang chengzhi <3144712872@qq.com> Date: Wed, 24 Jul 2024 10:00:17 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 46 +++++++ pom.xml | 88 ++++++++++++ .../com/muyu/auth/CloudAuthApplication.java | 19 +++ .../muyu/auth/controller/TokenController.java | 72 ++++++++++ .../java/com/muyu/auth/form/LoginBody.java | 34 +++++ .../java/com/muyu/auth/form/RegisterBody.java | 10 ++ .../muyu/auth/service/SysLoginService.java | 127 ++++++++++++++++++ .../muyu/auth/service/SysPasswordService.java | 76 +++++++++++ .../auth/service/SysRecordLogService.java | 44 ++++++ src/main/resources/banner.txt | 2 + src/main/resources/bootstrap.yml | 47 +++++++ src/main/resources/logback/dev.xml | 74 ++++++++++ src/main/resources/logback/prod.xml | 89 ++++++++++++ src/main/resources/logback/test.xml | 89 ++++++++++++ 14 files changed, 817 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/com/muyu/auth/CloudAuthApplication.java create mode 100644 src/main/java/com/muyu/auth/controller/TokenController.java create mode 100644 src/main/java/com/muyu/auth/form/LoginBody.java create mode 100644 src/main/java/com/muyu/auth/form/RegisterBody.java create mode 100644 src/main/java/com/muyu/auth/service/SysLoginService.java create mode 100644 src/main/java/com/muyu/auth/service/SysPasswordService.java create mode 100644 src/main/java/com/muyu/auth/service/SysRecordLogService.java create mode 100644 src/main/resources/banner.txt create mode 100644 src/main/resources/bootstrap.yml create mode 100644 src/main/resources/logback/dev.xml create mode 100644 src/main/resources/logback/prod.xml create mode 100644 src/main/resources/logback/test.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8a1c2a8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +###################################################################### +# Build Tools + +.gradle +/build/ +!gradle/wrapper/gradle-wrapper.jar + +target/ +!.mvn/wrapper/maven-wrapper.jar + +###################################################################### +# IDE + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### JRebel ### +rebel.xml +### NetBeans ### +nbproject/private/ +build/* +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +###################################################################### +# Others +*.log +*.xml.versionsBackup +*.swp + +!*/build/*.java +!*/build/*.html +!*/build/*.xml diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..99217da --- /dev/null +++ b/pom.xml @@ -0,0 +1,88 @@ + + + com.muyu + cloud-server-parent + 3.6.3 + + 4.0.0 + + cloud-auth + + + cloud-auth认证授权中心 + + + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-sentinel + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + com.muyu + cloud-common-security + + + + + com.muyu + cloud-common-api-doc + + + + + + ${project.artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + + + diff --git a/src/main/java/com/muyu/auth/CloudAuthApplication.java b/src/main/java/com/muyu/auth/CloudAuthApplication.java new file mode 100644 index 0000000..e6ea4fd --- /dev/null +++ b/src/main/java/com/muyu/auth/CloudAuthApplication.java @@ -0,0 +1,19 @@ +package com.muyu.auth; + +import com.muyu.common.security.annotation.EnableMyFeignClients; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; + +/** + * 认证授权中心 + * + * @author muyu + */ +@EnableMyFeignClients +@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) +public class CloudAuthApplication { + public static void main (String[] args) { + SpringApplication.run(CloudAuthApplication.class, args); + } +} diff --git a/src/main/java/com/muyu/auth/controller/TokenController.java b/src/main/java/com/muyu/auth/controller/TokenController.java new file mode 100644 index 0000000..d870762 --- /dev/null +++ b/src/main/java/com/muyu/auth/controller/TokenController.java @@ -0,0 +1,72 @@ +package com.muyu.auth.controller; + +import com.muyu.auth.form.LoginBody; +import com.muyu.auth.form.RegisterBody; +import com.muyu.auth.service.SysLoginService; +import com.muyu.common.core.domain.Result; +import com.muyu.common.core.utils.JwtUtils; +import com.muyu.common.core.utils.StringUtils; +import com.muyu.common.security.auth.AuthUtil; +import com.muyu.common.security.service.TokenService; +import com.muyu.common.security.utils.SecurityUtils; +import com.muyu.common.system.domain.LoginUser; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.servlet.http.HttpServletRequest; + +/** + * token 控制 + * + * @author muyu + */ +@RestController +public class TokenController { + @Autowired + private TokenService tokenService; + + @Autowired + private SysLoginService sysLoginService; + + @PostMapping("login") + public Result login (@RequestBody LoginBody form) { + // 用户登录 + LoginUser userInfo = sysLoginService.login(form.getUsername(), form.getPassword()); + // 获取登录token + return Result.success(tokenService.createToken(userInfo)); + } + + @DeleteMapping("logout") + public Result logout (HttpServletRequest request) { + String token = SecurityUtils.getToken(request); + if (StringUtils.isNotEmpty(token)) { + String username = JwtUtils.getUserName(token); + // 删除用户缓存记录 + AuthUtil.logoutByToken(token); + // 记录用户退出日志 + sysLoginService.logout(username); + } + return Result.success(); + } + + @PostMapping("refresh") + public Result refresh (HttpServletRequest request) { + LoginUser loginUser = tokenService.getLoginUser(request); + if (StringUtils.isNotNull(loginUser)) { + // 刷新令牌有效期 + tokenService.refreshToken(loginUser); + return Result.success(); + } + return Result.success(); + } + + @PostMapping("register") + public Result register (@RequestBody RegisterBody registerBody) { + // 用户注册 + sysLoginService.register(registerBody.getUsername(), registerBody.getPassword()); + return Result.success(); + } +} diff --git a/src/main/java/com/muyu/auth/form/LoginBody.java b/src/main/java/com/muyu/auth/form/LoginBody.java new file mode 100644 index 0000000..999ad44 --- /dev/null +++ b/src/main/java/com/muyu/auth/form/LoginBody.java @@ -0,0 +1,34 @@ +package com.muyu.auth.form; + +/** + * 用户登录对象 + * + * @author muyu + */ +public class LoginBody { + /** + * 用户名 + */ + private String username; + + /** + * 用户密码 + */ + private String password; + + public String getUsername () { + return username; + } + + public void setUsername (String username) { + this.username = username; + } + + public String getPassword () { + return password; + } + + public void setPassword (String password) { + this.password = password; + } +} diff --git a/src/main/java/com/muyu/auth/form/RegisterBody.java b/src/main/java/com/muyu/auth/form/RegisterBody.java new file mode 100644 index 0000000..c2a4d5a --- /dev/null +++ b/src/main/java/com/muyu/auth/form/RegisterBody.java @@ -0,0 +1,10 @@ +package com.muyu.auth.form; + +/** + * 用户注册对象 + * + * @author muyu + */ +public class RegisterBody extends LoginBody { + +} diff --git a/src/main/java/com/muyu/auth/service/SysLoginService.java b/src/main/java/com/muyu/auth/service/SysLoginService.java new file mode 100644 index 0000000..ff0af1a --- /dev/null +++ b/src/main/java/com/muyu/auth/service/SysLoginService.java @@ -0,0 +1,127 @@ +package com.muyu.auth.service; + +import com.muyu.common.core.constant.CacheConstants; +import com.muyu.common.core.constant.Constants; +import com.muyu.common.core.constant.SecurityConstants; +import com.muyu.common.core.constant.UserConstants; +import com.muyu.common.core.domain.Result; +import com.muyu.common.core.enums.UserStatus; +import com.muyu.common.core.exception.ServiceException; +import com.muyu.common.core.text.Convert; +import com.muyu.common.core.utils.StringUtils; +import com.muyu.common.core.utils.ip.IpUtils; +import com.muyu.common.redis.service.RedisService; +import com.muyu.common.security.utils.SecurityUtils; +import com.muyu.common.system.remote.RemoteUserService; +import com.muyu.common.system.domain.SysUser; +import com.muyu.common.system.domain.LoginUser; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * 登录校验方法 + * + * @author muyu + */ +@Component +public class SysLoginService { + @Autowired + private RemoteUserService remoteUserService; + + @Autowired + private SysPasswordService passwordService; + + @Autowired + private SysRecordLogService recordLogService; + + @Autowired + private RedisService redisService; + + /** + * 登录 + */ + public LoginUser login (String username, String password) { + // 用户名或密码为空 错误 + if (StringUtils.isAnyBlank(username, password)) { + recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户/密码必须填写"); + throw new ServiceException("用户/密码必须填写"); + } + // 密码如果不在指定范围内 错误 + if (password.length() < UserConstants.PASSWORD_MIN_LENGTH + || password.length() > UserConstants.PASSWORD_MAX_LENGTH) { + recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户密码不在指定范围"); + throw new ServiceException("用户密码不在指定范围"); + } + // 用户名不在指定范围内 错误 + if (username.length() < UserConstants.USERNAME_MIN_LENGTH + || username.length() > UserConstants.USERNAME_MAX_LENGTH) { + recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户名不在指定范围"); + throw new ServiceException("用户名不在指定范围"); + } + // IP黑名单校验 + String blackStr = Convert.toStr(redisService.getCacheObject(CacheConstants.SYS_LOGIN_BLACKIPLIST)); + if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) { + recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "很遗憾,访问IP已被列入系统黑名单"); + throw new ServiceException("很遗憾,访问IP已被列入系统黑名单"); + } + // 查询用户信息 + Result userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER); + + if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData())) { + recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "登录用户不存在"); + throw new ServiceException("登录用户:" + username + " 不存在"); + } + + if (Result.FAIL == userResult.getCode()) { + throw new ServiceException(userResult.getMsg()); + } + + LoginUser userInfo = userResult.getData(); + SysUser user = userResult.getData().getSysUser(); + if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) { + recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "对不起,您的账号已被删除"); + throw new ServiceException("对不起,您的账号:" + username + " 已被删除"); + } + if (UserStatus.DISABLE.getCode().equals(user.getStatus())) { + recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户已停用,请联系管理员"); + throw new ServiceException("对不起,您的账号:" + username + " 已停用"); + } + passwordService.validate(user, password); + recordLogService.recordLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功"); + return userInfo; + } + + public void logout (String loginName) { + recordLogService.recordLogininfor(loginName, Constants.LOGOUT, "退出成功"); + } + + /** + * 注册 + */ + public void register (String username, String password) { + // 用户名或密码为空 错误 + if (StringUtils.isAnyBlank(username, password)) { + throw new ServiceException("用户/密码必须填写"); + } + if (username.length() < UserConstants.USERNAME_MIN_LENGTH + || username.length() > UserConstants.USERNAME_MAX_LENGTH) { + throw new ServiceException("账户长度必须在2到20个字符之间"); + } + if (password.length() < UserConstants.PASSWORD_MIN_LENGTH + || password.length() > UserConstants.PASSWORD_MAX_LENGTH) { + throw new ServiceException("密码长度必须在5到20个字符之间"); + } + + // 注册用户信息 + SysUser sysUser = new SysUser(); + sysUser.setUserName(username); + sysUser.setNickName(username); + sysUser.setPassword(SecurityUtils.encryptPassword(password)); + Result registerResult = remoteUserService.registerUserInfo(sysUser, SecurityConstants.INNER); + + if (Result.FAIL == registerResult.getCode()) { + throw new ServiceException(registerResult.getMsg()); + } + recordLogService.recordLogininfor(username, Constants.REGISTER, "注册成功"); + } +} diff --git a/src/main/java/com/muyu/auth/service/SysPasswordService.java b/src/main/java/com/muyu/auth/service/SysPasswordService.java new file mode 100644 index 0000000..5f3c126 --- /dev/null +++ b/src/main/java/com/muyu/auth/service/SysPasswordService.java @@ -0,0 +1,76 @@ +package com.muyu.auth.service; + +import com.muyu.common.core.constant.CacheConstants; +import com.muyu.common.core.constant.Constants; +import com.muyu.common.core.exception.ServiceException; +import com.muyu.common.redis.service.RedisService; +import com.muyu.common.security.utils.SecurityUtils; +import com.muyu.common.system.domain.SysUser; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.concurrent.TimeUnit; + +/** + * 登录密码方法 + * + * @author muyu + */ +@Component +public class SysPasswordService { + @Autowired + private RedisService redisService; + + private int maxRetryCount = CacheConstants.PASSWORD_MAX_RETRY_COUNT; + + private Long lockTime = CacheConstants.PASSWORD_LOCK_TIME; + + @Autowired + private SysRecordLogService recordLogService; + + /** + * 登录账户密码错误次数缓存键名 + * + * @param username 用户名 + * + * @return 缓存键key + */ + private String getCacheKey (String username) { + return CacheConstants.PWD_ERR_CNT_KEY + username; + } + + public void validate (SysUser user, String password) { + String username = user.getUserName(); + + Integer retryCount = redisService.getCacheObject(getCacheKey(username)); + + if (retryCount == null) { + retryCount = 0; + } + + if (retryCount >= Integer.valueOf(maxRetryCount).intValue()) { + String errMsg = String.format("密码输入错误%s次,帐户锁定%s分钟", maxRetryCount, lockTime); + recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, errMsg); + throw new ServiceException(errMsg); + } + + if (!matches(user, password)) { + retryCount = retryCount + 1; + recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, String.format("密码输入错误%s次", retryCount)); + redisService.setCacheObject(getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES); + throw new ServiceException("用户不存在/密码错误"); + } else { + clearLoginRecordCache(username); + } + } + + public boolean matches (SysUser user, String rawPassword) { + return SecurityUtils.matchesPassword(rawPassword, user.getPassword()); + } + + public void clearLoginRecordCache (String loginName) { + if (redisService.hasKey(getCacheKey(loginName))) { + redisService.deleteObject(getCacheKey(loginName)); + } + } +} diff --git a/src/main/java/com/muyu/auth/service/SysRecordLogService.java b/src/main/java/com/muyu/auth/service/SysRecordLogService.java new file mode 100644 index 0000000..2d4de80 --- /dev/null +++ b/src/main/java/com/muyu/auth/service/SysRecordLogService.java @@ -0,0 +1,44 @@ +package com.muyu.auth.service; + +import com.muyu.common.core.constant.Constants; +import com.muyu.common.core.constant.SecurityConstants; +import com.muyu.common.core.utils.StringUtils; +import com.muyu.common.core.utils.ip.IpUtils; +import com.muyu.common.system.remote.RemoteLogService; +import com.muyu.common.system.domain.SysLogininfor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * 记录日志方法 + * + * @author muyu + */ +@Component +public class SysRecordLogService { + @Autowired + private RemoteLogService remoteLogService; + + /** + * 记录登录信息 + * + * @param username 用户名 + * @param status 状态 + * @param message 消息内容 + * + * @return + */ + public void recordLogininfor (String username, String status, String message) { + SysLogininfor logininfor = new SysLogininfor(); + logininfor.setUserName(username); + logininfor.setIpaddr(IpUtils.getIpAddr()); + logininfor.setMsg(message); + // 日志状态 + if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) { + logininfor.setStatus(Constants.LOGIN_SUCCESS_STATUS); + } else if (Constants.LOGIN_FAIL.equals(status)) { + logininfor.setStatus(Constants.LOGIN_FAIL_STATUS); + } + remoteLogService.saveLogininfor(logininfor, SecurityConstants.INNER); + } +} diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt new file mode 100644 index 0000000..0dd5eee --- /dev/null +++ b/src/main/resources/banner.txt @@ -0,0 +1,2 @@ +Spring Boot Version: ${spring-boot.version} +Spring Application Name: ${spring.application.name} diff --git a/src/main/resources/bootstrap.yml b/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..dabed83 --- /dev/null +++ b/src/main/resources/bootstrap.yml @@ -0,0 +1,47 @@ +# Tomcat +server: + port: 9500 + +# nacos线上地址 +nacos: + addr: 106.54.199.209:8848 + user-name: nacos + password: nacos + namespace: high-six +# Spring +spring: + application: + # 应用名称 + name: cloud-auth + profiles: + # 环境配置 + active: dev + cloud: + nacos: + discovery: + # 服务注册地址 + server-addr: ${nacos.addr} + # nacos用户名 + username: ${nacos.user-name} + # nacos密码 + password: ${nacos.password} + # 命名空间 + namespace: ${nacos.namespace} + config: + # 服务注册地址 + server-addr: ${nacos.addr} + # nacos用户名 + username: ${nacos.user-name} + # nacos密码 + password: ${nacos.password} + # 命名空间 + namespace: ${nacos.namespace} + # 配置文件格式 + file-extension: yml + # 共享配置 + shared-configs: + # 系统共享配置 + - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} + # 系统环境Config共享配置 + - application-config-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} + diff --git a/src/main/resources/logback/dev.xml b/src/main/resources/logback/dev.xml new file mode 100644 index 0000000..880c02d --- /dev/null +++ b/src/main/resources/logback/dev.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/info.log + + + + ${log.path}/info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/error.log + + + + ${log.path}/error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/logback/prod.xml b/src/main/resources/logback/prod.xml new file mode 100644 index 0000000..d05b780 --- /dev/null +++ b/src/main/resources/logback/prod.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/info.log + + + + ${log.path}/info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/error.log + + + + ${log.path}/error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + + + + + ${log.sky.pattern} + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/logback/test.xml b/src/main/resources/logback/test.xml new file mode 100644 index 0000000..d05b780 --- /dev/null +++ b/src/main/resources/logback/test.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/info.log + + + + ${log.path}/info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/error.log + + + + ${log.path}/error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + + + + + ${log.sky.pattern} + + + + + + + + + + + + + + + + + + + + + + +