From 23f65a4e5dfca889be3dcce9c5f73237a7b0d339 Mon Sep 17 00:00:00 2001 From: yulinling <2712495353@qq.com> Date: Sun, 15 Jun 2025 00:41:20 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20-=20=E5=AE=8C=E5=96=84Spring=20Security?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yulinling/workflow/config/JwtConfig.java | 31 ------ .../workflow/config/SecurityConfig.java | 48 ++-------- .../workflow/controller/AuthController.java | 33 +++++++ .../workflow/controller/LoginController.java | 51 ---------- .../dto/response/JWTAuthResponse.java | 21 ++++ ....java => JwtAuthenticationEntryPoint.java} | 2 +- .../security/JwtAuthenticationFilter.java | 64 +++++++------ .../security/JwtAuthenticationToken.java | 37 -------- .../JwtUserDetailsService.java} | 23 +++-- .../workflow/service/AuthService.java | 15 +++ .../service/impl/AuthServiceImpl.java | 36 +++++++ .../yulinling/workflow/utils/JwtUtil.java | 95 ++++++++----------- src/main/resources/application.properties | 2 +- src/main/resources/db/data.sql | 64 ++++++------- src/main/resources/db/schema.sql | 2 +- .../yulinling/workflow/utils/JwtUtilTest.java | 41 ++++---- 16 files changed, 256 insertions(+), 309 deletions(-) delete mode 100644 src/main/java/asia/yulinling/workflow/config/JwtConfig.java create mode 100644 src/main/java/asia/yulinling/workflow/controller/AuthController.java delete mode 100644 src/main/java/asia/yulinling/workflow/controller/LoginController.java create mode 100644 src/main/java/asia/yulinling/workflow/dto/response/JWTAuthResponse.java rename src/main/java/asia/yulinling/workflow/security/{AuthEntryPoint.java => JwtAuthenticationEntryPoint.java} (90%) delete mode 100644 src/main/java/asia/yulinling/workflow/security/JwtAuthenticationToken.java rename src/main/java/asia/yulinling/workflow/{service/CustomerDetailsService.java => security/JwtUserDetailsService.java} (70%) create mode 100644 src/main/java/asia/yulinling/workflow/service/AuthService.java create mode 100644 src/main/java/asia/yulinling/workflow/service/impl/AuthServiceImpl.java diff --git a/src/main/java/asia/yulinling/workflow/config/JwtConfig.java b/src/main/java/asia/yulinling/workflow/config/JwtConfig.java deleted file mode 100644 index 1d67657..0000000 --- a/src/main/java/asia/yulinling/workflow/config/JwtConfig.java +++ /dev/null @@ -1,31 +0,0 @@ -package asia.yulinling.workflow.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - *

- * JWT配置类 - *

- * - * @author YLL - * @since 2025/6/11 - */ -@ConfigurationProperties(prefix = "jwt.config") -@Data -public class JwtConfig { - /** - * jwt 加密 key,默认值:kw. - */ - private String key = "kw"; - - /** - * jwt 过期时间,默认值:600000 {@code 10 分钟}. - */ - private Long ttl = 600000L; - - /** - * 开启 记住我 之后 jwt 过期时间,默认值 604800000 {@code 7 天} - */ - private Long remember = 604800000L; -} diff --git a/src/main/java/asia/yulinling/workflow/config/SecurityConfig.java b/src/main/java/asia/yulinling/workflow/config/SecurityConfig.java index 00673c5..2ab1fb2 100644 --- a/src/main/java/asia/yulinling/workflow/config/SecurityConfig.java +++ b/src/main/java/asia/yulinling/workflow/config/SecurityConfig.java @@ -1,16 +1,12 @@ package asia.yulinling.workflow.config; - -import asia.yulinling.workflow.model.vo.user.UserPrincipal; -import asia.yulinling.workflow.security.AuthEntryPoint; +import asia.yulinling.workflow.security.JwtAuthenticationEntryPoint; import asia.yulinling.workflow.security.JwtAuthenticationFilter; -import asia.yulinling.workflow.service.CustomerDetailsService; +import asia.yulinling.workflow.security.JwtUserDetailsService; import asia.yulinling.workflow.utils.JwtUtil; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.security.authentication.dao.DaoAuthenticationProvider; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; @@ -18,16 +14,10 @@ import org.springframework.security.config.annotation.authentication.configurati import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; - -import java.util.List; @Configuration @EnableWebSecurity @@ -35,14 +25,14 @@ import java.util.List; @Slf4j public class SecurityConfig { - private final CustomerDetailsService customerDetailService; + private final JwtUserDetailsService jwtUserDetailsService; @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtUtil jwtUtil, AuthEntryPoint authEntryPoint, RequestMatcher requestMatcher) throws Exception { + public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtUtil jwtUtil, JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint) throws Exception { http .csrf(AbstractHttpConfigurer::disable) .exceptionHandling(ex -> ex - .authenticationEntryPoint(authEntryPoint) + .authenticationEntryPoint(jwtAuthenticationEntryPoint) .accessDeniedHandler((request, response, accessDeniedException) -> response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied")) ) .authorizeHttpRequests(auth -> auth @@ -52,9 +42,10 @@ public class SecurityConfig { ) .sessionManagement(AbstractHttpConfigurer::disable) .addFilterBefore( - new JwtAuthenticationFilter(jwtUtil, authEntryPoint, requestMatcher), + new JwtAuthenticationFilter(jwtUtil, jwtUserDetailsService), UsernamePasswordAuthenticationFilter.class - ); + ) + .userDetailsService(jwtUserDetailsService); return http.build(); } @@ -67,29 +58,6 @@ public class SecurityConfig { return new BCryptPasswordEncoder(); } - @Bean RequestMatcher requestMatcher() { - return new AntPathRequestMatcher("/login", "POST"); - } - - @Bean - public DaoAuthenticationProvider authenticationProvider() { - DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); - authProvider.setUserDetailsService(customerDetailService); - return authProvider; - } - - @Bean - public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) { - UserPrincipal user = new UserPrincipal( - 1L, - "admin", - passwordEncoder.encode("123456"), - List.of("ADMIN"), - List.of(new SimpleGrantedAuthority("ROLE_ADMIN")) - ); - return new InMemoryUserDetailsManager(user); - } - @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { return configuration.getAuthenticationManager(); diff --git a/src/main/java/asia/yulinling/workflow/controller/AuthController.java b/src/main/java/asia/yulinling/workflow/controller/AuthController.java new file mode 100644 index 0000000..189b28c --- /dev/null +++ b/src/main/java/asia/yulinling/workflow/controller/AuthController.java @@ -0,0 +1,33 @@ +package asia.yulinling.workflow.controller; + +import asia.yulinling.workflow.dto.request.LoginRequest; +import asia.yulinling.workflow.service.AuthService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +/** + *

+ * 登录控制层 + *

+ * + * @author YLL + * @since 2025/6/13 + */ +@RestController +@RequiredArgsConstructor +@Slf4j +public class AuthController { + + private final AuthService authenticate; + + @PostMapping("/login") + public ResponseEntity login(@RequestBody LoginRequest loginRequest) { + String token = authenticate.login(loginRequest); + return ResponseEntity.ok(token); + } +} + diff --git a/src/main/java/asia/yulinling/workflow/controller/LoginController.java b/src/main/java/asia/yulinling/workflow/controller/LoginController.java deleted file mode 100644 index e6c9cad..0000000 --- a/src/main/java/asia/yulinling/workflow/controller/LoginController.java +++ /dev/null @@ -1,51 +0,0 @@ -package asia.yulinling.workflow.controller; - -import asia.yulinling.workflow.dto.request.LoginRequest; -import asia.yulinling.workflow.utils.JwtUtil; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; - -/** - *

- * 登录控制层 - *

- * - * @author YLL - * @since 2025/6/13 - */ -@RestController -@RequiredArgsConstructor -@Slf4j -public class LoginController { - - private final AuthenticationManager authenticationManager; - private final JwtUtil jwtUtil; - - @PostMapping("/login") - public ResponseEntity login(@RequestBody LoginRequest loginRequest) { - try { - UsernamePasswordAuthenticationToken authToken = - new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()); - - Authentication authentication = authenticationManager.authenticate(authToken); - - log.info("{}", authentication.getPrincipal()); - String token = jwtUtil.generateToken(authentication, false); // 可根据需要传 true/false - - return ResponseEntity.ok(token); - } catch (AuthenticationException ex) { - log.info("{}", ex.getMessage()); - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("用户名或密码错误"); - } - } -} - diff --git a/src/main/java/asia/yulinling/workflow/dto/response/JWTAuthResponse.java b/src/main/java/asia/yulinling/workflow/dto/response/JWTAuthResponse.java new file mode 100644 index 0000000..9fc606f --- /dev/null +++ b/src/main/java/asia/yulinling/workflow/dto/response/JWTAuthResponse.java @@ -0,0 +1,21 @@ +package asia.yulinling.workflow.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *

+ * JWT响应体 + *

+ * + * @author YLL + * @since 2025/6/15 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class JWTAuthResponse { + private String accessToken; + private String tokenType = "Bearer"; +} diff --git a/src/main/java/asia/yulinling/workflow/security/AuthEntryPoint.java b/src/main/java/asia/yulinling/workflow/security/JwtAuthenticationEntryPoint.java similarity index 90% rename from src/main/java/asia/yulinling/workflow/security/AuthEntryPoint.java rename to src/main/java/asia/yulinling/workflow/security/JwtAuthenticationEntryPoint.java index cb72d43..5e70813 100644 --- a/src/main/java/asia/yulinling/workflow/security/AuthEntryPoint.java +++ b/src/main/java/asia/yulinling/workflow/security/JwtAuthenticationEntryPoint.java @@ -17,7 +17,7 @@ import java.io.IOException; * @since 2025/6/13 */ @Component -public class AuthEntryPoint implements AuthenticationEntryPoint { +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { diff --git a/src/main/java/asia/yulinling/workflow/security/JwtAuthenticationFilter.java b/src/main/java/asia/yulinling/workflow/security/JwtAuthenticationFilter.java index 88e4bd4..c406037 100644 --- a/src/main/java/asia/yulinling/workflow/security/JwtAuthenticationFilter.java +++ b/src/main/java/asia/yulinling/workflow/security/JwtAuthenticationFilter.java @@ -7,11 +7,12 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; @@ -28,39 +29,40 @@ import java.io.IOException; @Slf4j public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtUtil jwtUtil; - private final AuthenticationEntryPoint authenticationEntryPoint; - private final RequestMatcher requestMatcher; + private final UserDetailsService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, - FilterChain filterChain - ) - throws ServletException, IOException { - log.info("{}", request.getRequestURI()); - if (requestMatcher.matches(request)) { - log.info("{}", request.getRequestURI()); - filterChain.doFilter(request, response); - return; - } - String header = request.getHeader("Authorization"); - if (header == null || !header.startsWith("Bearer ")) { - String token = null; - if (header != null) { - token = header.substring(7); - } - if (jwtUtil.validateToken(token)) { - String username = jwtUtil.parseToken(token).getSubject(); - String roles = jwtUtil.parseToken(token).get("roles").toString(); - Authentication auth = new JwtAuthenticationToken(username, roles); - SecurityContextHolder.getContext().setAuthentication(auth); - } else { - authenticationEntryPoint.commence(request, response, new AuthenticationException("") { - }); - return; - } + FilterChain filterChain) throws ServletException, IOException { + + String token = getTokenFromRequest(request); + log.info("token: {}", token); + + if (StringUtils.hasText(token) && jwtUtil.validateToken(token)) { + // get username from token + String username = jwtUtil.parseToken(token).getSubject(); + + // load the user associated with token + UserDetails userDetails = userDetailsService.loadUserByUsername(username); + + UsernamePasswordAuthenticationToken authenticationToken + = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + SecurityContextHolder.getContext().setAuthentication(authenticationToken); } filterChain.doFilter(request, response); } + + private String getTokenFromRequest(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (bearerToken != null && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); + } + + return null; + } } diff --git a/src/main/java/asia/yulinling/workflow/security/JwtAuthenticationToken.java b/src/main/java/asia/yulinling/workflow/security/JwtAuthenticationToken.java deleted file mode 100644 index 62077e0..0000000 --- a/src/main/java/asia/yulinling/workflow/security/JwtAuthenticationToken.java +++ /dev/null @@ -1,37 +0,0 @@ -package asia.yulinling.workflow.security; - -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.core.authority.SimpleGrantedAuthority; - -import java.util.Collections; - -/** - *

- * JWT认证 - *

- * - * @author YLL - * @since 2025/6/13 - */ -public class JwtAuthenticationToken extends AbstractAuthenticationToken { - - private final String principal; - private final String role; - - public JwtAuthenticationToken(String username, String role) { - super(Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + role))); - this.principal = username; - this.role = role; - setAuthenticated(true); - } - - @Override - public Object getPrincipal() { - return principal; - } - - @Override - public Object getCredentials() { - return ""; - } -} diff --git a/src/main/java/asia/yulinling/workflow/service/CustomerDetailsService.java b/src/main/java/asia/yulinling/workflow/security/JwtUserDetailsService.java similarity index 70% rename from src/main/java/asia/yulinling/workflow/service/CustomerDetailsService.java rename to src/main/java/asia/yulinling/workflow/security/JwtUserDetailsService.java index 24d979e..a9fee01 100644 --- a/src/main/java/asia/yulinling/workflow/service/CustomerDetailsService.java +++ b/src/main/java/asia/yulinling/workflow/security/JwtUserDetailsService.java @@ -1,16 +1,15 @@ -package asia.yulinling.workflow.service; +package asia.yulinling.workflow.security; -import asia.yulinling.workflow.mapper.PermissionMapper; import asia.yulinling.workflow.mapper.RoleMapper; import asia.yulinling.workflow.mapper.UserMapper; -import asia.yulinling.workflow.model.entity.Permission; import asia.yulinling.workflow.model.entity.Role; import asia.yulinling.workflow.model.entity.User; -import asia.yulinling.workflow.model.vo.user.UserPrincipal; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; @@ -18,6 +17,7 @@ import org.springframework.stereotype.Service; import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; /** @@ -31,10 +31,9 @@ import java.util.stream.Collectors; @Service @RequiredArgsConstructor @Slf4j -public class CustomerDetailsService implements UserDetailsService { +public class JwtUserDetailsService implements UserDetailsService { private final UserMapper userMapper; private final RoleMapper roleMapper; - private final PermissionMapper permissionMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { @@ -47,9 +46,15 @@ public class CustomerDetailsService implements UserDetailsService { User user = userMapper.selectOne(queryWrapper); List roles = roleMapper.selectByIds(Collections.singleton(user.getId())); - List roleIds = roles.stream().map(Role::getId).collect(Collectors.toList()); - List permissions = permissionMapper.selectByIds(roleIds); - return UserPrincipal.create(user, roles, permissions); + + Set authorities = roles.stream() + .map((role) -> new SimpleGrantedAuthority(role.getName())) + .collect(Collectors.toSet()); + return new org.springframework.security.core.userdetails.User( + username, + user.getPassword(), + authorities + ); } } diff --git a/src/main/java/asia/yulinling/workflow/service/AuthService.java b/src/main/java/asia/yulinling/workflow/service/AuthService.java new file mode 100644 index 0000000..7293440 --- /dev/null +++ b/src/main/java/asia/yulinling/workflow/service/AuthService.java @@ -0,0 +1,15 @@ +package asia.yulinling.workflow.service; + +import asia.yulinling.workflow.dto.request.LoginRequest; + +/** + *

+ * 账号权限类 + *

+ * + * @author YLL + * @since 2025/6/14 + */ +public interface AuthService { + String login(LoginRequest loginRequest); +} diff --git a/src/main/java/asia/yulinling/workflow/service/impl/AuthServiceImpl.java b/src/main/java/asia/yulinling/workflow/service/impl/AuthServiceImpl.java new file mode 100644 index 0000000..d106128 --- /dev/null +++ b/src/main/java/asia/yulinling/workflow/service/impl/AuthServiceImpl.java @@ -0,0 +1,36 @@ +package asia.yulinling.workflow.service.impl; + +import asia.yulinling.workflow.dto.request.LoginRequest; +import asia.yulinling.workflow.service.AuthService; +import asia.yulinling.workflow.utils.JwtUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; + +/** + *

+ * 账号权限类实现 + *

+ * + * @author YLL + * @since 2025/6/14 + */ +@Service +@RequiredArgsConstructor +public class AuthServiceImpl implements AuthService { + + private final AuthenticationManager authenticationManager; + private final JwtUtil jwtUtil; + + @Override + public String login(LoginRequest loginRequest) { + Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken( + loginRequest.getUsername(), loginRequest.getPassword() + )); + SecurityContextHolder.getContext().setAuthentication(authentication); + return jwtUtil.generateToken(authentication, false); + } +} diff --git a/src/main/java/asia/yulinling/workflow/utils/JwtUtil.java b/src/main/java/asia/yulinling/workflow/utils/JwtUtil.java index 59dd2ea..f8ea3c6 100644 --- a/src/main/java/asia/yulinling/workflow/utils/JwtUtil.java +++ b/src/main/java/asia/yulinling/workflow/utils/JwtUtil.java @@ -1,21 +1,16 @@ package asia.yulinling.workflow.utils; -import asia.yulinling.workflow.config.JwtConfig; -import asia.yulinling.workflow.model.vo.user.UserPrincipal; +import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; import io.jsonwebtoken.*; import org.springframework.stereotype.Component; -import java.nio.charset.StandardCharsets; import java.security.Key; -import java.util.Collection; import java.util.Date; -import java.util.List; /** *

@@ -25,47 +20,27 @@ import java.util.List; * @author YLL * @since 2025/6/11 */ -@EnableConfigurationProperties(JwtConfig.class) @RequiredArgsConstructor @Component @Slf4j public class JwtUtil { - private final JwtConfig jwtConfig; + /** + * jwt 加密 key,默认值:kw. + */ + @Value("${jwt.config.key}") + private String key = "daf66e01593f61a15b857cf433aae03a005812b31234e149036bcc8dee755dbb"; /** - * 创建JWT - * - * @param isRememberMe 记住我 - * @param userId 用户id - * @param subject 用户名 - * @param roles 用户角色 - * @param authorities 用户权限 - * @return JWT + * jwt 过期时间,默认值:600000 {@code 10 分钟}. */ - public String generateToken(boolean isRememberMe, Long userId, String subject, List roles, Collection authorities) { - // 1. 构建签名密钥 - Key key = getSigningKey(); + @Value("${jwt.config.ttl}") + private Long ttl = 600000L; - // 2. 当前时间 - Date now = new Date(); - - // 3. 计算过期时间 - long ttl = isRememberMe ? jwtConfig.getRemember() : jwtConfig.getTtl(); - Date expiration = new Date(now.getTime() + ttl); - - // 4. 构建 JWT - JwtBuilder builder = Jwts.builder() - .setId(String.valueOf(userId)) - .setSubject(subject) - .setIssuedAt(now) - .setExpiration(expiration) - .claim("roles", roles) - .claim("authorities", authorities) - .signWith(key); - - // 5. 返回生成的 token 字符串 - return builder.compact(); - } + /** + * 开启 记住我 之后 jwt 过期时间,默认值 604800000 {@code 7 天} + */ + @Value("${jwt.config.remember}") + private Long remember = 604800000L; /** * 创建JWT @@ -74,15 +49,27 @@ public class JwtUtil { * @param isRememberMe 记住我 * @return JWT */ - public String generateToken(Authentication authentication, Boolean isRememberMe) { - UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal(); - return generateToken( - isRememberMe, - userPrincipal.getId(), - userPrincipal.getUsername(), - userPrincipal.getRoles(), - userPrincipal.getAuthorities() - ); + public String generateToken(Authentication authentication, boolean isRememberMe) { + // 1. 构建签名密钥 + Key key = key(); + + // 2. 当前时间 + Date now = new Date(); + + // 3. 计算过期时间 + long ttl = isRememberMe ? this.remember : this.ttl; + Date expiration = new Date(now.getTime() + ttl); + + // 4. 构建 JWT + String username = authentication.getName(); + JwtBuilder builder = Jwts.builder() + .setSubject(username) + .setIssuedAt(now) + .setExpiration(expiration) + .signWith(key); + + // 5. 返回生成的 token 字符串 + return builder.compact(); } /** @@ -93,7 +80,7 @@ public class JwtUtil { */ public Claims parseToken(String token) { try { - Key key = getSigningKey(); + Key key = key(); return Jwts.parserBuilder() .setSigningKey(key) @@ -120,7 +107,7 @@ public class JwtUtil { */ public boolean validateToken(String token) { try { - Jwts.parserBuilder().setSigningKey(getSigningKey()).build().parseClaimsJws(token); + Jwts.parserBuilder().setSigningKey(key()).build().parseClaimsJws(token); return true; } catch (JwtException e) { return false; @@ -132,11 +119,11 @@ public class JwtUtil { * * @return 返回key */ - private Key getSigningKey() { - String secret = jwtConfig.getKey(); + private Key key() { + String secret = this.key; if (secret == null || secret.isEmpty()) { throw new IllegalStateException("JWT 签名密钥未配置"); } - return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); + return Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret)); } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index be36363..8a3c9a8 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -38,6 +38,6 @@ spring.mail.properties.mail.display.sendmail=spring-boot-demo # Jasypt配置 jasypt.encryptor.password=abc # JWT配置 -jwt.config.key=kw +jwt.config.key=daf66e01593f61a15b857cf433aae03a005812b31234e149036bcc8dee755dbb jwt.config.ttl=600000 jwt.config.remember=604800000 \ No newline at end of file diff --git a/src/main/resources/db/data.sql b/src/main/resources/db/data.sql index e929152..7487ad8 100644 --- a/src/main/resources/db/data.sql +++ b/src/main/resources/db/data.sql @@ -45,41 +45,37 @@ COMMIT; BEGIN; -INSERT INTO `wk_user` ( - id, username, nickname, password, email, birthday, sex, phone, status, - create_time, update_time, last_login_time -) VALUES ( - 1072806377661009920, - 'admin', - '管理员', - 'ff342e862e7c3285cdc07e56d6b8973b', - 'admin@xkcoding.com', - '1994-11-28 00:00:00', -- birthday: 785433600000 → 1994-11-28 - 1, - '17300000000', - 1, - '2018-12-12 14:52:27', -- create_time - '2018-12-12 14:52:27', -- update_time - '2018-12-12 14:52:27' -- last_login_time - ); +INSERT INTO `wk_user` (id, username, nickname, password, email, birthday, sex, phone, status, + create_time, update_time, last_login_time) +VALUES (1072806377661009920, + 'admin', + '管理员', + '$2a$10$iH/XRNn1Y5Gcyl1iG5zCD..Qq1L2YnL6xU0XmsdxmUs5SEsU/7jVe', + 'admin@xkcoding.com', + '1994-11-28 00:00:00', -- birthday: 785433600000 → 1994-11-28 + 1, + '17300000000', + 1, + '2018-12-12 14:52:27', -- create_time + '2018-12-12 14:52:27', -- update_time + '2018-12-12 14:52:27' -- last_login_time + ); -INSERT INTO `wk_user` ( - id, username, nickname, password, email, birthday, sex, phone, status, - create_time, update_time, last_login_time -) VALUES ( - 1072806378780889088, - 'user', - '普通用户', - '6c6bf02c8d5d3d128f34b1700cb1e32c', - 'user@xkcoding.com', - '1994-11-28 00:00:00', -- birthday: 785433600000 → 1994-11-28 - 1, - '17300001111', - 1, - '2018-12-12 14:52:27', -- create_time - '2018-12-12 14:52:27', -- update_time - '2018-12-12 14:52:27' -- last_login_time - ); +INSERT INTO `wk_user` (id, username, nickname, password, email, birthday, sex, phone, status, + create_time, update_time, last_login_time) +VALUES (1072806378780889088, + 'user', + '普通用户', + '$2a$10$iH/XRNn1Y5Gcyl1iG5zCD..Qq1L2YnL6xU0XmsdxmUs5SEsU/7jVe', + 'user@xkcoding.com', + '1994-11-28 00:00:00', -- birthday: 785433600000 → 1994-11-28 + 1, + '17300001111', + 1, + '2018-12-12 14:52:27', -- create_time + '2018-12-12 14:52:27', -- update_time + '2018-12-12 14:52:27' -- last_login_time + ); COMMIT; diff --git a/src/main/resources/db/schema.sql b/src/main/resources/db/schema.sql index 792170a..f6c27cc 100644 --- a/src/main/resources/db/schema.sql +++ b/src/main/resources/db/schema.sql @@ -4,7 +4,7 @@ CREATE TABLE `wk_user` `id` bigint(64) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键', `username` VARCHAR(32) NOT NULL UNIQUE COMMENT '用户名', `nickname` VARCHAR(32) NOT NULL UNIQUE COMMENT '昵称', - `password` VARCHAR(32) NOT NULL COMMENT '加密后的密码', + `password` VARCHAR(256) NOT NULL COMMENT '加密后的密码', `email` VARCHAR(32) NOT NULL UNIQUE COMMENT '邮箱', `birthday` DATETIME DEFAULT NULL COMMENT '生日', `sex` INT(2) DEFAULT NULL COMMENT '性别,男-1,女-2', diff --git a/src/test/java/asia/yulinling/workflow/utils/JwtUtilTest.java b/src/test/java/asia/yulinling/workflow/utils/JwtUtilTest.java index 2f1e869..059a999 100644 --- a/src/test/java/asia/yulinling/workflow/utils/JwtUtilTest.java +++ b/src/test/java/asia/yulinling/workflow/utils/JwtUtilTest.java @@ -1,11 +1,14 @@ package asia.yulinling.workflow.utils; -import asia.yulinling.workflow.config.JwtConfig; import io.jsonwebtoken.Claims; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.BeforeEach; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import java.util.List; @@ -24,34 +27,35 @@ class JwtUtilTest { @BeforeEach void setUp() { - // 构造 JwtConfig 模拟配置 - JwtConfig config = new JwtConfig(); - config.setKey("your-256-bit-secret-key-which-needs-to-be-long-enough"); // 至少256位bit - config.setTtl(3600000L); // 1小时 - config.setRemember(604800000L); // 7天 - - jwtUtil = new JwtUtil(config); + jwtUtil = new JwtUtil(); } @Test void testGenerateTokenAndParseToken() { - Long userId = 123L; - String username = "test_user"; - List roles = List.of("USER", "ADMIN"); - List authorities = List.of(new SimpleGrantedAuthority("ROLE_USER")); - - String token = jwtUtil.generateToken(false, userId, username, roles, authorities); + UserDetails user = User.withUsername("test") + .password("test") + .roles("USER") + .build(); + Authentication authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); + String token = jwtUtil.generateToken(authentication, false); assertNotNull(token); Claims claims = jwtUtil.parseToken(token); - assertEquals(userId.toString(), claims.getId()); - assertEquals(username, claims.getSubject()); - assertEquals(roles, claims.get("roles", List.class)); + assertEquals("test", claims.getSubject()); } @Test void testValidateToken() { - String token = jwtUtil.generateToken(false, 1L, "valid_user", List.of("USER"), List.of()); + UserDetails user = User.withUsername("test") + .password("test") + .roles("USER") + .build(); + Authentication authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); + String token = jwtUtil.generateToken(authentication, false); + BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + String rawPassword = "admin"; + String encodedPassword = encoder.encode(rawPassword); + System.out.println(encodedPassword); assertTrue(jwtUtil.validateToken(token)); } @@ -75,7 +79,6 @@ class JwtUtilTest { Claims claims = jwtUtil.parseToken(token); assertEquals("admin", claims.getSubject()); - assertEquals("100", claims.getId()); } // Mock UserPrincipal 用于测试