fix: return 401 (not 403) for unauthenticated requests across all services

Spring Security's default Http403ForbiddenEntryPoint was returning 403
for all auth failures. Frontend clients treat 403 as a permission error
(not an auth error), so silent loops occurred instead of proper re-login.
Adding a custom AuthenticationEntryPoint that returns 401 makes clients
handle auth failures correctly (show login page on 401).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
XuqmGroup 2026-05-18 13:31:24 +08:00
父节点 39367a57e1
当前提交 3bc8a39d0f
共有 6 个文件被更改,包括 24 次插入0 次删除

查看文件

@ -2,6 +2,7 @@ package com.xuqm.file.config;
import com.xuqm.common.security.JwtAuthFilter; import com.xuqm.common.security.JwtAuthFilter;
import com.xuqm.common.security.JwtUtil; import com.xuqm.common.security.JwtUtil;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
@ -43,6 +44,9 @@ public class SecurityConfig {
// Upload requires authentication // Upload requires authentication
.anyRequest().authenticated() .anyRequest().authenticated()
) )
.exceptionHandling(ex -> ex
.authenticationEntryPoint((req, res, e) -> res.sendError(HttpServletResponse.SC_UNAUTHORIZED))
)
.addFilterBefore(new JwtAuthFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class); .addFilterBefore(new JwtAuthFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class);
return http.build(); return http.build();
} }

查看文件

@ -12,6 +12,7 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHt
import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
@ -42,6 +43,9 @@ public class SecurityConfig {
.requestMatchers("/api/im/auth/**", "/api/im/internal/**", "/ws/**", "/actuator/**").permitAll() .requestMatchers("/api/im/auth/**", "/api/im/internal/**", "/ws/**", "/actuator/**").permitAll()
.anyRequest().authenticated() .anyRequest().authenticated()
) )
.exceptionHandling(ex -> ex
.authenticationEntryPoint((req, res, e) -> res.sendError(HttpServletResponse.SC_UNAUTHORIZED))
)
.addFilterBefore(new JwtAuthFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class); .addFilterBefore(new JwtAuthFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class);
return http.build(); return http.build();
} }

查看文件

@ -9,6 +9,7 @@ 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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.config.http.SessionCreationPolicy;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
@ -39,6 +40,9 @@ public class SecurityConfig {
.requestMatchers("/api/license/internal/**", "/actuator/health", "/actuator/info").permitAll() .requestMatchers("/api/license/internal/**", "/actuator/health", "/actuator/info").permitAll()
.anyRequest().authenticated() .anyRequest().authenticated()
) )
.exceptionHandling(ex -> ex
.authenticationEntryPoint((req, res, e) -> res.sendError(HttpServletResponse.SC_UNAUTHORIZED))
)
.addFilterBefore(new JwtAuthFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class); .addFilterBefore(new JwtAuthFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class);
return http.build(); return http.build();
} }

查看文件

@ -8,6 +8,7 @@ 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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.config.http.SessionCreationPolicy;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@ -30,6 +31,9 @@ public class SecurityConfig {
.requestMatchers("/api/push/internal/**", "/api/push/auth/**", "/actuator/health", "/actuator/info").permitAll() .requestMatchers("/api/push/internal/**", "/api/push/auth/**", "/actuator/health", "/actuator/info").permitAll()
.anyRequest().authenticated() .anyRequest().authenticated()
) )
.exceptionHandling(ex -> ex
.authenticationEntryPoint((req, res, e) -> res.sendError(HttpServletResponse.SC_UNAUTHORIZED))
)
.addFilterBefore(new JwtAuthFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class); .addFilterBefore(new JwtAuthFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class);
return http.build(); return http.build();
} }

查看文件

@ -2,6 +2,7 @@ package com.xuqm.tenant.config;
import com.xuqm.common.security.JwtAuthFilter; import com.xuqm.common.security.JwtAuthFilter;
import com.xuqm.common.security.JwtUtil; import com.xuqm.common.security.JwtUtil;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
@ -41,6 +42,9 @@ public class SecurityConfig {
).permitAll() ).permitAll()
.anyRequest().authenticated() .anyRequest().authenticated()
) )
.exceptionHandling(ex -> ex
.authenticationEntryPoint((req, res, e) -> res.sendError(HttpServletResponse.SC_UNAUTHORIZED))
)
.addFilterBefore(new JwtAuthFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class); .addFilterBefore(new JwtAuthFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class);
return http.build(); return http.build();
} }

查看文件

@ -9,6 +9,7 @@ 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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.config.http.SessionCreationPolicy;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
@ -46,6 +47,9 @@ public class SecurityConfig {
).permitAll() ).permitAll()
.anyRequest().authenticated() .anyRequest().authenticated()
) )
.exceptionHandling(ex -> ex
.authenticationEntryPoint((req, res, e) -> res.sendError(HttpServletResponse.SC_UNAUTHORIZED))
)
.addFilterBefore(new JwtAuthFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class) .addFilterBefore(new JwtAuthFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class)
.httpBasic(AbstractHttpConfigurer::disable) .httpBasic(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable); .formLogin(AbstractHttpConfigurer::disable);