From faa7411ecc5ed2e76ea6f0afd06d9312a0e346d3 Mon Sep 17 00:00:00 2001 From: wangys <3401275564@qq.com> Date: Sun, 28 Sep 2025 15:50:59 +0800 Subject: [PATCH] service auth --- schoolNewsServ/auth/pom.xml | 124 +++++++++- .../auth/src/main/java/org/xyzh/Main.java | 7 - .../org/xyzh/auth/config/AuthProperties.java | 153 ++++++++++++ .../org/xyzh/auth/config/SecurityConfig.java | 74 ++++++ .../xyzh/auth/controller/AuthController.java | 100 ++++++++ .../org/xyzh/auth/domain/UserPrincipal.java | 98 ++++++++ .../auth/filter/JwtAuthenticationFilter.java | 102 ++++++++ .../xyzh/auth/service/LoginLogService.java | 69 ++++++ .../xyzh/auth/service/LoginServiceImpl.java | 228 ++++++++++++++++++ .../auth/service/UserDetailsServiceImpl.java | 95 ++++++++ .../org/xyzh/auth/strategy/LoginStrategy.java | 50 ++++ .../auth/strategy/LoginStrategyFactory.java | 51 ++++ .../strategy/impl/EmailLoginStrategy.java | 49 ++++ .../strategy/impl/PhoneLoginStrategy.java | 53 ++++ .../strategy/impl/UsernameLoginStrategy.java | 49 ++++ .../strategy/impl/WechatLoginStrategy.java | 47 ++++ .../java/org/xyzh/auth/util/JwtTokenUtil.java | 151 ++++++++++++ .../auth/src/main/resources/application.yml | 63 +++++ .../auth/src/main/resources/log4j2-spring.xml | 107 ++++++++ 19 files changed, 1662 insertions(+), 8 deletions(-) delete mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/Main.java create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/config/AuthProperties.java create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/config/SecurityConfig.java create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/controller/AuthController.java create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/domain/UserPrincipal.java create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/filter/JwtAuthenticationFilter.java create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/service/LoginLogService.java create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/service/LoginServiceImpl.java create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/service/UserDetailsServiceImpl.java create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/LoginStrategy.java create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/LoginStrategyFactory.java create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/EmailLoginStrategy.java create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/PhoneLoginStrategy.java create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/UsernameLoginStrategy.java create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/WechatLoginStrategy.java create mode 100644 schoolNewsServ/auth/src/main/java/org/xyzh/auth/util/JwtTokenUtil.java create mode 100644 schoolNewsServ/auth/src/main/resources/application.yml create mode 100644 schoolNewsServ/auth/src/main/resources/log4j2-spring.xml diff --git a/schoolNewsServ/auth/pom.xml b/schoolNewsServ/auth/pom.xml index 9361f66..6358857 100644 --- a/schoolNewsServ/auth/pom.xml +++ b/schoolNewsServ/auth/pom.xml @@ -12,10 +12,132 @@ org.xyzh auth ${school-news.version} + jar 21 21 - + + + + + org.xyzh + common-core + ${school-news.version} + + + org.xyzh + common-dto + ${school-news.version} + + + org.xyzh + common-exception + ${school-news.version} + + + org.xyzh + common-redis + ${school-news.version} + + + org.xyzh + common-util + ${school-news.version} + + + org.xyzh + api-auth + ${school-news.version} + + + + org.xyzh + api-system + ${school-news.version} + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-logging + + + + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-logging + + + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + + + io.jsonwebtoken + jjwt-api + + + io.jsonwebtoken + jjwt-impl + runtime + + + io.jsonwebtoken + jjwt-jackson + runtime + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + org.springframework.boot + spring-boot-starter-data-jdbc + + + + org.springframework.boot + spring-boot-starter-logging + + + + + com.mysql + mysql-connector-j + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + \ No newline at end of file diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/Main.java b/schoolNewsServ/auth/src/main/java/org/xyzh/Main.java deleted file mode 100644 index f660b7a..0000000 --- a/schoolNewsServ/auth/src/main/java/org/xyzh/Main.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.xyzh; - -public class Main { - public static void main(String[] args) { - System.out.println("Hello world!"); - } -} \ No newline at end of file diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/config/AuthProperties.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/config/AuthProperties.java new file mode 100644 index 0000000..e1aabb1 --- /dev/null +++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/config/AuthProperties.java @@ -0,0 +1,153 @@ +package org.xyzh.auth.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; +import java.util.List; +import java.util.ArrayList; + +/** + * @description AuthProperties.java文件描述 认证配置属性 + * @filename AuthProperties.java + * @author yslg + * @copyright xyzh + * @since 2025-09-28 + */ +@Component +@ConfigurationProperties(prefix = "school-news.auth") +public class AuthProperties { + + /** + * @description 免登录白名单路径 + * @author yslg + * @since 2025-09-28 + */ + private List whiteList = new ArrayList<>(); + + /** + * @description JWT密钥 + * @author yslg + * @since 2025-09-28 + */ + private String jwtSecret = "schoolNewsDefaultSecretKeyForJWT2025"; + + /** + * @description JWT过期时间(秒) + * @author yslg + * @since 2025-09-28 + */ + private Long jwtExpiration = 86400L; + + /** + * @description 最大登录失败次数 + * @author yslg + * @since 2025-09-28 + */ + private Integer maxLoginAttempts = 5; + + /** + * @description 账户锁定时间(分钟) + * @author yslg + * @since 2025-09-28 + */ + private Integer lockoutDuration = 30; + + /** + * @description 获取白名单 + * @return List 白名单路径列表 + * @author yslg + * @since 2025-09-28 + */ + public List getWhiteList() { + return whiteList; + } + + /** + * @description 设置白名单 + * @param whiteList 白名单路径列表 + * @author yslg + * @since 2025-09-28 + */ + public void setWhiteList(List whiteList) { + this.whiteList = whiteList; + } + + /** + * @description 获取JWT密钥 + * @return String JWT密钥 + * @author yslg + * @since 2025-09-28 + */ + public String getJwtSecret() { + return jwtSecret; + } + + /** + * @description 设置JWT密钥 + * @param jwtSecret JWT密钥 + * @author yslg + * @since 2025-09-28 + */ + public void setJwtSecret(String jwtSecret) { + this.jwtSecret = jwtSecret; + } + + /** + * @description 获取JWT过期时间 + * @return Long JWT过期时间(秒) + * @author yslg + * @since 2025-09-28 + */ + public Long getJwtExpiration() { + return jwtExpiration; + } + + /** + * @description 设置JWT过期时间 + * @param jwtExpiration JWT过期时间(秒) + * @author yslg + * @since 2025-09-28 + */ + public void setJwtExpiration(Long jwtExpiration) { + this.jwtExpiration = jwtExpiration; + } + + /** + * @description 获取最大登录失败次数 + * @return Integer 最大登录失败次数 + * @author yslg + * @since 2025-09-28 + */ + public Integer getMaxLoginAttempts() { + return maxLoginAttempts; + } + + /** + * @description 设置最大登录失败次数 + * @param maxLoginAttempts 最大登录失败次数 + * @author yslg + * @since 2025-09-28 + */ + public void setMaxLoginAttempts(Integer maxLoginAttempts) { + this.maxLoginAttempts = maxLoginAttempts; + } + + /** + * @description 获取账户锁定时间 + * @return Integer 账户锁定时间(分钟) + * @author yslg + * @since 2025-09-28 + */ + public Integer getLockoutDuration() { + return lockoutDuration; + } + + /** + * @description 设置账户锁定时间 + * @param lockoutDuration 账户锁定时间(分钟) + * @author yslg + * @since 2025-09-28 + */ + public void setLockoutDuration(Integer lockoutDuration) { + this.lockoutDuration = lockoutDuration; + } +} diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/config/SecurityConfig.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/config/SecurityConfig.java new file mode 100644 index 0000000..ff5fb95 --- /dev/null +++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/config/SecurityConfig.java @@ -0,0 +1,74 @@ +package org.xyzh.auth.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.xyzh.auth.filter.JwtAuthenticationFilter; + +/** + * @description SecurityConfig.java文件描述 Spring Security配置 + * @filename SecurityConfig.java + * @author yslg + * @copyright xyzh + * @since 2025-09-28 + */ +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Autowired + private AuthProperties authProperties; + + @Autowired + private JwtAuthenticationFilter jwtAuthenticationFilter; + + /** + * @description 密码编码器 + * @return PasswordEncoder 密码编码器 + * @author yslg + * @since 2025-09-28 + */ + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + /** + * @description 安全过滤器链配置 + * @param http HTTP安全配置 + * @return SecurityFilterChain 安全过滤器链 + * @author yslg + * @since 2025-09-28 + */ + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + // 白名单路径转换为数组 + String[] whiteListArray = authProperties.getWhiteList().toArray(new String[0]); + + http + // 禁用CSRF + .csrf(csrf -> csrf.disable()) + // 无状态session + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + // 配置授权规则 + .authorizeHttpRequests(authz -> authz + .requestMatchers(whiteListArray).permitAll() + .requestMatchers("/auth/login", "/auth/logout", "/auth/captcha").permitAll() + .requestMatchers("/actuator/**").permitAll() + .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll() + .requestMatchers("/favicon.ico", "/error").permitAll() + .anyRequest().authenticated() + ) + // 添加JWT过滤器 + .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } +} diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/controller/AuthController.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/controller/AuthController.java new file mode 100644 index 0000000..de275e8 --- /dev/null +++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/controller/AuthController.java @@ -0,0 +1,100 @@ +package org.xyzh.auth.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import org.xyzh.api.auth.login.LoginService; +import org.xyzh.common.core.domain.LoginParam; +import org.xyzh.common.core.domain.LoginDomain; +import org.xyzh.common.core.domain.ResultDomain; +import org.xyzh.common.utils.IDUtils; + +/** + * @description AuthController.java文件描述 认证控制器 + * @filename AuthController.java + * @author yslg + * @copyright xyzh + * @since 2025-09-28 + */ +@RestController +@RequestMapping("/auth") +public class AuthController { + + @Autowired + private LoginService loginService; + + /** + * @description 用户登录 + * @param loginParam 登录参数 + * @return ResultDomain 登录结果 + * @author yslg + * @since 2025-09-28 + */ + @PostMapping("/login") + public ResultDomain login(@RequestBody LoginParam loginParam) { + return loginService.login(loginParam); + } + + /** + * @description 用户退出登录 + * @param loginDomain 登录域对象 + * @return ResultDomain 退出结果 + * @author yslg + * @since 2025-09-28 + */ + @PostMapping("/logout") + public ResultDomain logout(@RequestBody LoginDomain loginDomain) { + return loginService.logout(loginDomain); + } + + /** + * @description 获取验证码 + * @return ResultDomain 验证码 + * @author yslg + * @since 2025-09-28 + */ + @GetMapping("/captcha") + public ResultDomain getCaptcha() { + // TODO: 实现验证码生成逻辑 + ResultDomain result = new ResultDomain<>(); + + // 生成验证码会话ID,用于验证时匹配 + String captchaId = IDUtils.generateID(); + String captchaData = captchaId + ":captcha-placeholder"; // 格式: ID:验证码内容 + + result.success("验证码获取成功", captchaData); + return result; + } + + /** + * @description 刷新令牌 + * @param token 原令牌 + * @return ResultDomain 新令牌 + * @author yslg + * @since 2025-09-28 + */ + @PostMapping("/refresh") + public ResultDomain refreshToken(@RequestHeader("Authorization") String token) { + // TODO: 实现令牌刷新逻辑 + ResultDomain result = new ResultDomain<>(); + + // 为新令牌生成唯一ID + String newTokenId = IDUtils.generateID(); + String newToken = "new-token-" + newTokenId; // 临时占位符 + + result.success("令牌刷新成功", newToken); + return result; + } + + /** + * @description 健康检查 + * @return ResultDomain 健康状态 + * @author yslg + * @since 2025-09-28 + */ + @GetMapping("/health") + public ResultDomain health() { + ResultDomain result = new ResultDomain<>(); + result.success("认证服务运行正常", "OK"); + return result; + } +} diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/domain/UserPrincipal.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/domain/UserPrincipal.java new file mode 100644 index 0000000..4930fb0 --- /dev/null +++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/domain/UserPrincipal.java @@ -0,0 +1,98 @@ +package org.xyzh.auth.domain; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.xyzh.common.dto.user.TbSysUser; +import org.xyzh.common.dto.dept.TbSysDeptRole; +import org.xyzh.common.dto.permission.TbSysPermission; +import org.xyzh.common.core.enums.UserStatus; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @description UserPrincipal.java文件描述 用户主体类 + * @filename UserPrincipal.java + * @author yslg + * @copyright xyzh + * @since 2025-09-28 + */ +public class UserPrincipal implements UserDetails { + + private TbSysUser user; + private List roles; + private List permissions; + + private UserPrincipal(TbSysUser user, List roles, List permissions) { + this.user = user; + this.roles = roles; + this.permissions = permissions; + } + + public static UserPrincipal create(TbSysUser user, List roles, List permissions) { + return new UserPrincipal(user, roles, permissions); + } + + @Override + public Collection getAuthorities() { + // 角色权限 + List roleAuthorities = roles.stream() + .map(role -> new SimpleGrantedAuthority("ROLE_" + role.getID())) + .collect(Collectors.toList()); + + // 功能权限 + List permissionAuthorities = permissions.stream() + .map(permission -> new SimpleGrantedAuthority(permission.getCode())) + .collect(Collectors.toList()); + + return Stream.concat(roleAuthorities.stream(), permissionAuthorities.stream()) + .collect(Collectors.toList()); + } + + @Override + public String getPassword() { + return user.getPassword(); + } + + @Override + public String getUsername() { + return user.getUsername(); + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + UserStatus status = UserStatus.fromCode(String.valueOf(user.getStatus())); + return status != UserStatus.LOCKED; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + UserStatus status = UserStatus.fromCode(String.valueOf(user.getStatus())); + return status == UserStatus.NORMAL; + } + + public TbSysUser getUser() { + return user; + } + + public List getRoles() { + return roles; + } + + public List getPermissions() { + return permissions; + } +} diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/filter/JwtAuthenticationFilter.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/filter/JwtAuthenticationFilter.java new file mode 100644 index 0000000..edfca2a --- /dev/null +++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/filter/JwtAuthenticationFilter.java @@ -0,0 +1,102 @@ +package org.xyzh.auth.filter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; +import org.xyzh.auth.service.UserDetailsServiceImpl; +import org.xyzh.auth.util.JwtTokenUtil; +import org.xyzh.auth.config.AuthProperties; + +import java.io.IOException; + +/** + * @description JwtAuthenticationFilter.java文件描述 JWT认证过滤器 + * @filename JwtAuthenticationFilter.java + * @author yslg + * @copyright xyzh + * @since 2025-09-28 + */ +@Component +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + @Autowired + private JwtTokenUtil jwtTokenUtil; + + @Autowired + private UserDetailsServiceImpl userDetailsService; + + @Autowired + private AuthProperties authProperties; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + + String requestURI = request.getRequestURI(); + + // 检查是否在白名单中 + if (isWhitelisted(requestURI)) { + filterChain.doFilter(request, response); + return; + } + + String token = getTokenFromRequest(request); + + if (StringUtils.hasText(token)) { + try { + String userId = jwtTokenUtil.getUserIdFromToken(token); + + if (userId != null && SecurityContextHolder.getContext().getAuthentication() == null) { + UserDetails userDetails = userDetailsService.loadUserByUserId(userId); + + if (jwtTokenUtil.validateToken(token, userId)) { + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } + } catch (Exception e) { + logger.error("JWT token validation failed: " + e.getMessage()); + } + } + + filterChain.doFilter(request, response); + } + + /** + * @description 从请求中获取Token + * @param request HTTP请求 + * @return String Token + * @author yslg + * @since 2025-09-28 + */ + private String getTokenFromRequest(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); + } + return null; + } + + /** + * @description 检查请求路径是否在白名单中 + * @param requestURI 请求URI + * @return boolean 是否在白名单中 + * @author yslg + * @since 2025-09-28 + */ + private boolean isWhitelisted(String requestURI) { + return authProperties.getWhiteList().stream() + .anyMatch(whitePath -> requestURI.matches(whitePath.replace("*", ".*"))); + } +} diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/service/LoginLogService.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/service/LoginLogService.java new file mode 100644 index 0000000..8aa1c05 --- /dev/null +++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/service/LoginLogService.java @@ -0,0 +1,69 @@ +package org.xyzh.auth.service; + +import org.springframework.stereotype.Service; +import org.xyzh.common.dto.log.TbSysLoginLog; +import org.xyzh.common.utils.IDUtils; + +/** + * @description LoginLogService.java文件描述 登录日志服务 + * @filename LoginLogService.java + * @author yslg + * @copyright xyzh + * @since 2025-09-28 + */ +@Service +public class LoginLogService { + + /** + * @description 保存登录日志 + * @param loginLog 登录日志 + * @author yslg + * @since 2025-09-28 + */ + public void saveLoginLog(TbSysLoginLog loginLog) { + // 确保登录日志有ID,如果没有则生成一个 + if (loginLog.getID() == null || loginLog.getID().isEmpty()) { + loginLog.setID(IDUtils.generateID()); + } + + // TODO: 实现登录日志保存逻辑 + // 这里应该调用数据层保存日志 + System.out.println("保存登录日志: " + loginLog); + } + + /** + * @description 根据用户ID查询登录日志 + * @param userId 用户ID + * @return List 登录日志列表 + * @author yslg + * @since 2025-09-28 + */ + public java.util.List findLoginLogsByUserId(String userId) { + // TODO: 实现根据用户ID查询登录日志的逻辑 + return new java.util.ArrayList<>(); + } + + /** + * @description 查询登录失败次数 + * @param userId 用户ID + * @param timeRange 时间范围(分钟) + * @return int 失败次数 + * @author yslg + * @since 2025-09-28 + */ + public int countFailedLoginAttempts(String userId, int timeRange) { + // TODO: 实现查询指定时间范围内的登录失败次数 + return 0; + } + + /** + * @description 清除登录失败记录 + * @param userId 用户ID + * @author yslg + * @since 2025-09-28 + */ + public void clearFailedLoginAttempts(String userId) { + // TODO: 实现清除登录失败记录的逻辑 + System.out.println("清除用户 " + userId + " 的登录失败记录"); + } +} diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/service/LoginServiceImpl.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/service/LoginServiceImpl.java new file mode 100644 index 0000000..24139e5 --- /dev/null +++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/service/LoginServiceImpl.java @@ -0,0 +1,228 @@ +package org.xyzh.auth.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.xyzh.api.auth.login.LoginService; +import org.xyzh.auth.strategy.LoginStrategy; +import org.xyzh.auth.strategy.LoginStrategyFactory; +import org.xyzh.auth.util.JwtTokenUtil; +import org.xyzh.common.core.domain.LoginParam; +import org.xyzh.common.core.domain.LoginDomain; +import org.xyzh.common.core.domain.ResultDomain; +import org.xyzh.common.core.enums.UserStatus; +import org.xyzh.common.dto.user.TbSysUser; +import org.xyzh.common.dto.log.TbSysLoginLog; +import org.xyzh.common.exception.auth.AuthException; +import org.xyzh.common.utils.IDUtils; +import org.xyzh.api.system.user.UserService; +import org.xyzh.api.system.role.RoleService; +import org.xyzh.api.system.permission.PermissionService; + +import java.util.Date; +import java.util.ArrayList; + +/** + * @description LoginServiceImpl.java文件描述 登录服务实现 + * @filename LoginServiceImpl.java + * @author yslg + * @copyright xyzh + * @since 2025-09-28 + */ +@Service +public class LoginServiceImpl implements LoginService { + + @Autowired + private LoginStrategyFactory loginStrategyFactory; + + @Autowired + private UserService userService; + + @Autowired(required = false) + private RoleService roleService; + + @Autowired(required = false) + private PermissionService permissionService; + + @Autowired + private JwtTokenUtil jwtTokenUtil; + + @Autowired + private LoginLogService loginLogService; + + @Override + public ResultDomain login(LoginParam loginParam) { + ResultDomain result = new ResultDomain<>(); + + try { + // 自动检测登录类型 + String loginType = detectLoginType(loginParam); + loginParam.setLoginType(loginType); + + // 获取对应的登录策略 + LoginStrategy strategy = loginStrategyFactory.getStrategy(loginType); + + // 验证登录参数 + if (!strategy.validate(loginParam)) { + result.fail("登录参数不正确"); + return result; + } + + // 查找用户 + TbSysUser user = strategy.findUser(loginParam); + if (user == null) { + result.fail("用户不存在"); + logLoginAttempt(loginParam, null, false, "用户不存在"); + return result; + } + + // 检查用户状态 + UserStatus userStatus = UserStatus.fromCode(String.valueOf(user.getStatus())); + if (userStatus != UserStatus.NORMAL) { + result.fail("用户状态异常: " + userStatus.getName()); + logLoginAttempt(loginParam, user, false, "用户状态异常: " + userStatus.getName()); + return result; + } + + // 验证密码 + if (!strategy.verifyPassword(loginParam.getPassword(), user.getPassword())) { + result.fail("密码错误"); + logLoginAttempt(loginParam, user, false, "密码错误"); + return result; + } + + // 构建登录域对象 + LoginDomain loginDomain = buildLoginDomain(user, loginType); + + // 生成JWT令牌 + String token = jwtTokenUtil.generateToken(loginDomain); + + // 记录成功登录日志 + logLoginAttempt(loginParam, user, true, "登录成功"); + + result.success("登录成功", (String)null); + result.setData(token); + + } catch (AuthException e) { + result.fail(e.getMessage()); + } catch (Exception e) { + result.fail("登录失败: " + e.getMessage()); + } + + return result; + } + + @Override + public ResultDomain logout(LoginDomain loginDomain) { + ResultDomain result = new ResultDomain<>(); + + try { + // TODO: 将token加入黑名单或从Redis中删除 + // 这里可以实现token黑名单机制 + + result.success("退出登录成功", (String)null); + } catch (Exception e) { + result.fail("退出登录失败: " + e.getMessage()); + } + + return result; + } + + /** + * @description 自动检测登录类型 + * @param loginParam 登录参数 + * @return String 登录类型 + * @author yslg + * @since 2025-09-28 + */ + private String detectLoginType(LoginParam loginParam) { + if (StringUtils.hasText(loginParam.getLoginType())) { + return loginParam.getLoginType(); + } + + if (StringUtils.hasText(loginParam.getEmail())) { + return "email"; + } + if (StringUtils.hasText(loginParam.getUsername())) { + return "username"; + } + if (StringUtils.hasText(loginParam.getPhone())) { + return "phone"; + } + if (StringUtils.hasText(loginParam.getWechatID())) { + return "wechat"; + } + + throw new AuthException("INVALID_LOGIN_TYPE", "无法确定登录类型"); + } + + /** + * @description 构建登录域对象 + * @param user 用户对象 + * @param loginType 登录类型 + * @return LoginDomain 登录域对象 + * @author yslg + * @since 2025-09-28 + */ + private LoginDomain buildLoginDomain(TbSysUser user, String loginType) { + LoginDomain loginDomain = new LoginDomain(); + loginDomain.setUser(user); + loginDomain.setLoginType(loginType); + loginDomain.setLoginTime(new Date()); + + // 获取用户角色和权限(如果服务可用) + if (roleService != null) { + try { + // TODO: 需要在RoleService中实现findRolesByUserId方法 + // loginDomain.setRoles(roleService.findRolesByUserId(user.getID())); + loginDomain.setRoles(new ArrayList<>()); + } catch (Exception e) { + loginDomain.setRoles(new ArrayList<>()); + } + } else { + loginDomain.setRoles(new ArrayList<>()); + } + + if (permissionService != null) { + try { + // TODO: 需要在PermissionService中实现findPermissionsByUserId方法 + // loginDomain.setPermissions(permissionService.findPermissionsByUserId(user.getID())); + loginDomain.setPermissions(new ArrayList<>()); + } catch (Exception e) { + loginDomain.setPermissions(new ArrayList<>()); + } + } else { + loginDomain.setPermissions(new ArrayList<>()); + } + + return loginDomain; + } + + /** + * @description 记录登录日志 + * @param loginParam 登录参数 + * @param user 用户对象 + * @param success 是否成功 + * @param message 消息 + * @author yslg + * @since 2025-09-28 + */ + private void logLoginAttempt(LoginParam loginParam, TbSysUser user, boolean success, String message) { + TbSysLoginLog loginLog = new TbSysLoginLog(); + + // 使用IDUtils生成登录日志ID + loginLog.setID(IDUtils.generateID()); + + if (user != null) { + loginLog.setUserID(user.getID()); + loginLog.setUsername(user.getUsername()); + } + // 注意:实际生产中不应记录密码 + // loginLog.setPassword(loginParam.getPassword()); + loginLog.setStatus(success ? 1 : 0); + loginLog.setMessage(message); + loginLog.setLoginTime(new Date().toString()); + + loginLogService.saveLoginLog(loginLog); + } +} diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/service/UserDetailsServiceImpl.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/service/UserDetailsServiceImpl.java new file mode 100644 index 0000000..45d88f2 --- /dev/null +++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/service/UserDetailsServiceImpl.java @@ -0,0 +1,95 @@ +package org.xyzh.auth.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import org.xyzh.auth.domain.UserPrincipal; +import org.xyzh.common.dto.user.TbSysUser; +import org.xyzh.common.dto.dept.TbSysDeptRole; +import org.xyzh.common.dto.permission.TbSysPermission; +import org.xyzh.api.system.user.UserService; +import org.xyzh.api.system.role.RoleService; +import org.xyzh.api.system.permission.PermissionService; + +import java.util.List; +import java.util.ArrayList; + +/** + * @description UserDetailsServiceImpl.java文件描述 用户详情服务实现 + * @filename UserDetailsServiceImpl.java + * @author yslg + * @copyright xyzh + * @since 2025-09-28 + */ +@Service +public class UserDetailsServiceImpl implements UserDetailsService { + + private static final Logger logger = LoggerFactory.getLogger(UserDetailsServiceImpl.class); + + @Autowired + private UserService userService; + + @Autowired(required = false) + private RoleService roleService; + + @Autowired(required = false) + private PermissionService permissionService; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + TbSysUser filter = new TbSysUser(); + filter.setUsername(username); + TbSysUser user = userService.find(filter); + + if (user == null) { + throw new UsernameNotFoundException("用户不存在: " + username); + } + + return loadUserByUserId(user.getID()); + } + + /** + * @description 根据用户ID加载用户详情 + * @param userId 用户ID + * @return UserDetails 用户详情 + * @author yslg + * @since 2025-09-28 + */ + public UserDetails loadUserByUserId(String userId) { + TbSysUser filter = new TbSysUser(); + filter.setID(userId); + TbSysUser user = userService.find(filter); + + if (user == null) { + throw new UsernameNotFoundException("用户不存在: " + userId); + } + + // 获取用户角色(如果角色服务可用) + List roles = new ArrayList<>(); + if (roleService != null) { + try { + // TODO: 需要在RoleService中实现findRolesByUserId方法 + // roles = roleService.findRolesByUserId(userId); + } catch (Exception e) { + logger.warn("无法获取用户角色: " + e.getMessage()); + } + } + + // 获取用户权限(如果权限服务可用) + List permissions = new ArrayList<>(); + if (permissionService != null) { + try { + // TODO: 需要在PermissionService中实现findPermissionsByUserId方法 + // permissions = permissionService.findPermissionsByUserId(userId); + } catch (Exception e) { + logger.warn("无法获取用户权限: " + e.getMessage()); + } + } + + return UserPrincipal.create(user, roles, permissions); + } +} diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/LoginStrategy.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/LoginStrategy.java new file mode 100644 index 0000000..06a4979 --- /dev/null +++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/LoginStrategy.java @@ -0,0 +1,50 @@ +package org.xyzh.auth.strategy; + +import org.xyzh.common.core.domain.LoginParam; +import org.xyzh.common.dto.user.TbSysUser; + +/** + * @description LoginStrategy.java文件描述 登录策略接口 + * @filename LoginStrategy.java + * @author yslg + * @copyright xyzh + * @since 2025-09-28 + */ +public interface LoginStrategy { + + /** + * @description 支持的登录类型 + * @return String 登录类型 + * @author yslg + * @since 2025-09-28 + */ + String getLoginType(); + + /** + * @description 验证登录参数 + * @param loginParam 登录参数 + * @return boolean 是否有效 + * @author yslg + * @since 2025-09-28 + */ + boolean validate(LoginParam loginParam); + + /** + * @description 根据登录参数查找用户 + * @param loginParam 登录参数 + * @return TbSysUser 用户对象 + * @author yslg + * @since 2025-09-28 + */ + TbSysUser findUser(LoginParam loginParam); + + /** + * @description 验证密码 + * @param inputPassword 输入密码 + * @param storedPassword 存储密码 + * @return boolean 是否匹配 + * @author yslg + * @since 2025-09-28 + */ + boolean verifyPassword(String inputPassword, String storedPassword); +} \ No newline at end of file diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/LoginStrategyFactory.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/LoginStrategyFactory.java new file mode 100644 index 0000000..2f578d5 --- /dev/null +++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/LoginStrategyFactory.java @@ -0,0 +1,51 @@ +package org.xyzh.auth.strategy; + +import org.springframework.stereotype.Component; +import org.xyzh.common.exception.auth.AuthException; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * @description LoginStrategyFactory.java文件描述 登录策略工厂 + * @filename LoginStrategyFactory.java + * @author yslg + * @copyright xyzh + * @since 2025-09-28 + */ +@Component +public class LoginStrategyFactory { + + private final Map strategies; + + public LoginStrategyFactory(List loginStrategies) { + this.strategies = loginStrategies.stream() + .collect(Collectors.toMap(LoginStrategy::getLoginType, Function.identity())); + } + + /** + * @description 获取登录策略 + * @param loginType 登录类型 + * @return LoginStrategy 登录策略 + * @author yslg + * @since 2025-09-28 + */ + public LoginStrategy getStrategy(String loginType) { + LoginStrategy strategy = strategies.get(loginType); + if (strategy == null) { + throw new AuthException("UNSUPPORTED_LOGIN_TYPE", "不支持的登录类型: " + loginType); + } + return strategy; + } + + /** + * @description 获取所有支持的登录类型 + * @return Set 登录类型集合 + * @author yslg + * @since 2025-09-28 + */ + public java.util.Set getSupportedLoginTypes() { + return strategies.keySet(); + } +} \ No newline at end of file diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/EmailLoginStrategy.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/EmailLoginStrategy.java new file mode 100644 index 0000000..acad866 --- /dev/null +++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/EmailLoginStrategy.java @@ -0,0 +1,49 @@ +package org.xyzh.auth.strategy.impl; + +import org.springframework.stereotype.Component; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.beans.factory.annotation.Autowired; +import org.xyzh.auth.strategy.LoginStrategy; +import org.xyzh.common.core.domain.LoginParam; +import org.xyzh.common.dto.user.TbSysUser; +import org.xyzh.api.system.user.UserService; + +/** + * @description EmailLoginStrategy.java文件描述 邮箱登录策略 + * @filename EmailLoginStrategy.java + * @author yslg + * @copyright xyzh + * @since 2025-09-28 + */ +@Component +public class EmailLoginStrategy implements LoginStrategy { + + @Autowired + private UserService userService; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Override + public String getLoginType() { + return "email"; + } + + @Override + public boolean validate(LoginParam loginParam) { + return loginParam.getEmail() != null && !loginParam.getEmail().trim().isEmpty() + && loginParam.getPassword() != null && !loginParam.getPassword().trim().isEmpty(); + } + + @Override + public TbSysUser findUser(LoginParam loginParam) { + TbSysUser filter = new TbSysUser(); + filter.setEmail(loginParam.getEmail()); + return userService.find(filter); + } + + @Override + public boolean verifyPassword(String inputPassword, String storedPassword) { + return passwordEncoder.matches(inputPassword, storedPassword); + } +} \ No newline at end of file diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/PhoneLoginStrategy.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/PhoneLoginStrategy.java new file mode 100644 index 0000000..fd2c1d2 --- /dev/null +++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/PhoneLoginStrategy.java @@ -0,0 +1,53 @@ +package org.xyzh.auth.strategy.impl; + +import org.springframework.stereotype.Component; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.beans.factory.annotation.Autowired; +import org.xyzh.auth.strategy.LoginStrategy; +import org.xyzh.common.core.domain.LoginParam; +import org.xyzh.common.dto.user.TbSysUser; +import org.xyzh.api.system.user.UserService; + +/** + * @description PhoneLoginStrategy.java文件描述 手机号登录策略 + * @filename PhoneLoginStrategy.java + * @author yslg + * @copyright xyzh + * @since 2025-09-28 + */ +@Component +public class PhoneLoginStrategy implements LoginStrategy { + + @Autowired + private UserService userService; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Override + public String getLoginType() { + return "phone"; + } + + @Override + public boolean validate(LoginParam loginParam) { + return loginParam.getPhone() != null && !loginParam.getPhone().trim().isEmpty() + && (loginParam.getPassword() != null || loginParam.getCaptcha() != null); + } + + @Override + public TbSysUser findUser(LoginParam loginParam) { + TbSysUser filter = new TbSysUser(); + filter.setPhone(loginParam.getPhone()); + return userService.find(filter); + } + + @Override + public boolean verifyPassword(String inputPassword, String storedPassword) { + // 手机号登录可能使用验证码,如果有验证码则跳过密码验证 + if (inputPassword == null) { + return true; // 假设验证码已经在其他地方验证过了 + } + return passwordEncoder.matches(inputPassword, storedPassword); + } +} \ No newline at end of file diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/UsernameLoginStrategy.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/UsernameLoginStrategy.java new file mode 100644 index 0000000..3f1d2fb --- /dev/null +++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/UsernameLoginStrategy.java @@ -0,0 +1,49 @@ +package org.xyzh.auth.strategy.impl; + +import org.springframework.stereotype.Component; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.beans.factory.annotation.Autowired; +import org.xyzh.auth.strategy.LoginStrategy; +import org.xyzh.common.core.domain.LoginParam; +import org.xyzh.common.dto.user.TbSysUser; +import org.xyzh.api.system.user.UserService; + +/** + * @description UsernameLoginStrategy.java文件描述 用户名登录策略 + * @filename UsernameLoginStrategy.java + * @author yslg + * @copyright xyzh + * @since 2025-09-28 + */ +@Component +public class UsernameLoginStrategy implements LoginStrategy { + + @Autowired + private UserService userService; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Override + public String getLoginType() { + return "username"; + } + + @Override + public boolean validate(LoginParam loginParam) { + return loginParam.getUsername() != null && !loginParam.getUsername().trim().isEmpty() + && loginParam.getPassword() != null && !loginParam.getPassword().trim().isEmpty(); + } + + @Override + public TbSysUser findUser(LoginParam loginParam) { + TbSysUser filter = new TbSysUser(); + filter.setUsername(loginParam.getUsername()); + return userService.find(filter); + } + + @Override + public boolean verifyPassword(String inputPassword, String storedPassword) { + return passwordEncoder.matches(inputPassword, storedPassword); + } +} \ No newline at end of file diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/WechatLoginStrategy.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/WechatLoginStrategy.java new file mode 100644 index 0000000..6a46f5c --- /dev/null +++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/strategy/impl/WechatLoginStrategy.java @@ -0,0 +1,47 @@ +package org.xyzh.auth.strategy.impl; + +import org.springframework.stereotype.Component; +import org.springframework.beans.factory.annotation.Autowired; +import org.xyzh.auth.strategy.LoginStrategy; +import org.xyzh.common.core.domain.LoginParam; +import org.xyzh.common.dto.user.TbSysUser; +import org.xyzh.api.system.user.UserService; + +/** + * @description WechatLoginStrategy.java文件描述 微信登录策略 + * @filename WechatLoginStrategy.java + * @author yslg + * @copyright xyzh + * @since 2025-09-28 + */ +@Component +public class WechatLoginStrategy implements LoginStrategy { + + @Autowired + private UserService userService; + + + @Override + public String getLoginType() { + return "wechat"; + } + + @Override + public boolean validate(LoginParam loginParam) { + return loginParam.getWechatID() != null && !loginParam.getWechatID().trim().isEmpty(); + } + + @Override + public TbSysUser findUser(LoginParam loginParam) { + TbSysUser filter = new TbSysUser(); + filter.setWechatID(loginParam.getWechatID()); + return userService.find(filter); + } + + @Override + public boolean verifyPassword(String inputPassword, String storedPassword) { + // 微信登录通常不需要密码验证,通过微信授权码验证 + // 这里可以添加微信授权验证逻辑 + return true; + } +} diff --git a/schoolNewsServ/auth/src/main/java/org/xyzh/auth/util/JwtTokenUtil.java b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/util/JwtTokenUtil.java new file mode 100644 index 0000000..2eabe8a --- /dev/null +++ b/schoolNewsServ/auth/src/main/java/org/xyzh/auth/util/JwtTokenUtil.java @@ -0,0 +1,151 @@ +package org.xyzh.auth.util; + +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.Keys; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.xyzh.common.core.domain.LoginDomain; +import org.xyzh.common.dto.user.TbSysUser; +import org.xyzh.common.utils.IDUtils; + +import javax.crypto.SecretKey; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * @description JwtTokenUtil.java文件描述 JWT工具类 + * @filename JwtTokenUtil.java + * @author yslg + * @copyright xyzh + * @since 2025-09-28 + */ +@Component +public class JwtTokenUtil { + + @Value("${school-news.auth.jwt-secret:schoolNewsDefaultSecretKeyForJWT2025}") + private String secret; + + @Value("${school-news.auth.jwt-expiration:86400}") + private Long expiration; + + private SecretKey getSigningKey() { + return Keys.hmacShaKeyFor(secret.getBytes()); + } + + /** + * @description 生成JWT令牌 + * @param loginDomain 登录域对象 + * @return String JWT令牌 + * @author yslg + * @since 2025-09-28 + */ + public String generateToken(LoginDomain loginDomain) { + Map claims = new HashMap<>(); + TbSysUser user = loginDomain.getUser(); + + claims.put("userId", user.getID()); + claims.put("username", user.getUsername()); + claims.put("email", user.getEmail()); + claims.put("loginType", loginDomain.getLoginType()); + claims.put("ipAddress", loginDomain.getIpAddress()); + + return Jwts.builder() + .setClaims(claims) + .setSubject(user.getID()) + .setId(IDUtils.generateID()) // 使用IDUtils生成JWT ID + .setIssuedAt(new Date()) + .setExpiration(generateExpirationDate()) + .signWith(getSigningKey()) + .compact(); + } + + /** + * @description 从令牌中获取用户ID + * @param token JWT令牌 + * @return String 用户ID + * @author yslg + * @since 2025-09-28 + */ + public String getUserIdFromToken(String token) { + return getClaimFromToken(token, Claims::getSubject); + } + + /** + * @description 从令牌中获取过期时间 + * @param token JWT令牌 + * @return Date 过期时间 + * @author yslg + * @since 2025-09-28 + */ + public Date getExpirationDateFromToken(String token) { + return getClaimFromToken(token, Claims::getExpiration); + } + + /** + * @description 从令牌中获取指定信息 + * @param token JWT令牌 + * @param claimsResolver 信息解析器 + * @return T 指定信息 + * @author yslg + * @since 2025-09-28 + */ + public T getClaimFromToken(String token, java.util.function.Function claimsResolver) { + final Claims claims = getAllClaimsFromToken(token); + return claimsResolver.apply(claims); + } + + /** + * @description 从令牌中获取所有信息 + * @param token JWT令牌 + * @return Claims 所有信息 + * @author yslg + * @since 2025-09-28 + */ + public Claims getAllClaimsFromToken(String token) { + return Jwts.parserBuilder() + .setSigningKey(getSigningKey()) + .build() + .parseClaimsJws(token) + .getBody(); + } + + /** + * @description 验证令牌 + * @param token JWT令牌 + * @param userId 用户ID + * @return boolean 是否有效 + * @author yslg + * @since 2025-09-28 + */ + public boolean validateToken(String token, String userId) { + try { + final String tokenUserId = getUserIdFromToken(token); + return (userId.equals(tokenUserId) && !isTokenExpired(token)); + } catch (Exception e) { + return false; + } + } + + /** + * @description 检查令牌是否过期 + * @param token JWT令牌 + * @return boolean 是否过期 + * @author yslg + * @since 2025-09-28 + */ + public boolean isTokenExpired(String token) { + final Date expiration = getExpirationDateFromToken(token); + return expiration.before(new Date()); + } + + /** + * @description 生成过期时间 + * @return Date 过期时间 + * @author yslg + * @since 2025-09-28 + */ + private Date generateExpirationDate() { + return new Date(System.currentTimeMillis() + expiration * 1000); + } +} diff --git a/schoolNewsServ/auth/src/main/resources/application.yml b/schoolNewsServ/auth/src/main/resources/application.yml new file mode 100644 index 0000000..6687dc0 --- /dev/null +++ b/schoolNewsServ/auth/src/main/resources/application.yml @@ -0,0 +1,63 @@ +server: + port: 8081 + +spring: + application: + name: school-news-auth + + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/school_news?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai + username: root + password: root + hikari: + maximum-pool-size: 20 + minimum-idle: 5 + +# 认证配置 +school-news: + auth: + # JWT配置 + jwt-secret: schoolNewsSecretKeyForJWT2025SecureEnough + jwt-expiration: 86400 # 24小时 + + # 安全配置 + max-login-attempts: 5 + lockout-duration: 30 # 锁定30分钟 + + # 免登录白名单 + white-list: + - "/auth/login" + - "/auth/logout" + - "/auth/captcha" + - "/auth/health" + - "/actuator/**" + - "/swagger-ui/**" + - "/v3/api-docs/**" + - "/favicon.ico" + - "/error" + - "/public/**" + - "/static/**" + +# 日志配置(使用log4j2配置文件) +logging: + config: classpath:log4j2-spring.xml + +# 管理端点配置 +management: + endpoints: + web: + exposure: + include: health,info,metrics + endpoint: + health: + show-details: when-authorized + +# 文档配置 +springdoc: + api-docs: + path: /v3/api-docs + swagger-ui: + path: /swagger-ui.html + tags-sorter: alpha + operations-sorter: alpha diff --git a/schoolNewsServ/auth/src/main/resources/log4j2-spring.xml b/schoolNewsServ/auth/src/main/resources/log4j2-spring.xml new file mode 100644 index 0000000..44801e2 --- /dev/null +++ b/schoolNewsServ/auth/src/main/resources/log4j2-spring.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +