From 5a6402f7fdfb09a96899f639ad7991c844e0b365 Mon Sep 17 00:00:00 2001 From: yulinling <2712495353@qq.com> Date: Sun, 15 Jun 2025 20:08:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20-=20=E5=AE=8C=E5=96=84Jwt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../workflow/config/MyBatisPlusConfig.java | 2 +- .../workflow/config/SecurityConfig.java | 18 +++++-- .../yulinling/workflow/constant/Status.java | 10 ++-- .../yulinling/workflow/model/ApiResponse.java | 49 ++++++++++--------- .../security/JwtAccessDeniedHandler.java | 45 +++++++++++++++++ .../security/JwtAuthenticationEntryPoint.java | 21 +++++++- .../security/JwtAuthenticationFilter.java | 22 ++------- .../security/JwtUserDetailsService.java | 14 ++++++ .../yulinling/workflow/utils/JwtUtil.java | 42 ++++++++++++++-- .../yulinling/workflow/utils/JwtUtilTest.java | 3 -- 10 files changed, 165 insertions(+), 61 deletions(-) create mode 100644 src/main/java/asia/yulinling/workflow/security/JwtAccessDeniedHandler.java diff --git a/src/main/java/asia/yulinling/workflow/config/MyBatisPlusConfig.java b/src/main/java/asia/yulinling/workflow/config/MyBatisPlusConfig.java index 21f57ea..f3c9203 100644 --- a/src/main/java/asia/yulinling/workflow/config/MyBatisPlusConfig.java +++ b/src/main/java/asia/yulinling/workflow/config/MyBatisPlusConfig.java @@ -9,7 +9,7 @@ import org.springframework.context.annotation.Configuration; /** *
- * MybatisPlus配置类 + * MybatisPlus配置类 *
* * @author YLL diff --git a/src/main/java/asia/yulinling/workflow/config/SecurityConfig.java b/src/main/java/asia/yulinling/workflow/config/SecurityConfig.java index 60ac3e9..21411fe 100644 --- a/src/main/java/asia/yulinling/workflow/config/SecurityConfig.java +++ b/src/main/java/asia/yulinling/workflow/config/SecurityConfig.java @@ -1,5 +1,6 @@ package asia.yulinling.workflow.config; +import asia.yulinling.workflow.security.JwtAccessDeniedHandler; import asia.yulinling.workflow.security.JwtAuthenticationEntryPoint; import asia.yulinling.workflow.security.JwtAuthenticationFilter; import asia.yulinling.workflow.security.JwtUserDetailsService; @@ -28,20 +29,29 @@ public class SecurityConfig { private final JwtUserDetailsService jwtUserDetailsService; @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtUtil jwtUtil, JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint) throws Exception { + public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtUtil jwtUtil, + JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint, + JwtAccessDeniedHandler jwtAccessDeniedHandler) throws Exception { http + // 关闭CSRF .csrf(AbstractHttpConfigurer::disable) + // 关闭session .sessionManagement(AbstractHttpConfigurer::disable) + // 登录登出自定义实现 + .formLogin(AbstractHttpConfigurer::disable) + .logout(AbstractHttpConfigurer::disable) + // 异常处理 .exceptionHandling(ex -> ex .authenticationEntryPoint(jwtAuthenticationEntryPoint) - .accessDeniedHandler((request, response, accessDeniedException) -> - response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied")) + .accessDeniedHandler(jwtAccessDeniedHandler) ) + // 认证请求 .authorizeHttpRequests(auth -> auth .requestMatchers("/login").permitAll() - .requestMatchers("/users", "/users/**").authenticated() + .requestMatchers("/users", "/users/**").hasRole("ADMIN") .anyRequest().authenticated() ) + // JWT过滤器 .addFilterBefore( new JwtAuthenticationFilter(jwtUtil, jwtUserDetailsService), UsernamePasswordAuthenticationFilter.class diff --git a/src/main/java/asia/yulinling/workflow/constant/Status.java b/src/main/java/asia/yulinling/workflow/constant/Status.java index c1dd399..ac290e5 100644 --- a/src/main/java/asia/yulinling/workflow/constant/Status.java +++ b/src/main/java/asia/yulinling/workflow/constant/Status.java @@ -4,7 +4,7 @@ import lombok.Getter; /** *- * 状态码封装 + * 状态码封装 *
* * @author yulinling @@ -18,14 +18,10 @@ public enum Status { /** 未知异常 */ UNKNOWN_ERROR(500, "Unknown error"); - /** - * 状态码 - */ + /** 状态码 */ private final Integer code; - /** - * 内容 - */ + /** 内容 */ private final String message; Status(Integer code, String message) { diff --git a/src/main/java/asia/yulinling/workflow/model/ApiResponse.java b/src/main/java/asia/yulinling/workflow/model/ApiResponse.java index 598d5d3..519a958 100644 --- a/src/main/java/asia/yulinling/workflow/model/ApiResponse.java +++ b/src/main/java/asia/yulinling/workflow/model/ApiResponse.java @@ -32,14 +32,15 @@ public class ApiResponse+ * JWT权限不足处理器 + *
+ * + * @author YLL + * @since 2025/6/15 + */ +@Component +public class JwtAccessDeniedHandler implements AccessDeniedHandler { + private final ObjectMapper objectMapper; + + public JwtAccessDeniedHandler(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException { + + // 1. 设置res 403 + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + + // 2. 构建通用response + BaseException baseException = new BaseException(HttpServletResponse.SC_FORBIDDEN, "权限不足"); + ApiResponse> apiResponse = ApiResponse.ofException(baseException, null); + + // 3. 将通用res写入res + response.getWriter().write(objectMapper.writeValueAsString(apiResponse)); + } +} diff --git a/src/main/java/asia/yulinling/workflow/security/JwtAuthenticationEntryPoint.java b/src/main/java/asia/yulinling/workflow/security/JwtAuthenticationEntryPoint.java index 5e70813..9f9063a 100644 --- a/src/main/java/asia/yulinling/workflow/security/JwtAuthenticationEntryPoint.java +++ b/src/main/java/asia/yulinling/workflow/security/JwtAuthenticationEntryPoint.java @@ -1,5 +1,8 @@ package asia.yulinling.workflow.security; +import asia.yulinling.workflow.exception.BaseException; +import asia.yulinling.workflow.model.ApiResponse; +import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.core.AuthenticationException; @@ -18,9 +21,25 @@ import java.io.IOException; */ @Component public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + private final ObjectMapper objectMapper; + + public JwtAuthenticationEntryPoint(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { - response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized: Missing or invalid token"); + + // 1. 设置res 401 + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + + // 2. 构建通用response + BaseException baseException = new BaseException(401, "账号未登录"); + ApiResponse> apiResponse = ApiResponse.ofException(baseException, null); + + // 3. 将通用res写入res + response.getWriter().write(objectMapper.writeValueAsString(apiResponse)); } } diff --git a/src/main/java/asia/yulinling/workflow/security/JwtAuthenticationFilter.java b/src/main/java/asia/yulinling/workflow/security/JwtAuthenticationFilter.java index 9800f4a..e528a0f 100644 --- a/src/main/java/asia/yulinling/workflow/security/JwtAuthenticationFilter.java +++ b/src/main/java/asia/yulinling/workflow/security/JwtAuthenticationFilter.java @@ -38,16 +38,14 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException { - log.info("request: {}", request.getHeader("Authorization")); - String token = getTokenFromRequest(request); - log.info("token: {}", token); + // 1. 拿取Token + String token = jwtUtil.getTokenFromRequest(request); if (StringUtils.hasText(token) && jwtUtil.validateToken(token)) { - // 解析token获取username + // 2. 解析token获取username String username = jwtUtil.parseToken(token).getSubject(); - log.info("username: {}", username); - // 根据token获取的username,加载当前登录中userDetails + // 3. 根据username验证password UserDetails userDetails = userDetailsService.loadUserByUsername(username); log.info("userDetails: {}", userDetails); @@ -61,16 +59,4 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { filterChain.doFilter(request, response); } - - private @Nullable String getTokenFromRequest(HttpServletRequest request) { -// eyJhbGciOiJIUzM4NCJ9.eyJzdWIiOiJhZG1pbiIsImlhdCI6MTc0OTk2MzA1OSwiZXhwIjoxNzQ5OTYzNjU5fQ.QxiZmycBGxfVfooh_T_lo9SibugLZ2bFt752UChHdtpNb6u__iXodQDK_s6hcz0R -// eyJhbGciOiJIUzI1NiJ9.e30.7QzwIJVh2WpbwTF5ce4crYy3kK2-4GOs0eYJqrGD8FU - 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/JwtUserDetailsService.java b/src/main/java/asia/yulinling/workflow/security/JwtUserDetailsService.java index a9fee01..c32006a 100644 --- a/src/main/java/asia/yulinling/workflow/security/JwtUserDetailsService.java +++ b/src/main/java/asia/yulinling/workflow/security/JwtUserDetailsService.java @@ -37,6 +37,7 @@ public class JwtUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + // 1. 构建查找sql LambdaQueryWrapper