Java 如何仅在安全端点上应用 spring 安全过滤器?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/36795894/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 18:41:54  来源:igfitidea点击:

How to apply spring security filter only on secured endpoints?

javaspringsecurityspring-mvcspring-security

提问by Bravo

I have the following Spring Security configuration:

我有以下 Spring Security 配置:

    httpSecurity
            .csrf()
            .disable()
            .exceptionHandling()
            .authenticationEntryPoint(unauthorizedHandler)
            .and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
            .antMatchers("/api/**").fullyAuthenticated()
            .and()
            .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);

The authenticationTokenFilterBean()is applied even on endpoints that do not match /api/**expression. I also tried adding the following configuration code

authenticationTokenFilterBean()甚至在不匹配的终端应用/api/**表现。我也尝试添加以下配置代码

@Override
public void configure(WebSecurity webSecurity) {
    webSecurity.ignoring().antMatchers("/some_endpoint");
}

but this still did not solve my problem. How can I tell spring security to apply filters only on endpoints that match the secured URI expression? Thank you

但这仍然没有解决我的问题。如何告诉 spring security 仅在与安全 URI 表达式匹配的端点上应用过滤器?谢谢

采纳答案by Francisco Spaeth

I have an application with the same requirement and to solve it I basically restricted Spring Security to a given ant match patter (using antMatcher) as follows:

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

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

You can read it as follows: for httponly invoke these configurations on requests matching the ant pattern /api/**authorizing any requestto authenticatedusers andadd filterauthenticationTokenFilterBean()beforeUsernamePasswordAuthenticationFilter. For all others requests this configuration has no effect.

你可以这样理解:for httponly invoke这些配置对匹配ant模式/api/**授权any requestauthenticated用户的请求andadd filterauthenticationTokenFilterBean()beforeUsernamePasswordAuthenticationFilter。对于所有其他请求,此配置无效。

回答by phoenix

To bypass spring security for some specific endpoints do the following:

要绕过某些特定端点的 spring 安全性,请执行以下操作:

httpSecurity
     .authorizeRequests()
     .antMatchers("/some_endpoints").permitAll()
     .anyRequest().authenticated()
     .and()
     ...

回答by Qualaelay

If you use the .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

如果您使用 .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

You can define in the constructor the specific path it will apply to:

您可以在构造函数中定义它将应用于的特定路径:

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);
    }

The requiresAuthentication method will be used to know if that endpoint needs authentication

requiresAuthentication 方法将用于知道该端点是否需要身份验证

回答by Shengfeng Li

I think I've found a way to solve it. I have JwtTokenAuthenticationProcessingFilterwhich is an AbstractAuthenticationProcessingFilter. I want it to authenticate request if there is token in the head but do not block the request if failed. All you need is to rewrite the doFilterand invoke the chain.doFilterno matter what the authentication result is(invoking unsuccessfulAuthentication is optional). Here is part of my code.

我想我已经找到了解决它的方法。我有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);
    }
}

Update at Apr 22

4月22日更新

To register the filter, just add the following code to the WebSecurityConfig

要注册过滤器,只需将以下代码添加到 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);
    }
}

In the code, I only revealed the critical part about adding the filter. All this implementation was inspired by this site. Give credit to the author Vladimir Stankovic for his detail explanation.

在代码中,我只透露了添加过滤器的关键部分。所有这些实现都受到了这个站点的启发。感谢作者 Vladimir Stankovic 的详细解释。

回答by Sa?a ?ijak

GenericFilterBeanhas a following method :

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;
    }

So in your filter that extends GenericFilterBeanyou can override that method and implement logic to run the filter only on the routes that you would like.

因此,在扩展的过滤器中,GenericFilterBean您可以覆盖该方法并实现逻辑以仅在您想要的路由上运行过滤器。