Spring Boot 如何在 Zuul 微服务上验证令牌

Posted

技术标签:

【中文标题】Spring Boot 如何在 Zuul 微服务上验证令牌【英文标题】:Springboot how to validate token on Zuul microservice 【发布时间】:2020-08-15 02:25:55 【问题描述】:

我是 Springboot 的新手,我试图通过 Zuul API 网关过滤请求,但是我收到以下错误:

AnonymousAuthenticationToken 无法转换为 org.aacctt.ms.auth.security.JWTAuthentication

当我设置断点时,当请求从 zuul 网关到达身份验证服务时,我会得到一个空标头/令牌字符串值,这发生在需要授权令牌的受保护请求上。

我的目标是能够验证客户端发送的令牌,以便我可以允许客户端对受保护端点的请求或拒绝它。

我不确定我做错了什么是我的代码:

身份验证服务


@Component
public class JWTAuthorizationFilter extends GenericFilterBean 

    private static final Logger LOG = LoggerFactory.getLogger(JWTAuthorizationFilter.class);

    private static final String HEADER_STRING = "Authorization";
    public static final String TOKEN_PREFIX = "Bearer ";

    @Value("$jwt.encryption.secret")
    private String SECRET;

    @Value("$jwt.access.token.expiration.seconds")
    private long EXPIRATION_TIME_IN_SECONDS;


    public String generateAccessToken(long userId) 
        return JWT.create()
                .withSubject(String.valueOf(userId))
                .withIssuedAt(new Date())
                .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME_IN_SECONDS * 1000))
                .sign(Algorithm.HMAC256(SECRET.getBytes()));
    



    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException 

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        String header = httpRequest.getHeader(HEADER_STRING);  // this is null

        if (header == null || !header.startsWith(TOKEN_PREFIX)) 
            chain.doFilter(httpRequest, httpResponse);
            return;
        

        SecurityContextHolder.getContext().setAuthentication(getAuthentication(header));
        chain.doFilter(httpRequest, httpResponse);
    

    private Authentication getAuthentication(String token) 

        final String username;
        try 
            DecodedJWT jwt = JWT.require(Algorithm.HMAC256(SECRET.getBytes()))
                    .build()
                    .verify(token.replace(TOKEN_PREFIX, ""));
            username = jwt.getSubject();
         catch (JWTVerificationException e) 
            LOG.debug("Invalid JWT", e);
            return null;
        

        final Long userId;
        try 
            userId = Long.valueOf(username);
         catch (NumberFormatException e) 
            LOG.debug("Invalid JWT. Username is not an user ID");
            return null;
        

        LOG.debug("Valid JWT. User ID: " + userId);

        return new JWTAuthentication(userId);
    



WebSecurityConfig


@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter 

    private final JWTAuthorizationFilter jwtAuthorizationFilter;

    public WebSecurityConfig(JWTAuthorizationFilter jwtAuthorizationFilter) 
        this.jwtAuthorizationFilter = jwtAuthorizationFilter;
    

    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() 
        return (request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
    

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http.cors().disable();
        http.csrf().disable();
        http.addFilterAfter(jwtAuthorizationFilter, BasicAuthenticationFilter.class);
        http.authorizeRequests()
                .antMatchers("/**").permitAll()
                .antMatchers(AccountController.PATH_POST_SIGN_UP).permitAll()
                .antMatchers(AccountController.PATH_POST_REFRESH).permitAll()
                .antMatchers(AccountController.PATH_POST_LOGIN).permitAll()
                .antMatchers("/v2/api-docs",
                        "/swagger-resources/configuration/ui",
                        "/swagger-resources",
                        "/swagger-resources/configuration/security",
                        "/swagger-ui.html",
                        "/webjars/**").permitAll()
                .anyRequest().authenticated()
        ;
         http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    

    @Bean
    public PasswordEncoder passwordEncoder() 
        return new BCryptPasswordEncoder();
    


JWTAuthentication

public class JWTAuthentication implements Authentication 

    private final long userId;

    public JWTAuthentication(long userId) 
        this.userId = userId;
    

    @Override public Collection<? extends GrantedAuthority> getAuthorities() 
        return Collections.emptySet();
    

    @Override public Object getCredentials() 
        return null;
    

    @Override public Object getDetails() 
        return null;
    

    public long getUserId() 
        return userId;
    

    @Override public Long getPrincipal() 
        return userId;
    

    @Override public boolean isAuthenticated() 
        return true;
    

    @Override public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException 
        throw new UnsupportedOperationException("JWT authentication is always authenticated");
    

    @Override public String getName() 
        return String.valueOf(userId);
    

安全服务

@Service
public class SecurityService 

    public long getLoggedUserId() 
        JWTAuthentication authentication = (JWTAuthentication) SecurityContextHolder.getContext().getAuthentication();
        return authentication.getUserId();
    




Zuul 网关


public class AuthorizationFilter extends BasicAuthenticationFilter 

    private static final Logger LOG = LoggerFactory.getLogger(AuthorizationFilter.class);
    private static final String HEADER_STRING = "Authorization";
    public static final String TOKEN_PREFIX = "Bearer ";

    Environment environment;

    public AuthorizationFilter(AuthenticationManager authManager, Environment environment) 
        super(authManager);
        this.environment = environment;
    


    @Override
    protected void doFilterInternal(HttpServletRequest req,
            HttpServletResponse res,
            FilterChain chain) throws IOException, ServletException 

        String authorizationHeader = req.getHeader(environment.getProperty("authorization.token.header.name"));

        if (authorizationHeader == null || !authorizationHeader.startsWith(environment.getProperty("authorization.token.header.prefix"))) 
            chain.doFilter(req, res);
            return;
        

        UsernamePasswordAuthenticationToken authentication = getAuthentication(req);

        SecurityContextHolder.getContext().setAuthentication(authentication);
        chain.doFilter(req, res);
      

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest req) 

        String token = req.getHeader(HEADER_STRING);

        final String username;
        try 
            DecodedJWT jwt = JWT.require(Algorithm.HMAC256(environment.getProperty("token.secret").getBytes()))
                    .build()
                    .verify(token.replace(TOKEN_PREFIX, ""));
            username = jwt.getSubject();
         catch (JWTVerificationException e) 
            LOG.debug("Invalid JWT", e);
            return null;
        

        final Long userId;
        try 
            userId = Long.valueOf(username);
         catch (NumberFormatException e) 
            LOG.debug("Invalid JWT. Username is not an user ID");
            return null;
        

        LOG.debug("Valid JWT. User ID: " + userId);

         return new UsernamePasswordAuthenticationToken(userId, null, new ArrayList<>());

     


【问题讨论】:

【参考方案1】:

问题是敏感头,Zuul默认Authorization是敏感头,你只需要覆盖敏感头。

zuul:
  sensitive-headers:
  -

通过在 Zuul 网关 application.yml 中设置此属性,它将请求路由到带有 Authorization 标头的身份验证服务

仅供参考:

身份验证服务参考

基于 JWT 的身份验证 https://github.com/nikhilmalavia/SpringBootJWT.git

【讨论】:

标题仍然为空,我收到错误org.springframework.security.authentication.AnonymousAuthenticationToken cannot be cast to org.aacctt.ms.auth.security.JWTAuthentication 可以分享一下安全配置设置代码吗?

以上是关于Spring Boot 如何在 Zuul 微服务上验证令牌的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Spring Boot 应用程序中从 Api 网关(Zuul)调用外部服务(非 MSA)

在 Spring Boot 中使用 netflix zuul 面临微服务问题

使用 Spring boot、Eureka、Zuul、Spring Oauth 创建 OAuth 安全微服务的问题

Spring Boot 微服务 com.netflix.zuul.exception.ZuulException:转发错误

spring cloud: zuul: 正则表达式匹配其他微服务(给其他微服务加版本号)

Spring Boot + Spring Cloud 实现权限管理系统 后端篇(二十一):服务网关(Zuul)