Swagger UI oauth cookie interfering with Spring boot security oauth? - spring-boot

I added springdoc-openapi-ui 1.6.12 in my Spring Boot project. I configured oAuth2 with PKCE and everything works fine, when I click on the "authorize" button it redirects me to a sso connection page. Then, I can send requests via swagger ui.
The problem is that it interferes with my Angular front end authentication. The front loops on the /login rout, with a 401 error. Swagger oauth uses the same session cookie as Spring Security/Angular.
Is there a way to use the same session for swagger ui AND angular ? Or is the problem somewhere else ?
This is my configuration :
springdoc:
swagger-ui:
path: /api-docs
tagsSorter: alpha
oauth:
clientId: "XXX"
clientSecret: "XXX"
use-pkce-with-authorization-code-grant: true
oAuthFlow:
authorizationUrl: "XXX/as/authorization.oauth2"
tokenUrl: "XXX/as/token.oauth2"
scope : XXX profile groups XXX email
edit :
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().configurationSource(corsConfigurationSource())
.and().csrf().disable()
.headers()
.frameOptions().disable()
.httpStrictTransportSecurity()
.includeSubDomains(false)
.maxAgeInSeconds(60*60*24*5)
.and().and()
.authorizeRequests(a -> a
// Management endpoints
.antMatchers(
"/health" + MATCH_ALL,
"/info",
"/prometheus",
"/loggers" + MATCH_ALL,
"/metrics" + MATCH_ALL
).permitAll()
// Authentication
.antMatchers(Routes.CURRENT_USER).permitAll()
.antMatchers("/oauth2/authorization/XXX").permitAll()
.antMatchers(Routes.LOGIN).authenticated()
// Preflight requests
.antMatchers(HttpMethod.OPTIONS).permitAll()
// Applications
.antMatchers(Routes.XXX.BASE + MATCH_ALL).hasAuthority(AuthorityUtil.AUTHORITY_XXX)
)
// By setting the login page here, Spring won't ask which provider we want to use
.oauth2Login().loginPage("/oauth2/authorization/XXX")
.and()
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken)
;
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.applyPermitDefaultValues();
config.setAllowedOriginPatterns(List.of("*"));
config.setAllowedMethods(List.of("*"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
config.setMaxAge(1800L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration(MATCH_ALL, config);
return source;
}

Related

Setting up Swagger UI with Spring WebFlux

I am currently in the process of setting up a Swagger UI interface for one of the projects I am working on and I am experiencing various issues.
My project uses Spring security to secure the API calls using bearer token authentication, so I need to provide a way of enabling the input dialog so that users can input their bearer token. I have tried everything mentioned in the documentation of OpenAPI regarding this but nothing seems to work in rendering the dialog correctly.
Secondly the project does CSRF checks and even though my application properties include springdoc.swagger-ui.csrf.enabled=true the check fails constantly. I have a dead end and I have no idea how to resolve both problems. For reference my security configuration is the following:
#Bean
public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity security) {
if (securityProperties.isEnabled()) {
return security
.securityMatcher(new NegatedServerWebExchangeMatcher(ServerWebExchangeMatchers.pathMatchers(securityProperties.getIgnoredPaths())))
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler)
.authenticationEntryPoint(entryPoint)
.and()
.cors()
.and()
.authorizeExchange(spec -> spec.anyExchange().authenticated())
.oauth2ResourceServer(ServerHttpSecurity.OAuth2ResourceServerSpec::jwt)
.build();
}
return security
.securityMatcher(new PathPatternParserServerWebExchangeMatcher("/**"))
.authorizeExchange(spec -> spec.anyExchange().permitAll())
.csrf()
.disable()
.build();
}
We fixed it with our multi-provider (OAuth2 Keycloak for API and Basic Auth for Swagger UI) Webflux security configuration by adding this to every application.yaml:
springdoc:
api-docs:
enabled: true
swagger-ui:
oauth:
client-id: dev
client-secret: 123
scopes: [openid]
csrf:
enabled: false
Key point here is csrf.enabled: false.
Our Keycloak security configuration:
// Keycloak-based JWT authorization for #RestControllers
#Order(1)
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
public class JwtSecurityConfig {
#Bean("jwt")
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.authorizeExchange()
.pathMatchers("/api/**")
.authenticated()
.and()
.csrf()
.disable()
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(grantedAuthoritiesExtractor());
return http.build();
}
private Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>>
grantedAuthoritiesExtractor() {
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new GrantedAuthoritiesExtractor());
return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
}
}

Spring Security: CORS blocks POST requests, but GET, PUT and DELETE are working

I have the following CORS configuration on my spring gateway:
#Configuration
#EnableWebFluxSecurity
public class GatewayConfig {
#Bean
public CorsWebFilter corsWebFilter() {
final CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.setAllowedOrigins(Collections.singletonList("*"));
corsConfig.setMaxAge(3600L);
corsConfig.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
corsConfig.addAllowedHeader("*");
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfig);
return new CorsWebFilter(source);
}
}
It works perfectly fine with the GET, PUT and DELETE requests, but any POST request returns:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at <service-url>. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 302
Update:
Actually, for some reason it only blocks POST request on one route only.
This is the security configuration:
protected void configure(HttpSecurity http) throws Exception {
// Validate tokens through configured OpenID Provider
http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter());
// Service security setup
http
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/polls").hasRole("ADMIN")
.antMatchers(HttpMethod.PUT, "/polls/*").hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE, "/polls/*").hasRole("ADMIN")
.antMatchers(HttpMethod.POST, "/polls/{author:[\\s\\S]+}/vote").authenticated()
.antMatchers(HttpMethod.POST, "/polls/*").hasRole("ADMIN")
.anyRequest().permitAll();
}
CORS only blocks POST requests on the "/polls" route, while every other request works fine

Spring security - 403 status response on OPTIONS call

I have backend hosted on Heroku, and frontend on Netlify. When I call endpoint on backend it sends preflight OPTIONS but it gives 403 status.
I did search for solution but it still not working.
I want to be able to call "/authenticate" endpoint with "POST" method with body from FE to BE.
Spring security configuration (just configuration methods)
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
...
#Override
public void configure(WebSecurity web) throws Exception
{
web.ignoring()
.antMatchers(HttpMethod.POST, "/authenticate", "/register")
.antMatchers(HttpMethod.GET, "/token")
.antMatchers("/h2-console/**")
.antMatchers("/v2/api-docs",
"/configuration/ui",
"/swagger-resources/**",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.cors()
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/authenticate").permitAll()
.antMatchers(HttpMethod.GET, "/user-data").authenticated()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(new JwtFilter(), UsernamePasswordAuthenticationFilter.class);
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of(<MY-URL>));
configuration.setAllowedHeaders(List.of("*"));
configuration.setMaxAge(Long.valueOf(3600));
configuration.setAllowedMethods(Arrays.asList("GET","POST", "OPTIONS"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
And call from FE
var req = new XMLHttpRequest();
req.open('POST', API_URL + '/authenticate', true);
req.setRequestHeader("Content-Type", "application/json");
req.withCredentials = true;
req.onreadystatechange = function (aEvt) {
if (req.readyState === 4) {
if(req.status === 200) {
console.log(req.responseText);
isAuthenticationSucessful = true;
}
else
console.log("Error loading site");
}
};
req.send(JSON.stringify({username, password}));
Browser dev-tools:
Reason: CORS header 'Access-Control-Allow-Origin' missing
Reason: CORS request did not succeed
TL;DR
Make sure that in setAllowedOrigins("https://myrul.com") you don't have trailing slash or you have exactly the same origin that your browser send.
If your endpoint is in web.ignoring(... delete it from here and put it in (with my example endpoint) http.authorizeRequests().antMatchers("/authenticate").permitAll()
(web and http according to my code in question)
Longer
So how I said in my comment, one thing that make it not working correctly was setting setAllowedOrigins("https://myrul.com/") in corsConfigurationSource.
Notice that trailing slash.
But I noticed in dev-tools that browser send origin header like this: Origin: https://myrul.com without trailing slash. To make it works I have to change allowed origins to proper origin like this: setAllowedOrigins("https://myrul.com") (without trailing slash).
This make browser able to send requests to server, and get 200 response, but browser don't accept response from server cuz CORS.
The next thing was that I have my endpoint in web.ignoring("/authenticate")... and according to this question
Spring Security Configuration - HttpSecurity vs WebSecurity
this statement prevents Spring Security Filter Chain where it should header Access-Control-Allow-Origin which tell browser that it can accept response. MDN Access-Control-Allow-Origin
So the answer for that was take my endpoint from web.ignoring("/authenticate") to http.authorizeRequests().antMatchers("/authenticate").permitAll().
But this makes another problem, that is it will go now to filter chain and to my custom filter http.addFilterBefore(new JwtFilter()..., so make sure to adopt custom filters to yours need.

iron-ajax request results in cors issue with spring boot

Currently I face a CORS issue when doing an ajax call (with the iron-ajax element of Polymer 2) to my server, developed using Spring Boot 2.
Performing a post request to /login via Postman returns in the expected results, however, using a browser like Safari or Chrome results in the following error:
Failed to load http://localhost:8080/login: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8081' is therefore not allowed access. The response had HTTP status code 403.
My configuration in the back-end looks as follows:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/users").permitAll()
.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()
.passwordEncoder(NoOpPasswordEncoder.getInstance())
.withUser("admin")
.password("password")
.roles("ADMIN");
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
The ajax call is set up as follows:
<iron-ajax
id="postLoginAjax"
method="post"
headers='{"access-control-allow-origin": "*"}' // Tried with and without
content-type="application/json"
handle-as"json"
on-touch="touch"
on-response="handleUserResponse"
on-error"handleUserError">
</iron-ajax>
Based on other SO posts I have implemented the Bean, however still not successful.
EDIT:
Also following the global cors settings suggested at https://spring.io/blog/2015/06/08/cors-support-in-spring-framework does not result in what I want. I assume because it is relying on mvc dependency, which I am not using.
Thanks in advance for your help,
Chris

Unauthorized Error when using jHipster oAuth despite CORS

I am running a jHipster instance with oAuth authentication and CORS enabled on the server. I've added the following bean:
#Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.setAllowedMethods(Arrays.asList(new String[]{"GET", "PUT", "POST", "DELETE", "OPTIONS"}));
source.registerCorsConfiguration("/api/**", config);
source.registerCorsConfiguration("/v2/api-docs", config);
source.registerCorsConfiguration("/oauth/**", config);
return new CorsFilter(source);
}
and added .antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll() to ResourceServerConfiguration configuration.
When I attempt to authenticate a user (using jHipster running on a server) from an app running locally on a browser, I get:
Request Method:OPTIONS - Status Code:401 Unauthorized
It seems CORS is not configured properly to handle pre-flight authentication POST requests.
I've tried to implement some solutions proposed at Spring Data Rest and Cors and Spring Data Rest and Cors to no avail.
Is this something specific that can be done in jHipster to enabled authentication to work from a browser or app (not running on the jhipster server)?
I uncommented lines of CORS
cors: #By default CORS are not enabled. Uncomment to enable.
allowed-origins: "*"
allowed-methods: GET, PUT, POST, DELETE, OPTIONS
allowed-headers: "*"
exposed-headers:
allow-credentials: true
max-age: 1800
Added in SecurityConfiguration
**.antMatchers(HttpMethod.OPTIONS, "/**")**
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**")
.antMatchers("/scripts/**/*.{js,html}")
.antMatchers("/bower_components/**")
.antMatchers("/i18n/**")
.antMatchers("/assets/**")
.antMatchers("/swagger-ui/index.html")
.antMatchers("/api/register")
.antMatchers("/api/activate")
.antMatchers("/api/login/**")
.antMatchers("/api/account/reset_password/init")
.antMatchers("/api/account/reset_password/finish")
.antMatchers("/test/**");
}
And it has been working so far.

Resources