Spring Cloud Netflix Zuul routing with account service

Viewed 121

I am developing a microservices web application.

I have this microservices:

  • frontEnd service (all html files)
  • Account service (with Spring Security)
  • ZuulGateway service
  • Demo service (a simple rest controller that return a string)
  • EurekaServer service

My account service works. The login phase works and the JWT creation works. I save it in the header of the response:

response.addHeader("Authorization", "Bearer " + token);

This is the Ajax code in the frontEnd:

/* sign in submit function */
    $("#submit").click(function(e) {
        e.preventDefault();
        $.ajax({ /* Ajax call to AccountMicroservice for login */
            url : 'http://localhost:8762/token/generate-token',
            type : "POST",
            data : {
                username : $("#username").val(),
                password : $("#password").val()
            },
            success : function(data) {

                console.log(data.token);

                window.location.href = 'http://localhost:8762/test';

            },
            error : function(result) {
                alert("Sign in failed!");
                console.log(result);
            }
        });
    });

The problems arose with the addition of Spring Cloud Netflix Zuul. The gateway server port is 8762. Each request to 'http://localhost:8762/token/generate-token' return a 401 error.

These are the Zuul gateway service. I omitted the import.

application.properties:

server.port=8762
spring.application.name=gateway-service

eureka.client.serviceUrl.defaultZone=${EUREKA_SERVER_URL:http://localhost:8761/eureka}
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true

zuul.routes.test-service.path=/test/**
zuul.routes.test-service.service-id=test-service


zuul.routes.account-service.path=/token/**
zuul.routes.account-service.service-id=account-service


zuul.routes.auth-service.sensitive-headers=Cookie,Set-Cookie

webSecurityConfig:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


@Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;



@Override
protected void configure(HttpSecurity http) throws Exception {
    http
    .cors()
    .and()
    .csrf().disable()
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    .and()
        .exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
    .and()
        .addFilterAfter(new JwtTokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
    .authorizeRequests()
        // allow all who are accessing "auth" service
       .antMatchers(HttpMethod.POST, "/token/**", "/signup").permitAll()
       // Any other request must be authenticated
       .anyRequest().authenticated(); 
}

//The CORS filter bean - Configures allowed CORS any (source) to any 
//(api route and method) endpoint
@Bean
CorsConfigurationSource corsConfigurationSource() {
    final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    final CorsConfiguration config = new CorsConfiguration();
    config.setAllowCredentials(true);
    config.addAllowedOrigin(CorsConfiguration.ALL);
    //config.addAllowedHeaders(Collections.singletonList(CorsConfiguration.ALL));
    config.addAllowedMethod("OPTIONS");
    config.addAllowedMethod("HEAD");
    config.addAllowedMethod("GET");
    config.addAllowedMethod("PUT");
    config.addAllowedMethod("POST");
    config.addAllowedMethod("DELETE");
    config.addAllowedMethod("PATCH");
    source.registerCorsConfiguration("/**", config);
    return source;
}

}

JwtTokenUtil:

@Component
public class JwtTokenUtil implements Serializable {

public String getUsernameFromToken(String token) {
    return getClaimFromToken(token, Claims::getSubject);
}

public Date getExpirationDateFromToken(String token) {
    return getClaimFromToken(token, Claims::getExpiration);
}

public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
    final Claims claims = getAllClaimsFromToken(token);
    return claimsResolver.apply(claims);
}

private Claims getAllClaimsFromToken(String token) {
    return Jwts.parser()
            .setSigningKey(SIGNING_KEY)
            .parseClaimsJws(token)
            .getBody();
}

private Boolean isTokenExpired(String token) {
    final Date expiration = getExpirationDateFromToken(token);
    return expiration.before(new Date());
}

public String generateToken(Authentication authentication) {
    final String authorities = authentication.getAuthorities().stream()
            .map(GrantedAuthority::getAuthority)
            .collect(Collectors.joining(","));
    return Jwts.builder()
            .setSubject(authentication.getName())
            .claim(AUTHORITIES_KEY, authorities)
            .signWith(SignatureAlgorithm.HS256, SIGNING_KEY)
            .setIssuedAt(new Date(System.currentTimeMillis()))
            .setExpiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_VALIDITY_SECONDS*1000))
            .compact();
    
}

public Boolean validateToken(String token, UserDetails userDetails) {
    final String username = getUsernameFromToken(token);
    return (
          username.equals(userDetails.getUsername())
                && !isTokenExpired(token));
}

UsernamePasswordAuthenticationToken getAuthentication(final String token, final Authentication existingAuth, final UserDetails userDetails) {

    final JwtParser jwtParser = Jwts.parser().setSigningKey(SIGNING_KEY);

    final Jws<Claims> claimsJws = jwtParser.parseClaimsJws(token);

    final Claims claims = claimsJws.getBody();

    final Collection<? extends GrantedAuthority> authorities =
            Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
                    .map(SimpleGrantedAuthority::new)
                    .collect(Collectors.toList());

    return new UsernamePasswordAuthenticationToken(userDetails, "", authorities);
}

}

JwtTokenAuthenticationFilter:

public class JwtTokenAuthenticationFilter extends  OncePerRequestFilter {


@Autowired
private UserDetailsService userDetailsService;

@Autowired
private JwtTokenUtil jwtTokenUtil;


@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
    String header = request.getHeader(HEADER_STRING);
    String username = null;
    String authToken = null;

    // 2. validate the header and check the prefix
    if (header != null && header.startsWith(TOKEN_PREFIX)) {
        // 3. Get the token
        authToken = header.replace(TOKEN_PREFIX,"");
        try {
            username = jwtTokenUtil.getUsernameFromToken(authToken);
            
            
            System.out.println("\n\n\n\n\n\n\n\n\n\n" + username + "\n\n\n\n\n\n\n\n\n\n");

            
            
        } catch (IllegalArgumentException e) {
            logger.error("c'è stato un errore durante il reperimento dello username dal token", e);
        }
    }
    
    if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {

        UserDetails userDetails = userDetailsService.loadUserByUsername(username);

        // 4. Validate the token
        if (jwtTokenUtil.validateToken(authToken, userDetails)) {
            //UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")));
            UsernamePasswordAuthenticationToken authentication = jwtTokenUtil.getAuthentication(authToken, SecurityContextHolder.getContext().getAuthentication(), userDetails);
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            logger.info("authenticated user " + username + ", setting security context");
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
    }

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

}

JwtAuthenticationEntryPoint:

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {

@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {

    response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
    
    String json = String.format("{\"message\": \"%s\"}", authException.getMessage());
    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    response.setContentType("application/json");
    response.setCharacterEncoding("UTF-8");
    response.getWriter().write(json);   
}

}

constants:

public class Constants {

public static final long ACCESS_TOKEN_VALIDITY_SECONDS = 5*60*60;
public static final String SIGNING_KEY = "devglan123r";
public static final String TOKEN_PREFIX = "Bearer ";
public static final String HEADER_STRING = "Authorization";
public static final String AUTHORITIES_KEY = "scopes";

}

0 Answers
Related