You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Security配置注册端点排除认证后仍返回401 Unauthorized问题求助

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.22 07:03:00