如何仅在安全端点上应用 Spring Security 过滤器?
Posted
技术标签:
【中文标题】如何仅在安全端点上应用 Spring Security 过滤器?【英文标题】:How to apply Spring Security filter only on secured endpoints? 【发布时间】:2022-01-18 23:14:55 【问题描述】:我有以下 Spring Security 配置:
httpSecurity
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/**").fullyAuthenticated()
.and()
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
authenticationTokenFilterBean()
甚至应用于与/api/**
表达式不匹配的端点。我还尝试添加以下配置代码:
@Override
public void configure(WebSecurity webSecurity)
webSecurity.ignoring().antMatchers("/some_endpoint");
但这仍然没有解决我的问题。如何告诉 Spring Security 仅在与受保护的 URI 表达式匹配的端点上应用过滤器?
【问题讨论】:
【参考方案1】:我有一个具有相同要求的应用程序,为了解决它,我基本上将 Spring Security 限制为给定的蚂蚁匹配模式(使用antMatcher
),如下所示:
http
.antMatcher("/api/**")
.authorizeRequests() //
.anyRequest().authenticated() //
.and()
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
您可以阅读如下:对于http
,仅在与蚂蚁模式/api/**
匹配的请求上调用这些配置@授权any request
到authenticated
用户and
add filter
add filter
authenticationTokenFilterBean()
before
@987654331 @。对于所有其他请求,此配置无效。
【讨论】:
如果我想允许 /api/login 即完全绕过 /api/login 怎么办。即使我做了一个 permitAll(),过滤器仍然被调用。请提出建议。 这不是真的...authenticationTokenFilterBean
将运行在它只适用于匿名的每个请求上。
它对我不起作用,过滤器被调用以获取 /api 以外的请求
对我也不起作用,过滤器是针对例如请求执行的。 /内部
对于 cmets 的更多读者:答案是正确。所有说它不起作用的人都做错了(例如,他们将authenticationTokenFilterBean()
方法定义为@Bean
,在这种情况下,即使没有此安全配置,spring-boot 也会自动扫描它并将其添加为通用过滤器,这如果您只想将此过滤器添加到安全过滤器链中,显然是错误的)。【参考方案2】:
GenericFilterBean
有以下方法:
/**
* Can be overridden in subclasses for custom filtering control,
* returning @code true to avoid filtering of the given request.
* <p>The default implementation always returns @code false.
* @param request current HTTP request
* @return whether the given request should <i>not</i> be filtered
* @throws ServletException in case of errors
*/
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException
return false;
因此,在扩展 GenericFilterBean
的过滤器中,您可以覆盖该方法并实现逻辑以仅在您想要的路由上运行过滤器。
【讨论】:
我似乎在 javadoc 中找不到这个。你确定这存在吗?编辑:我发现它已移至OncePerRequestFilter
,但感谢您指出正确的方向【参考方案3】:
我的要求是排除匹配 /api/auth/** 的端点,为了达到同样的效果,我已经配置了我的 WebSecurityConfig spring 配置组件,如下所示:
/**
* The purpose of this method is to exclude the URL's specific to Login, Swagger UI and static files.
* Any URL that should be excluded from the Spring security chain should be added to the ignore list in this
* method only
*/
@Override
public void configure(WebSecurity web) throws Exception
web.ignoring().antMatchers("/api/auth/**","/v2/api-docs",
"/configuration/ui",
"/swagger-resources",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**",
"/favicon.ico",
"/**/*.png",
"/**/*.gif",
"/**/*.svg",
"/**/*.jpg",
"/**/*.html",
"/**/*.css",
"/**/*.js");
/**
* The purpose of this method is to define the HTTP configuration that defines how an HTTP request is
* going to be treated by the Spring Security chain. All the request URL's (excluding the URL's added
* in WebSecurity configuration ignore list) matching this configuration have to pass through the
* custom Spring security filter defined in this method
*/
@Override
protected void configure(HttpSecurity http) throws Exception
http.csrf().disable()
.cors().disable()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
/**
* The purpose of this method is to create a new instance of JWTAuthenticationFilter
* and return the same from the method body. It must be ensured that this filter should
* not be configured as a Spring bean or registered into the Spring Application context
* failing which the below filter shall be registered as a default web filter, and thus
* all the URL's even the excluded ones shall be intercepted by the below filter
*/
public JWTAuthenticationFilter authenticationTokenFilterBean()
return new JWTAuthenticationFilter();
【讨论】:
非常感谢,这解决了我的问题!我无法使用其他地方提到的/api/**
方法,所以这适合我的用例。你能解释一下为什么会这样吗? WebSecurity
在链中首先被调用吗?我只是想知道为什么它适用于WebSecurity
上的.ignore
端点HttpSecurity
荣誉。【参考方案4】:
如果你使用
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
您可以在构造函数中定义它将应用到的特定路径:
public class JwtAuthenticationFilter extends AbstractAuthenticationProcessingFilter
public JwtAuthenticationFilter(AuthenticationManager authenticationManager)
super("/api/**");
this.setAuthenticationManager(authenticationManager);
@Override
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response)
return super.requiresAuthentication(request, response);
requiresAuthentication
方法将用于了解该端点是否需要身份验证。
【讨论】:
【参考方案5】:我想我已经找到了解决它的方法。我有JwtTokenAuthenticationProcessingFilter
,这是一个AbstractAuthenticationProcessingFilter
。如果头部有令牌,我希望它对请求进行身份验证,但如果失败则不阻止请求。您只需重写 doFilter 并调用 chain.doFilter
,无论身份验证结果如何(调用 unsuccessfulAuthentication 是可选的)。这是我的部分代码。
public class JwtTokenAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter
private final TokenExtractor tokenExtractor;
@Autowired
public JwtTokenAuthenticationProcessingFilter(TokenExtractor tokenExtractor, RequestMatcher matcher)
super(matcher);
this.tokenExtractor = tokenExtractor;
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
ServletException
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (!this.requiresAuthentication(request, response))
chain.doFilter(request, response);
else
if (this.logger.isDebugEnabled())
this.logger.debug("Request is to process authentication");
boolean success = true;
Authentication authResult = null;
try
authResult = this.attemptAuthentication(request, response);
catch (InternalAuthenticationServiceException var8)
this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
success = false;
catch (AuthenticationException var9)
success = false;
if (success && null != authResult)
this.successfulAuthentication(request, response, chain, authResult);
// Please ensure that chain.doFilter(request, response) is invoked upon successful authentication. You want
// processing of the request to advance to the next filter, because very last one filter
// FilterSecurityInterceptor#doFilter is responsible to actually invoke method in your controller that is
// handling requested API resource.
chain.doFilter(request, response);
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException
String tokenPayload = request.getHeader(WebSecurityConfig.AUTHENTICATION_HEADER_NAME);
RawAccessJwtToken token = new RawAccessJwtToken(tokenExtractor.extract(tokenPayload));
return getAuthenticationManager().authenticate(new JwtAuthenticationToken(token));
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authResult);
SecurityContextHolder.setContext(context);
4 月 22 日更新
要注册过滤器,只需将以下代码添加到 WebSecurityConfig
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
private final JwtAuthenticationProvider mJwtAuthenticationProvider;
@Autowired
public WebSecurityConfig(JwtAuthenticationProvider jwtAuthenticationProvider)
this.mJwtAuthenticationProvider = jwtAuthenticationProvider;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
// When multiple authentication providers are defined, the providers will be queried in the order they’re
// declared.
auth.authenticationProvider(mJwtAuthenticationProvider);
在代码中,我只透露了添加过滤器的关键部分。 所有这些实现都受到this site 的启发。感谢作者 Vladimir Stankovic 的详细解释。
【讨论】:
@NeelamKapoor 嗨,那里。您可以根据需要使用过滤器,也可以使用新过滤器,然后将其注册到适配器。这取决于您如何实现代码。【参考方案6】:要绕过某些特定端点的 spring 安全性,请执行以下操作:
httpSecurity
.authorizeRequests()
.antMatchers("/some_endpoints").permitAll()
.anyRequest().authenticated()
.and()
...
【讨论】:
有没有办法指定适用于特定过滤器而不是端点的路径? 你的意思是像 /some_endpoint/** 这样会包括 /some_endpoint/path1 等的东西吗?...然后是的...让 antMatcher 接受/api/**
感谢您的回答,@phoenix。不幸的是,这并不能解决我的问题。过滤器仍然适用于“/some_endpoints”网址
仍在应用哪个过滤器?
如果您想要两个不同的 httpSecurity 元素作为解决方案...这对您来说非常有用...我可以为您推荐一些解决方案以上是关于如何仅在安全端点上应用 Spring Security 过滤器?的主要内容,如果未能解决你的问题,请参考以下文章
Cloudfront 无法访问部署在 EBS 上的 Spring Boot 上的安全端点
使用 Cognito IAM 角色的端点上的 Spring 安全性?
启用具有 JWT 安全性的 Spring Boot 2.0 执行器端点
当Host头存在时如何确定请求的真实serverPort [重复]