【问题标题】:How to apply Spring Security filter only on secured endpoints?如何仅在安全端点上应用 Spring Security 过滤器?
【发布时间】: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 表达式匹配的端点上应用过滤器?

【问题讨论】:

    标签: java spring security spring-mvc spring-security


    【解决方案1】:

    我有一个具有相同要求的应用程序,为了解决它,我基本上将 Spring Security 限制为给定的蚂蚁匹配模式(使用antMatcher),如下所示:

    http
        .antMatcher("/api/**")
        .authorizeRequests() //
            .anyRequest().authenticated() //
            .and()
        .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
    

    您可以阅读如下:对于http,仅在与蚂蚁模式/api/**匹配的请求上调用这些配置@授权any requestauthenticated用户andadd filteradd filterauthenticationTokenFilterBean()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 元素作为解决方案...这对您来说非常有用...我可以为您推荐一些解决方案
      猜你喜欢
      • 2014-01-31
      • 2017-04-26
      • 2015-12-04
      • 2016-08-26
      • 2018-08-05
      • 1970-01-01
      • 2023-03-05
      • 2020-07-17
      相关资源
      最近更新 更多