英文:
I get Error 401 when I send a POST request to an excluded endpoint
问题
我正在开发一个Web应用程序,用户可以使用OAuth2进行身份验证。在Web应用程序中,有一组仅通过发送请求(GET-POST-PUT-DELETE)并包括有效令牌才能访问的端点列表。
现在我想通过允许用户使用用户名和密码进行身份验证来实现我的Web应用程序。有一个表单,用户可以在其中注册Web应用程序(他们需要填写所需的数据)。
因此,一旦用户点击注册,将向URL localhost:8080/api/v1/accounts/register 发送POST请求,并将其数据存储到数据库中。
我的问题是,仅当请求中包含有效令牌时,POST请求才起作用。如果不指定有效令牌,我会收到“错误401未经授权”的错误。如果包括令牌发送请求,我会收到201。
我尝试过的是将端点(/api/v1/accounts/register)设置为对所有人都可访问。但我仍然收到错误401。
以下是涉及授权的类:
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
<details>
<summary>英文:</summary>
I'm developing a web app where user can authenticate with OAuth2. In the web app there are a list of endpoints that are accessible only by sending requests (GET-POST-PUT-DELETE) including a valid token.
Now I want to implement my web app by allowing users to authenticate with username and password. There is a form where user can Signup to the web app (they need to fill the form with the required data).
So, once an user clicked on Signup, a POST request is sent to the URL **localhost:8080/api/v1/accounts/register** and their data are stored into the db.
My problem is that the POST request works only if a valid token is included into the request. If I don't specify a valid token, I get an "error 401 unauthorized". If I send the request by including the token, I got 201.
What I tried to do was to make the endpoint (/api/v1/accounts/register) accessible to everyone. But I still get the error 401.
Those are the classes involved into the authorization:
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/");
}
}
</details>
# 答案1
**得分**: 1
你已经在你的基于所提供的代码的Web项目中使用了Spring Security来管理身份验证和权限。你需要帮助,因为你想要将`/api/v1/accounts/register`端点设置为公开访问,但它仍然需要有效的令牌来访问。
你必须修改你的`SecurityConfig`类,以使`/api/v1/accounts/register`端点在没有身份验证的情况下可用。这个端点现在已经包含在`excludeFromCheck`列表中,这意味着不应在它上面执行身份验证检查。但它仍然被视为已经过身份验证的请求。
为了解决这个问题,请尝试以以下方式更新`SecurityConfig`类:
```java
//...
public SecurityFilterChain securityFilterChain(HttpSecurity http, AccountRepository accountRepository) throws Exception {
List<AntPathRequestMatcher> excludeFromCheck = List.of(
//...
new AntPathRequestMatcher("/api/v1/accounts/register")
);
return http
//...
.authorizeHttpRequests(requests -> requests
.requestMatchers(excludeFromCheck.toArray(new AntPathRequestMatcher[0])).permitAll()
.anyRequest().authenticated()
)
//...
}
这将允许/api/v1/accounts/register
端点在不需要身份验证的情况下访问。
英文:
You have used Spring Security to manage authentication and permission in your web project based on the code you gave. You need help because you want to make the /api/v1/accounts/register
endpoint publicly available, but it requires a valid token to access.
You must alter your SecurityConfig
class to make the /api/v1/accounts/register
endpoint available without authentication. This endpoint is now included in the excludeFromCheck
list, which implies that authentication checks should not be performed on it. It is still viewed as an authenticated request though.
To resolve this, try out updating the SecurityConfig
class in this way,
//...
public SecurityFilterChain securityFilterChain(HttpSecurity http, AccountRepository accountRepository) throws Exception {
List<AntPathRequestMatcher> excludeFromCheck = List.of(
//...
new AntPathRequestMatcher("/api/v1/accounts/register")
);
return http
//...
.authorizeHttpRequests(requests -> requests
.requestMatchers(excludeFromCheck.toArray(new AntPathRequestMatcher[0])).permitAll()
.anyRequest().authenticated()
)
//...
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论