Issue
I have a webservice for users management that uses Spring security with the following configuration:
@Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
// Configure AuthenticationManagerBuilder
AuthenticationManagerBuilder authenticationManagerBuilder =
http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.userDetailsService(userDetailsService)
.passwordEncoder(bCryptPasswordEncoder);
AuthenticationManager authenticationManager = authenticationManagerBuilder.build();
// Create AuthenticationFilter
AuthenticationFilter authenticationFilter = getAuthenticationFilter(authenticationManager);
IpAddressMatcher hasIpAddress = new IpAddressMatcher(Objects.requireNonNull(environment.getProperty("gateway.ip")));
IpAddressMatcher hasIpv4Address = new IpAddressMatcher(Objects.requireNonNull(environment.getProperty("gateway.ipv4")));
return http
.cors(corsCustomizer -> corsCustomizer.configurationSource(corsConfigurationSource()))
//.csrf((csrf) -> csrf.disable())
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/**")
.access((authentication, context) -> getAccess(authentication, context, hasIpAddress, hasIpv4Address))
// on autorise tous les points d'entrée d'actuator
.requestMatchers(new AntPathRequestMatcher("/actuator/**"))
.access((authentication, context) -> getAccess(authentication, context, hasIpAddress, hasIpv4Address))
.requestMatchers(HttpMethod.POST, SecurityConstants.SIGN_UP_URL)
.access((authentication, context) -> getAccess(authentication, context, hasIpAddress, hasIpv4Address))
.requestMatchers(HttpMethod.GET, SecurityConstants.EMAIL_VERIFICATION_URL)
.access((authentication, context) -> getAccess(authentication, context, hasIpAddress, hasIpv4Address))
.requestMatchers(HttpMethod.POST, SecurityConstants.PASSWORD_RESET_REQUEST_URL)
.access((authentication, context) -> getAccess(authentication, context, hasIpAddress, hasIpv4Address))
.requestMatchers(HttpMethod.POST, SecurityConstants.PASSWORD_RESET_URL)
.access((authentication, context) -> getAccess(authentication, context, hasIpAddress, hasIpv4Address))
.requestMatchers("/v2/api-docs", "/configuration/**", "/swagger*/**", "/webjars/**", "/v3/api-docs/**").permitAll()
.requestMatchers(HttpMethod.GET, "/hello")
.access((authentication, context) -> getAccess(authentication, context, hasIpAddress, hasIpv4Address))
//.permitAll()
//.anyRequest().access((authentication, context) -> {
// if(authentication.get().isAuthenticated())
// return getAccess(authentication, context, hasIpAddress, hasIpv4Address);
// return new AuthorizationDecision(false);
//})
)
.addFilterAfter(authenticationFilter, AuthenticationFilter.class)
.addFilterAfter(new AuthorizationFilter(authenticationManager, jwtTokenProvider), AuthorizationFilter.class)
.authenticationManager(authenticationManager)
.sessionManagement((session) -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.build();
}
On my API gateway, I defined routes and global cors configuration based on official documentation :
# https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/cors-configuration.html
spring.cloud.gateway.default-filters= DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
spring.cloud.gateway.globalcors.cors-configurations[/**].allowed-origins="http://localhost:4200, http://127.0.0.1:4200"
spring.cloud.gateway.globalcors.cors-configurations[/**].allowed-headers="*"
spring.cloud.gateway.globalcors.cors-configurations[/**].allowedMethods="POST", "GET", "PUT", "DELETE", "PATCH", "OPTIONS"
spring.cloud.gateway.globalcors.cors-configurations[/**].max-age=3600
spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping=true
And I defined routes:
# user registration routes do not need filter
spring.cloud.gateway.routes[0].id=users-ws
spring.cloud.gateway.routes[0].uri = lb://users-ws
spring.cloud.gateway.routes[0].predicates[0]=Path=/users-ws/users
spring.cloud.gateway.routes[0].predicates[1]=Method=POST
spring.cloud.gateway.routes[0].filters[0]=RemoveRequestHeader=Cookie
#spring.cloud.gateway.routes[0].filters[1]=RewritePath=/users-ws/(?<segment>.*), /$\{segment}
spring.cloud.gateway.routes[1].id = users-ws-login
spring.cloud.gateway.routes[1].uri = lb://users-ws
spring.cloud.gateway.routes[1].predicates[0]=Path=/users-ws/users/login
spring.cloud.gateway.routes[1].predicates[1]=Method=POST
spring.cloud.gateway.routes[1].filters[0]=RemoveRequestHeader=Cookie
#spring.cloud.gateway.routes[1].filters[1]=RewritePath=/users-ws/(?<segment>.*), /$\{segment}
# user management routes do need filter
spring.cloud.gateway.routes[2].id=users-management-ws
spring.cloud.gateway.routes[2].uri=lb://users-ws
spring.cloud.gateway.routes[2].predicates[0]=Path=/users-ws/users/**
spring.cloud.gateway.routes[2].predicates[1]=Method=GET,PUT,DELETE
spring.cloud.gateway.routes[2].predicates[2]=Header=Authorization, Bearer (.*)
spring.cloud.gateway.routes[2].filters[0]=RemoveRequestHeader=Cookie
spring.cloud.gateway.routes[2].filters[1]=AuthorizationHeaderFilter
#spring.cloud.gateway.routes[2].filters[2]=RewritePath=/users-ws/(?<segment>.*), /$\{segment}
# configuring filter for actuator
spring.cloud.gateway.routes[3].id = users-ws-actuator
spring.cloud.gateway.routes[3].uri = lb://users-ws
spring.cloud.gateway.routes[3].predicates[0]=Path=/users-ws/actuator/**
spring.cloud.gateway.routes[3].predicates[1]=Method=GET
spring.cloud.gateway.routes[3].filters[0]=RemoveRequestHeader=Cookie
For security i added the following filter for Authorization:
@Component
public class AuthorizationHeaderFilter
extends AbstractGatewayFilterFactory<AuthorizationHeaderFilter.Config> {
private final Environment env;
// step 1: Reading Authorization HTTP Header
@Autowired
public AuthorizationHeaderFilter(Environment env) {
super(Config.class);
this.env = env;
}
@Override
//public GatewayFilter apply(Object config) {
public GatewayFilter apply(Config config) {
// step 1: Reading Authorization HTTP Header
return ((exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
if(!request.getHeaders().containsKey(HttpHeaders.AUTHORIZATION))
return onError(exchange, " No authorization Header", HttpStatus.UNAUTHORIZED);
String authHeader = Objects.requireNonNull(
request.getHeaders().get(HttpHeaders.AUTHORIZATION)).get((0));
String jwt = authHeader.replace("Bearer ", "");
// step 2: Validating JWT Access Token
if(!isJwtValid(jwt))
return onError(exchange, "Invalid JWT", HttpStatus.UNAUTHORIZED);
return chain.filter(exchange);
});
}
private Mono<Void> onError(ServerWebExchange exchange, String err, HttpStatus httpStatus) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(httpStatus);
return response.setComplete();
}
private boolean isJwtValid(String jwt) {
boolean isValid = true;
String subject = null;
String tokenSecret = env.getProperty("token.secret");
assert tokenSecret != null;
byte[] tokenSecretBytes = Base64.getEncoder().encode(tokenSecret.getBytes());
SecretKey signingKey = Keys.hmacShaKeyFor(tokenSecretBytes);
JwtParser parser = Jwts.parser()
.verifyWith(signingKey)
.build();
try {
subject = parser.parseSignedClaims(jwt).getPayload().getSubject();
} catch (Exception e) {
//e.printStackTrace();
System.out.println(e.getMessage());
isValid = false;
}
if(subject == null || subject.isEmpty()) {
isValid = false;
}
return isValid;
}
public static class Config {
// put configuration properties
}
}
I also tried to configure CORS programmatically :
@Configuration
public class AppGlobalCorsConfiguration extends CorsConfiguration {
@Bean
public CorsConfigurationSource corsWebFilter() {
final CorsConfiguration corsConfig = new CorsConfiguration();
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
corsConfig.setAllowedOrigins(Arrays.asList("http://localhost:4200", "http://127.0.0.1:4200"));
corsConfig.setMaxAge(3600L);
corsConfig.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "DELETE", "PATCH", "OPTIONS"));
corsConfig.setAllowedHeaders(Collections.singletonList("*"));
source.registerCorsConfiguration("/**", corsConfig);
return source;
}
}
Now here is my issue: the call to login entry point which requires a json payload works fine with Postman but returns CORS error when using an angular application locally.
For login call, I just implemented the following http call :
login(email: string, password: string): Observable<any> {
return this.http.post<AuthModel>(`${API_USERS_URL}/login`, {
email,
password,
});
}
I do have the following error message:
Solution
Additionally setting CORS config on your backend controller side should solved you issue:
@CrossOrigin(origins = "*")
public class AuthController {
//...
}
Answered By - Oscar
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.