【问题标题】:Zuul Routing not redirect to given URL after security filter get applied应用安全过滤器后,Zuul 路由不会重定向到给定的 URL
【发布时间】:2019-06-26 07:16:04
【问题描述】:

我有一个使用 spring security 和 spring zuul 路由和过滤器的 spring boot 应用程序。我有 2 个请求,一个是 /auth,它被忽略为安全性。另一个是 /api,它使用 JWT 的自定义过滤器验证。 当前的问题是第二个请求 /api 没有路由到属性文件中提到的相应服务。

这是我的安全配置类

@EnableWebSecurity
@Configuration
public class WebSecurityConfigurtion extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomAuthenticationEntryPoinit customAuthEntrypoint;

    @Autowired
    private JwtConfig jwtConfig;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

         http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
        .csrf().disable()
        .exceptionHandling()
        .authenticationEntryPoint(customAuthEntrypoint)
        .and()
        .authorizeRequests()
            .antMatchers("/api/**").permitAll()
            .antMatchers("/auth/**").permitAll()
        .and()
        .antMatcher("/api/**") // if we mention the antmatcher first , it will apply only if the url starts with /api
        .authorizeRequests()
            .anyRequest()
            .authenticated()
        .and()
        .addFilterBefore(jwtTokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }

    @Bean
    public JwtTokenAuthenticationFilter jwtTokenAuthenticationFilter() {
        return new JwtTokenAuthenticationFilter(jwtConfig);
    }

    @Bean
    public FilterRegistrationBean registration(JwtTokenAuthenticationFilter filter) {
        FilterRegistrationBean registration = new FilterRegistrationBean(filter);
        registration.setEnabled(false);
        return registration;
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/auth/**");//ignore all /auth request , so no security will be applied , so it wont check for matching filters for  /auth
    }

路由属性

zuul.routes.auth-service.path=/auth/**
zuul.routes.auth-service.url=http://localhost:9100/
zuul.routes.auth-service.strip-prefix=false
zuul.routes.userservice.path=/api/userservice/**
zuul.routes.userservice.url=http://localhost:9200/
zuul.routes.userservice.strip-prefix=false

请查看自定义 JWTAuthFilter

    package com.cavion.config;

import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;


public class JwtTokenAuthenticationFilter extends OncePerRequestFilter {
    private final JwtConfig jwtConfig;

    public JwtTokenAuthenticationFilter(JwtConfig jwtConfig) {
        this.jwtConfig = jwtConfig;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {

        // 1. get the authentication header. Tokens are supposed to be passed in the
        // authentication header
        System.out.println("am in JwtTokenAuthenticationFilter");
        String header = request.getHeader(jwtConfig.getHeader());

        // 2. validate the header and check the prefix
        if (header == null || !header.startsWith(jwtConfig.getPrefix())) {
            chain.doFilter(request, response); // If not valid, go to the next filter.
            return;
        }

        // If there is no token provided and hence the user won't be authenticated.
        // It's Ok. Maybe the user accessing a public path or asking for a token.

        // All secured paths that needs a token are already defined and secured in
        // config class.
        // And If user tried to access without access token, then he won't be
        // authenticated and an exception will be thrown.

        // 3. Get the token
        String token = header.replace(jwtConfig.getPrefix(), "");

        try { // exceptions might be thrown in creating the claims if for example the token is
                // expired

            // 4. Validate the token
            Claims claims = Jwts.parser().setSigningKey(jwtConfig.getSecret().getBytes()).parseClaimsJws(token)
                    .getBody();

            String username = claims.getSubject();
            if (username != null) {
                @SuppressWarnings("unchecked")
                List<String> authorities = (List<String>) claims.get("authorities");

                // 5. Create auth object
                // UsernamePasswordAuthenticationToken: A built-in object, used by spring to
                // represent the current authenticated / being authenticated user.
                // It needs a list of authorities, which has type of GrantedAuthority interface,
                // where SimpleGrantedAuthority is an implementation of that interface
                UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(username, null,
                        authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()));

                // 6. Authenticate the user
                // Now, user is authenticated
                SecurityContextHolder.getContext().setAuthentication(auth);
            }

        } catch (Exception e) {
            // In case of failure. Make sure it's clear; so guarantee user won't be
            // authenticated
            SecurityContextHolder.clearContext();
        }

        // go to the next filter in the filter chain
        chain.doFilter(request, response);
    }
}

我也尝试过传递正确的不记名令牌,但没有任何变化。

请查看日志

    2019-02-01 22:28:12.356 DEBUG 8456 --- [nio-8084-exec-3] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/api/userservice/hostdata/gethostnames'; against '/auth/**'
2019-02-01 22:28:12.356 DEBUG 8456 --- [nio-8084-exec-3] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/api/userservice/hostdata/gethostnames'; against '/api/**'
2019-02-01 22:28:12.356 DEBUG 8456 --- [nio-8084-exec-3] o.s.security.web.FilterChainProxy        : /api/userservice/hostdata/gethostnames at position 1 of 11 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2019-02-01 22:28:12.356 DEBUG 8456 --- [nio-8084-exec-3] o.s.security.web.FilterChainProxy        : /api/userservice/hostdata/gethostnames at position 2 of 11 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2019-02-01 22:28:12.356 DEBUG 8456 --- [nio-8084-exec-3] o.s.security.web.FilterChainProxy        : /api/userservice/hostdata/gethostnames at position 3 of 11 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2019-02-01 22:28:12.356 DEBUG 8456 --- [nio-8084-exec-3] o.s.security.web.FilterChainProxy        : /api/userservice/hostdata/gethostnames at position 4 of 11 in additional filter chain; firing Filter: 'LogoutFilter'
2019-02-01 22:28:12.356 DEBUG 8456 --- [nio-8084-exec-3] o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/logout', GET]
2019-02-01 22:28:12.356 DEBUG 8456 --- [nio-8084-exec-3] o.s.s.w.u.matcher.AntPathRequestMatcher  : Request 'POST /api/userservice/hostdata/gethostnames' doesn't match 'GET /logout
2019-02-01 22:28:12.356 DEBUG 8456 --- [nio-8084-exec-3] o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/logout', POST]
2019-02-01 22:28:12.356 DEBUG 8456 --- [nio-8084-exec-3] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/api/userservice/hostdata/gethostnames'; against '/logout'
2019-02-01 22:28:12.356 DEBUG 8456 --- [nio-8084-exec-3] o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/logout', PUT]
2019-02-01 22:28:12.356 DEBUG 8456 --- [nio-8084-exec-3] o.s.s.w.u.matcher.AntPathRequestMatcher  : Request 'POST /api/userservice/hostdata/gethostnames' doesn't match 'PUT /logout
2019-02-01 22:28:12.356 DEBUG 8456 --- [nio-8084-exec-3] o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/logout', DELETE]
2019-02-01 22:28:12.356 DEBUG 8456 --- [nio-8084-exec-3] o.s.s.w.u.matcher.AntPathRequestMatcher  : Request 'POST /api/userservice/hostdata/gethostnames' doesn't match 'DELETE /logout
2019-02-01 22:28:12.356 DEBUG 8456 --- [nio-8084-exec-3] o.s.s.web.util.matcher.OrRequestMatcher  : No matches found
2019-02-01 22:28:12.356 DEBUG 8456 --- [nio-8084-exec-3] o.s.security.web.FilterChainProxy        : /api/userservice/hostdata/gethostnames at position 5 of 11 in additional filter chain; firing Filter: 'JwtTokenAuthenticationFilter'
am in JwtTokenAuthenticationFilter
2019-02-01 22:28:12.467 DEBUG 8456 --- [nio-8084-exec-3] o.s.security.web.FilterChainProxy        : /api/userservice/hostdata/gethostnames at position 6 of 11 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
2019-02-01 22:28:12.467 DEBUG 8456 --- [nio-8084-exec-3] o.s.security.web.FilterChainProxy        : /api/userservice/hostdata/gethostnames at position 7 of 11 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
2019-02-01 22:28:12.467 DEBUG 8456 --- [nio-8084-exec-3] o.s.security.web.FilterChainProxy        : /api/userservice/hostdata/gethostnames at position 8 of 11 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
2019-02-01 22:28:12.467 DEBUG 8456 --- [nio-8084-exec-3] o.s.s.w.a.AnonymousAuthenticationFilter  : SecurityContextHolder not populated with anonymous token, as it already contained: 'org.springframework.security.authentication.UsernamePasswordAuthenticationToken@7da74079: Principal: demo; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_user, admin'
2019-02-01 22:28:12.467 DEBUG 8456 --- [nio-8084-exec-3] o.s.security.web.FilterChainProxy        : /api/userservice/hostdata/gethostnames at position 9 of 11 in additional filter chain; firing Filter: 'SessionManagementFilter'
2019-02-01 22:28:12.467 DEBUG 8456 --- [nio-8084-exec-3] s.CompositeSessionAuthenticationStrategy : Delegating to org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy@63d25458
2019-02-01 22:28:12.470 DEBUG 8456 --- [nio-8084-exec-3] o.s.security.web.FilterChainProxy        : /api/userservice/hostdata/gethostnames at position 10 of 11 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
2019-02-01 22:28:12.470 DEBUG 8456 --- [nio-8084-exec-3] o.s.security.web.FilterChainProxy        : /api/userservice/hostdata/gethostnames at position 11 of 11 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
2019-02-01 22:28:12.470 DEBUG 8456 --- [nio-8084-exec-3] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/api/userservice/hostdata/gethostnames'; against '/api/**'
2019-02-01 22:28:12.470 DEBUG 8456 --- [nio-8084-exec-3] o.s.s.w.a.i.FilterSecurityInterceptor    : Secure object: FilterInvocation: URL: /api/userservice/hostdata/gethostnames; Attributes: [permitAll]
2019-02-01 22:28:12.470 DEBUG 8456 --- [nio-8084-exec-3] o.s.s.w.a.i.FilterSecurityInterceptor    : Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@7da74079: Principal: demo; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_user, admin
2019-02-01 22:28:12.470 DEBUG 8456 --- [nio-8084-exec-3] o.s.s.access.vote.AffirmativeBased       : Voter: org.springframework.security.web.access.expression.WebExpressionVoter@4d26c5a9, returned: 1
2019-02-01 22:28:12.470 DEBUG 8456 --- [nio-8084-exec-3] o.s.s.w.a.i.FilterSecurityInterceptor    : Authorization successful
2019-02-01 22:28:12.470 DEBUG 8456 --- [nio-8084-exec-3] o.s.s.w.a.i.FilterSecurityInterceptor    : RunAsManager did not change Authentication object
2019-02-01 22:28:12.470 DEBUG 8456 --- [nio-8084-exec-3] o.s.security.web.FilterChainProxy        : /api/userservice/hostdata/gethostnames reached end of additional filter chain; proceeding with original chain
2019-02-01 22:28:12.472  INFO 8456 --- [nio-8084-exec-3] com.cavion.filter.LoggerFilter           : POST request to http://localhost:8084/api/userservice/hostdata/gethostnames
2019-02-01 22:28:12.492 DEBUG 8456 --- [nio-8084-exec-3] **o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@3c41ee75**
2019-02-01 22:28:12.495 DEBUG 8456 --- [nio-8084-exec-3] o.s.s.w.a.ExceptionTranslationFilter     : Chain processed normally
2019-02-01 22:28:12.495 DEBUG 8456 --- [nio-8084-exec-3] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed

有人可以调查一下并帮我解决路由问题吗?

【问题讨论】:

    标签: spring spring-boot spring-security


    【解决方案1】:

    看来/api/** 是允许所有,这不是你想要的。毕竟,您需要 JWT 的令牌身份验证

    .antMatchers("/api/**").permitAll()

           .and()
            .authorizeRequests()
                .antMatchers("/api/**").permitAll()
                .antMatchers("/auth/**").permitAll()
            .and()
            .antMatcher("/api/**") // if we mention the antmatcher first , it will apply only if the url starts with /api
            .authorizeRequests()
                .anyRequest()
                .authenticated()
    

    Spring Security 分两个阶段工作

    1. 身份验证 - 证明你就是你所说的那个人!
    2. 授权 - 您可以做您将要做的事情吗?也称为访问控制

    让我们看一个示例安全过滤器(伪代码删除了任何多余的方法)

        http
            .authorizeRequests()
            .antMatchers("/api/**").authenticated()
            .antMatchers("/auth/**").permitAll()
            .anyRequest().denyAll()
            .httpBasic()
            .formLogin()
            .oauth2ResourceServer().jwt()
    

    此过滤器将接受三种不同的身份验证方法中的任何一种

    1. http-basic - https://www.rfc-editor.org/rfc/rfc7617
    2. OAuth2 令牌(使用 JWT)-https://www.rfc-editor.org/rfc/rfc6749
    3. 表单登录(带有正文的 HTTP POST)

    这些配置每个都插入一个过滤器。如果凭据是请求的一部分,该过滤器将自动检测,并且仅在存在凭据时执行身份验证。

    最重要的是要了解如果没有凭据作为传入请求的一部分,过滤器将不执行任何操作(无访问控制)并且过滤器链继续。

    一旦 Spring Security 运行完所有 身份验证 过滤器,就该进行授权或访问控制了。此请求是否有权调用此 URL?

    1. 端点需要身份验证

    .antMatchers("/api/**").authenticated()

    这告诉 Spring Security,只要请求经过身份验证,您就可以调用以 /api 开头的任何内容。 它不关心身份验证来自哪里。它可以是基本的,可以是表单登录,也可以是不记名令牌。 最后这句话很重要,要理解。如果您的过滤器链中有多种身份验证方法,则其中任何一种都可以使用。

    1. 端点不需要身份验证

    .antMatchers("/api/**").permitAll()

    如果端点是open,那么我们使用permitAll。这里我们告诉 Spring Security,这些端点可以通过或不通过身份验证来访问。

    如果我将两个匹配器放在同一路径上,就像问题中一样

    .antMatchers("/api/**").permitAll()
    .antMatchers("/api/**").authenticated()
    

    Spring Security 将按顺序运行它们,并使用第一个命中。 在这种情况下,permitAll 返回 true,授予访问权限。其他授权配置无需检查。

    那应该怎么做呢? 我的理解是您希望/api/** 受到保护。而且您并不关心用户拥有什么组、角色或范围。只要用户通过身份验证,就可以访问/api/** 端点。在这种情况下,您希望拥有

    .antMatchers("/api/**").authenticated()
    

    你不想使用permitAll

    【讨论】:

    • 那么您建议的解决方法是什么?
    • 应该全部允许还是经过身份验证?你定义了两者,第一个获胜
    • 如果我的理解是正确的, permitAll() 将只跳过授权检查,所有其他过滤器都将被应用
    • 嗨,安萨尔,让我更新答案。我想我明白混乱来自哪里。
    • @Flip :感谢您的详细解释,现在对 permitAall() 有了清晰的认识,这是为了纠正我。
    猜你喜欢
    • 2020-07-14
    • 2020-04-29
    • 2017-09-03
    • 2016-05-27
    • 2021-09-09
    • 1970-01-01
    • 2018-10-10
    • 1970-01-01
    • 2018-10-31
    相关资源
    最近更新 更多