I have a oauth flow in my project.
I retrieve in the front-end a jwt token and add it to each request in the authorization header.
Now I need to validate said token and verify the signature in my back-end which is a kotlin spring boot app.
I know how to validate the token with the jjwt library but I don't understand where the validation is done.
I have a certificate to validate the tokens with and just want to let the requests with a valid token to be treated.
I saw online that some people do it with a OncePerRequestFilter that they add to their SecurityConfiguration but I don't understand what's going on and how it works.
I tried searching for tutorials online but many of them make a backend that's both the authorization server and resource server. I just want the backend to be the resource server that checks with the certificate if the token is valid and treats the request if it is. How can I do that ?
For now this is my SecurityConfiguration :
package com.renaulttrucks.transfertprotocolbackend.security.config
import org.springframework.beans.factory.annotation.Value
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
#EnableWebSecurity
class SecurityConfig : WebSecurityConfigurerAdapter() {
#Value("\${security.enabled}")
val securityEnabled : Boolean? = false
#Throws(Exception::class)
override fun configure(http: HttpSecurity) {
if(!securityEnabled!!) {
http.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/**").permitAll()
.and()
.csrf().disable()
.formLogin().disable()
} else {
http.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/**").permitAll()
.and()
.csrf().disable()
.formLogin().disable()
}
}
}
Spring Security supports resource servers out-of-the-box when including the correct dependencies and configuration.
As #sdoxsee mentioned, there is a Spring Security sample that outlines how to create a resource server with a public key; however, I'll briefly summarize it here, though you can find more detail in the Spring Security reference.
First, you need to add the appropriate dependency. If you are a Spring Boot application, then you can add:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
Second, you either specify your key as a Boot property:
spring:
security:
oauth2:
resourceserver:
jwt:
public-key-location: classpath:my-key.pub
or, you configure a JwtDecoder with your public key directly:
#Configuration
class SecurityConfig {
#Value("${public.key.property}") val key : RSAPublicKey;
#Bean
fun jwtDecoder() : JwtDecoder {
return NimbusJwtDecoder.withPublicKey(this.key).build();
}
}
Either the Boot property or the JwtDecoder #Bean will introduce a filter automatically into the filter chain called BearerTokenAuthenticationFilter, so you don't need to create your own.
Related
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);
}
}
I developed backend microservice application using Spring Boot and put API Gateway in front of microservices. To authenticate users I am using Keycloak.
Right now I am developing frontend application using Svelte, I configured my application.yml in gateway application like this:
spring:
cloud:
gateway:
default-filters:
- TokenRelay
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*"
allowedMethods: "*"
allowedHeaders: "*"
add-to-simple-url-handler-mapping: true
However, when I am trying to send AJAX request I get the CORS error.
Also I Have spring security (through org.springframework.boot:spring-boot-starter-oauth2-client and org.springframework.boot:spring-boot-starter-oauth2-resource-server dependencies). I defined SecurityWebFilterChain as:
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.cors().and()
.authorizeExchange()
.pathMatchers("/actuator/**")
.permitAll()
.and()
.authorizeExchange()
.anyExchange()
.authenticated()
.and()
.oauth2Login(); // to redirect to oauth2 login page.
return http.build();
}
When putting build of frontend in static folder there is no CORS error, but for development I need developer node.js server on localhost on different port.
So, how to fix this cors issue?
You can create a class to define the Cors mapping like this:
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}
}
The example above enables CORS requests from any origin to any endpoint in the application.
To lock this down a bit more, the registry.addMapping method returns a CorsRegistration object, which we can use for additional configuration. There’s also an allowedOrigins method that lets us specify an array of allowed origins. This can be useful if we need to load this array from an external source at runtime.
Additionally, there are also allowedMethods, allowedHeaders, exposedHeaders, maxAge and allowCredentials that we can use to set the response headers and customization options.
CORS With Spring Security:
If you use Spring Security in youproject, you must take an extra step to make sure it plays well with CORS. That's because CORS needs to be processed first. Otherwise, Spring Security will reject the request before it reaches Spring MVC.
Luckily, Spring Security provides an out-of-the-box solution:
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and()...
}
}
I'm writing an API that must verify if JWS has a valid signature and it's not expired - my project has a few routes and only a single route is protected.
I have create a filter:
#Component
class JWTFilter : OncePerRequestFilter() {
override fun doFilterInternal(req: HttpServletRequest, resp: HttpServletResponse, chain: FilterChain) {
// code to validate JWT
chain.doFilter(req, resp)
}
}
And the SecConfig:
#Configuration
#EnableWebSecurity
class SecConfig(val authRequestFilter: AuthRequestFilter) : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http
.authorizeRequests()
.antMatchers("/cash")
.authenticated()
.anyRequest()
.permitAll()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
http.addFilterBefore(authRequestFilter, <WhatShouldIPutHere.class>)
}
}
I have created the SecConfig class to centralize all configuration and my API doesn't have any kind of login, I just receive the JWS Token and I need to ensure that is valid.
What is the correct implementation to be used in <WhatShouldIPutHere.class>?
edit:
It works but what's the point of use UsernamePasswordAuthenticationFilter if I dont have Username/Pw authentication in my application?
http.addFilterBefore(authRequestFilter, UsernamePasswordAuthenticationFilter::class.java)
edit 2:
Why is it validating every request? I just want to validate /cash
It works but what's the point of use UsernamePasswordAuthenticationFilter if I dont have Username/Pw authentication in my application?
It tells spring where in the filter chain you want to place your JWT filter. See filter ordering. More specifically, using addFilterBefore like that will put your filter just before the UsernamePasswordAuthenticationFilter.
Why is it validating every request? I just want to validate /cash
Because you have not configured any specific paths in this line:
http.addFilterBefore(authRequestFilter, UsernamePasswordAuthenticationFilter::class.java)
Use http.antMatchers("/cash").addFilterBefore...
You must use:
org.springframework.web.filter.OncePerRequestFilter
I have a Spring Boot 2 app with Spring security, as follow:
#SpringBootApplication(exclude = [(SecurityAutoConfiguration::class)])
class UntappdCqrsApplication
fun main(args: Array<String>) {
runApplication<UntappdCqrsApplication>(*args)
}
and the configuration class
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
class TokenConfiguration(
val jwtTokenProvider: JwtTokenProvider
) : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests()
.antMatchers("/users/signup").permitAll()
.anyRequest().authenticated()
http.apply(JwtTokenConfigurer(jwtTokenProvider));
}
}
There are two endpoints: POST users/signup and GET users/test.
According to my configuration, /signup should not require authentication and /test should, but both endpoints are accessible without any authentication.
If I add #EnableWebSecurity in my TokenConfiguration class, Spring now generates a default password and both endpoints are now protected.
I think I'm missing something here, but I have no idea what
You haven't provided the source or imports for your JwtTokenProvider or JwtTokenConfigurer classes, but it seems likely that your JwtTokenProvider is throwing an unchecked exception or even directly sending a response on authentication failure. This will prevent permitAll() from ever being triggered.
See my response to a similar question:
https://stackoverflow.com/a/46086769/873590
I am using spring security oauth in my project. I am excluding some urls from authentication by configuring in spring security ResourceServerConfigurerAdapter. I added http.authorizeRequests().antMatchers(url).permitAll().
Now, what I am seeing is that, if I don't pass the Authorization header to these urls, it is not authenticated. And the API is called properly.
If the call is made with an Authorization header, then it validates the token and fails the call if the token is not validated.
My question is what do I need to do so that the token is ignored in the request for which I have permitAll.
Spring OAuth2 will intercept all url with header: Authorization Bearer xxx.
To avoid Spring OAuth2 from intercept the url. I have created a SecurityConfiguration which has higher order than Spring OAuth2 configuration.
#Configuration
#EnableWebSecurity
#Order(1) // this is important to run this before Spring OAuth2
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
List<RequestMatcher> requestMatchers = new ArrayList<RequestMatcher>();
// allow /api/public/product/** and /api/public/content/** not intercepted by Spring OAuth2
requestMatchers.add(new AntPathRequestMatcher("/api/public/product/**"));
requestMatchers.add(new AntPathRequestMatcher("/api/public/content/**"));
http
.requestMatcher(new OrRequestMatcher(requestMatchers))
.authorizeRequests()
.antMatchers("/api/public/product/**", "/api/public/content/**").permitAll()
}
}
The above configuration allows /api/public/product/** and /api/public/content/** to be handled by this configuration, not by Spring OAuth2 because this configuration has higher #Order.
Therefore, even setting invalid token to above api call will not result in invalid access token.
As per spring-oauth2 docs https://projects.spring.io/spring-security-oauth/docs/oauth2.html
Note: if your Authorization Server is also a Resource Server then there is another security filter chain with lower priority controlling the API resources. Fo those requests to be protected by access tokens you need their paths not to be matched by the ones in the main user-facing filter chain, so be sure to include a request matcher that picks out only non-API resources in the WebSecurityConfigurer above.
So define WebSecurityConfigurer implementation with higher order than ResourceServerConfig.
In case you are dealing with Reactive Spring webflux, from SooCheng Koh's answer.
#Configuration
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
#Order(1) // this is important to run this before Spring OAuth2
public class PublicSecurityConfiguration {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.pathMatchers("/api/public/**").permitAll();
return http.build();
}
}
It's not a bug it's a feature :)
As already mentioned by other people, even if you have permitAll, Spring Security will still check the token if there is a header "Authorization".
I don't like the workaround on the backend with Order(1) so I did a change on the frontend simply removing the header "Authorization" for the specific request.
Angular example with interceptor:
#Injectable()
export class PermitAllInterceptor implements HttpInterceptor {
constructor() {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if(req.url.includes('permitAllUrl')){
req = req.clone({ headers: req.headers.delete('Authorization') });
}
return next.handle(req);
}
}
and then just register the interceptor in app.module.ts:
{
provide: HTTP_INTERCEPTORS,
useClass: PermitAllInterceptor ,
multi: true
}