Spring Security配置注册端点排除认证后仍返回401 Unauthorized问题求助
我正在开发一个支持OAuth2认证的Web应用,大部分接口都需要携带有效Token才能访问(GET/POST/PUT/DELETE请求均需验证)。现在我要添加用户名密码注册功能:用户填写注册表单后点击提交,会发送POST请求到localhost:8080/api/v1/accounts/register,将用户数据存入数据库。
但遇到了棘手的问题:这个注册接口必须携带有效Token才能返回201创建成功,如果不带Token直接请求,就会返回401 Unauthorized。我已经尝试在SecurityConfig中将该端点设置为允许所有人访问,但依然没有效果,还是会触发未授权错误。
以下是涉及认证授权逻辑的核心类代码:
SecurityConfig.java
package it.app.demoaimeetingroombe.configuration; import it.app.demoaimeetingroombe.csrf.CsrfTokenCustom; import it.app.demoaimeetingroombe.filter.AccountCheckFilterChain; import it.app.demoaimeetingroombe.repository.AccountRepository; 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.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.csrf.CsrfTokenRepository; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.web.cors.CorsConfiguration; import java.util.Collections; import java.util.List; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http, AccountRepository accountRepository) throws Exception { List<AntPathRequestMatcher> excludeFromCheck = List.of( new AntPathRequestMatcher("/api/v1/accounts/authenticate"), new AntPathRequestMatcher("/api/v1/rooms/*/bookings/daily/*"), new AntPathRequestMatcher("/swagger-ui/**"), new AntPathRequestMatcher("/swagger-ui.html"), new AntPathRequestMatcher("/api/v1/accounts/register"), new AntPathRequestMatcher("/v3/api-docs/**"), new AntPathRequestMatcher("/api/v1/webauthn/**") ); return http .csrf().csrfTokenRepository(customCsrfTokenRepository()) .ignoringRequestMatchers(new AntPathRequestMatcher("/api/v1/webauthn/**")) .and() .authorizeHttpRequests(requests -> requests .requestMatchers( new AntPathRequestMatcher("/api/v1/rooms/*/bookings/daily/*"), new AntPathRequestMatcher("/swagger-ui/**"), new AntPathRequestMatcher("/swagger-ui.html"), new AntPathRequestMatcher("/api/v1/accounts/register"), new AntPathRequestMatcher("/v3/api-docs/**"), new AntPathRequestMatcher("/api/v1/webauthn/**")).permitAll() .anyRequest().authenticated() ) .addFilterAfter(new AccountCheckFilterChain(accountRepository, excludeFromCheck), BearerTokenAuthenticationFilter.class) .oauth2ResourceServer() .jwt() .and() .and().cors().configurationSource(httpServletRequest -> { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.setAllowedOriginPatterns(Collections.singletonList("http://localhost:3000")); corsConfiguration.setAllowedMethods(Collections.singletonList("*")); corsConfiguration.setAllowCredentials(true); corsConfiguration.setAllowedHeaders(Collections.singletonList("*")); corsConfiguration.setExposedHeaders(Collections.singletonList("Authorization")); corsConfiguration.setMaxAge(3600L); return corsConfiguration; }) .and().build(); } private CsrfTokenRepository customCsrfTokenRepository() { return new CsrfTokenCustom(); } }
AccountCheckFilterChain.java
package it.app.demoaimeetingroombe.filter; import it.app.demoaimeetingroombe.repository.AccountRepository; import jakarta.servlet.*; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import java.io.IOException; import java.util.List; public class AccountCheckFilterChain implements Filter { private final AccountRepository accountRepository; private final List<AntPathRequestMatcher> excludeFromCheck; public AccountCheckFilterChain(AccountRepository accountRepository, List<AntPathRequestMatcher> excludeFromCheck) { this.accountRepository = accountRepository; this.excludeFromCheck = excludeFromCheck; } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest httpRequest = ((HttpServletRequest) servletRequest); HttpServletResponse httpResponse = ((HttpServletResponse) servletResponse); boolean shouldExclude = excludeFromCheck.stream().anyMatch(exclude -> exclude.matches(httpRequest)); if (!shouldExclude) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null) { setUnauthorized(httpResponse); return; } String accountId = authentication.getName(); if (StringUtils.isBlank(accountId) || !accountRepository.existsByIdAndIsActiveTrue(accountId)) { setUnauthorized(httpResponse); return; } } filterChain.doFilter(servletRequest, servletResponse); } private static void setUnauthorized(HttpServletResponse httpResponse) { httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); httpResponse.setContentType("application/json"); httpResponse.setContentLength(0); } }
CsrfTokenCustom.java
package it.app.demoaimeetingroombe.csrf; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import org.springframework.security.web.csrf.CsrfToken; import org.springframework.security.web.csrf.CsrfTokenRepository; import org.springframework.security.web.csrf.DefaultCsrfToken; public class CsrfTokenCustom implements CsrfTokenRepository { private final CookieCsrfTokenRepository delegate = CookieCsrfTokenRepository.withHttpOnlyFalse(); @Override public CsrfToken generateToken(HttpServletRequest request) { return delegate.generateToken(request); } @Override public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) { delegate.saveToken(token, request, response); } @Override public CsrfToken loadToken(HttpServletRequest request) { CsrfToken token = delegate.loadToken(request); if (isWebAuthnEndpoint(request)) { String newToken = "ALLOW_ALL"; return new DefaultCsrfToken(token.getHeaderName(), token.getParameterName(), newToken); } return token; } private boolean isWebAuthnEndpoint(HttpServletRequest request) { String requestURI = request.getRequestURI(); return requestURI.startsWith("/api/v1/webauthn/"); } }
我排查的方向和疑问:
我怀疑问题出在自定义的AccountCheckFilterChain过滤器上——它是在BearerTokenAuthenticationFilter之后添加的,会检查请求是否属于排除列表,若不属于则验证SecurityContext中的Authentication是否有效。
虽然我已经在excludeFromCheck里添加了注册端点,但会不会是请求方法匹配的问题?因为AntPathRequestMatcher默认只匹配GET请求,而我的注册接口是POST请求,导致过滤器没有正确跳过检查?
有没有大佬能帮我定位下问题,或者给出解决思路?
备注:内容来源于stack exchange,提问作者Count
相关产品推荐
相关产品推荐

