1

I'm kind of new to Spring Cloud. I'm trying to build a few microservices by using Spring Cloud Eureka Discovery and Zuul Gateway.

I can access the microservices and perform actions through Zuul Gateway, but only when there is no security involved. If my microservice is secured, then I cannot do anything through Zuul as it doesn't return back a JWT token. If I do it through Eureka discovery client it works like a charm.

Maybe there is something wrong with my Zuul configuration? Or maybe I have chosen an invalid way to secure the services? Thanks in advance!

Here is my Zuul Gateway configuration:

Application.class

@SpringBootApplication
@EnableZuulProxy
@EnableDiscoveryClient
public class GatewayServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayServiceApplication.class, args);
    }

    @Bean
    public Filter shallowEtagHeaderFilter() {
        return new ShallowEtagHeaderFilter();
    }
}

application.properties

eureka.client.service-url.defaultZone=http://localhost:8010/eureka/
server.port=8011
zuul.prefix=/services

bootstrap.properties

spring.application.name=gateway-service
# specify where config server is up
spring.cloud.config.uri=http://localhost:8001

Here is my microservice JWT & security configuration:

WebSecurityConfig.class

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure (HttpSecurity http) throws Exception {
        http.csrf().disable().authorizeRequests()
                .antMatchers(HttpMethod.POST, "/login").permitAll()
                .anyRequest().authenticated()
                .and()
                // We filter the api/login requests
                .addFilterBefore(new JWTLoginFilter("/login", authenticationManager()), UsernamePasswordAuthenticationFilter.class)
                // And filter other requests to check the presence of JWT in header
                .addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    protected void configure (AuthenticationManagerBuilder auth) throws Exception {
        // Create a default account
        auth.inMemoryAuthentication()
                .withUser("**")
                .password("**")
                .roles("**");
    }
}

TokenAuthenticationService.class

public class TokenAuthenticationService {
    static final long EXPIRATIONTIME = 864_000_000; // 10 days
    static final String SECRET = "*";
    static final String TOKEN_PREFIX = "Bearer";
    static final String HEADER_STRING = "Authorization";

    static void addAuthentication (HttpServletResponse res, String username) {
        String JWT = Jwts.builder()
                .setSubject(username)
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATIONTIME))
                .signWith(SignatureAlgorithm.HS512, SECRET)
                .compact();
        res.addHeader(HEADER_STRING, TOKEN_PREFIX + " " + JWT);
    }

    static Authentication getAuthentication (HttpServletRequest request) {
        String token = request.getHeader(HEADER_STRING);
        if (token != null) {
            // parse the token.
            String user = Jwts.parser()
                    .setSigningKey(SECRET)
                    .parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
                    .getBody()
                    .getSubject();

            return user != null ?
                    new UsernamePasswordAuthenticationToken(user, null, Collections.emptyList()) :
                    null;
        }
        return null;
    }
}

JWTLoginFilter.class

public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter {

    public JWTLoginFilter (String url, AuthenticationManager authManager) {
        super(new AntPathRequestMatcher(url));
        setAuthenticationManager(authManager);
    }

    @Override
    public Authentication attemptAuthentication (HttpServletRequest req, HttpServletResponse res)
            throws AuthenticationException, IOException, ServletException {

        AccountCredentials creds = new ObjectMapper().readValue(req.getInputStream(), AccountCredentials.class);
        return getAuthenticationManager().authenticate(
                new UsernamePasswordAuthenticationToken(
                        creds.getUsername(),
                        creds.getPassword(),
                        Collections.emptyList()
                )
        );

    }

    @Override
    protected void successfulAuthentication (HttpServletRequest req,
                                             HttpServletResponse res,
                                             FilterChain chain,
                                             Authentication auth)
            throws IOException, ServletException {
        TokenAuthenticationService.addAuthentication(res, auth.getName());
    }
}

JWTAuthenticationFilter.class

public class JWTAuthenticationFilter extends GenericFilterBean {
    @Override
    public void doFilter (ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
        Authentication authentication = TokenAuthenticationService.getAuthentication((HttpServletRequest) servletRequest);

        SecurityContextHolder.getContext().setAuthentication(authentication);
        filterChain.doFilter(servletRequest, servletResponse);
    }
}
Deniss M.
  • 3,617
  • 17
  • 52
  • 100
  • Did you remove 'Authorization' header form sensitiveHeaders list ? sensitiveHeaders: Cookie,Set-Cookie,Authorization are defaults. So Authroization headers are filltered out by zuul proxy. If you need it, declare sensitiveHeaders w/o Authorization header. – yongsung.yoon Apr 19 '17 at 08:13
  • @yongsung.yoon you mean this? `zuul.sensitive-headers=Authorisation` – Deniss M. Apr 19 '17 at 08:16
  • You can define sensitive headers for each route. You need to re-declare it without Authroization to allow Authorization header field. Please refere "Cookies and Sensitive Headers" section in http://cloud.spring.io/spring-cloud-netflix/spring-cloud-netflix.html – yongsung.yoon Apr 19 '17 at 08:17
  • @yongsung.yoon `zuul.routes.users.path=/myusers/** zuul.routes.users.sensitiveHeaders=Cookie,Set-Cookie,Authorization zuul.routes.users.url=downstream` like this? – Deniss M. Apr 19 '17 at 08:20
  • Right. Also as you wrote, you can define it globally with "zuu.sensitive-headers=Cookie, Set-Cookie". You need to REMOVE "Authorization" from the list because it's default – yongsung.yoon Apr 19 '17 at 08:21
  • Still doesn't work :( `zuul.routes.users.path=/myusers/** zuul.sensitive-headers=Cookie,Set-Cookie,Authorization zuul.routes.users.sensitiveHeaders=Cookie,Set-Cookie,Authorization zuul.routes.users.url=downstream` – Deniss M. Apr 19 '17 at 08:23
  • 'users' should be replaced with your routing name (serivce ID). And remove Authorization from the list. – yongsung.yoon Apr 19 '17 at 08:24
  • Just try "zuul.sensitiveHeaders=Cookie,Set-Cookie" after removing previous one. – yongsung.yoon Apr 19 '17 at 08:25
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/142051/discussion-between-deniss-m-and-yongsung-yoon). – Deniss M. Apr 19 '17 at 08:26

1 Answers1

2

try to define the below.

zuul.sensitiveHeaders=Cookie,Set-Cookie

In zuul, Cookie, Set-Cookie and Authroization headers are default sensitive headers. If you want to Authroization header in your api server, you need to redefine it without Authroization header like above.

Also you can define it for each route. Please refer to the doc : http://cloud.spring.io/spring-cloud-netflix/spring-cloud-netflix.html [Cookies and Sensitive Headers]

yongsung.yoon
  • 5,489
  • 28
  • 32