How to solve 403 error on WebSecurityConfigurerAdapter with a valid JWT? - java

I've looked at pretty much all of the blogs and SO posts on this topic and I'm not seeing a solution. I have a WebSecurityConfigurerAdapter that looks like this:
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
String issuerUri = "issuer url";
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable()
.authorizeRequests(authorizeRequests ->
authorizeRequests
.antMatchers(HttpMethod.GET, "/test").hasRole("Task.Write")
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2ResourceServer ->
oauth2ResourceServer
.jwt(jwt ->
jwt.decoder(JwtDecoders.fromIssuerLocation(issuerUri))
)
);
}
I have a controller that looks like this:
#GetMapping(value="/test")
public ApplicationResponse test(#AuthenticationPrincipal Jwt jwt) {
// Map<String, Object> x = jwt.getClaims();
return new ApplicationResponse("ok", "ok");
}
However when I hit this endpoint with Postman with a valid JWT I get a 403 error. I have tried prefixing the role with ROLE_ and Role_ and tried numerous other things but it always is 403.
What's weird too is if I do a permitAll() instead of the authenticate and let the JWT go through. I can look into the JWT object in the controller. I see that the role is there. So why does this WebSecurityConfigurerAdapter always throw a 403 when the JWT is valid and the role is there?
I noticed that the roles are located in the claim in the JWT. Maybe I need to get it from here? I don't see how to get roles from claims in the configure method:

By default, Spring Security converts the items in the scope or scp claim and uses the SCOPE_ prefix. You can change both conventions by defining a custom JwtAuthenticationConverter bean.
To export authorities from a roles scope and use the ROLE_ prefix, you can define the following converter, so that you can use methods like hasRole("Task_Write").
#Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}

Related

How to implement a custom OAauth2 authorization with Spring?

Looking at the new spring-security-oauth2-authorization-server library, I need to implement a custom authorization server for a resource server. Actually, I intend to manually implement the PRIVATE_KEY_JWT Authentication method that is either not yet implemented or lacking examples. I followed one good example that uses this approach (the authorization server issuing the access token to the resource server). The resource server has a simple configuration that points to my issuer (authorization server) as in the code:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://auth-server:9000
but this approach uses client-id and client-secret credentials like:
#Bean
#Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http.formLogin(Customizer.withDefaults()).build();
}
#Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("client_id123")
.clientSecret("{noop}secret123")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.scope("https://purl.imsglobal.org/spec/lti-ags/scope/score")
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
I appreciate if anyone could refer me an example of either a PRIVATE_KEY_JWT implementation or a custom authorization implementation that receives a JWT and issues an access token.
In application.yml set up the location of the certs, and create a config class RsaKeyProperties to load them:
rsa:
privateKey: classpath:certs/private.pem
publicKey: classpath:certs/public.pem
Then in WebSecurityConfig:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
#Bean
protected SecurityFilterChain configure(
BasicAuthFilter basicAuthFilter,
ActivitiAuthenticationFilter activitiAuthenticationFilter,
HttpSecurity http) throws Exception {
return http
// Disabling CSRF is safe for token-based API's
.csrf().disable()
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeRequests(auth -> {
auth.antMatchers(
"/api/authenticate/**",
"/api/tenants/**").permitAll();
auth.antMatchers("/api/**").authenticated();
// When an exception is thrown, ErrorMvcAutoConfiguration sets stuff up so that /error is called
// internally using an anonymous user. Without this line, the call to /error fails with a 403 error
// because anonymous users would not be able to view the page.
auth.antMatchers("/error").anonymous();
})
.build();
}
#Bean
public JwtDecoder jwtDecoder(RsaKeyProperties rsaKeyProperties) {
return NimbusJwtDecoder.withPublicKey(rsaKeyProperties.publicKey()).build();
}
#Bean
public JwtEncoder jwtEncoder(RsaKeyProperties rsaKeyProperties) {
JWK jwk = new RSAKey.Builder(rsaKeyProperties.publicKey())
.privateKey(rsaKeyProperties.privateKey())
.build();
JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
return new NimbusJwtEncoder(jwks);
}
}
The "SCOPE_" prefix that the identity server library was adding in the JWT didn't work well with spring security as far as I could figure out, so I removed that:
/**
* For some reason the JwtGrantedAuthoritiesConverter defaults to adding the prefix "SCOPE_" to all
* the claims in the token, so we need to provide a JwtGrantedAuthoritiesConverter that doesn't do
* that and just passes them through.
*/
#Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
grantedAuthoritiesConverter.setAuthorityPrefix("");
JwtAuthenticationConverter authConverter = new JwtAuthenticationConverter();
authConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
return authConverter;
}

How to configure Spring WebFlux authentication with custom AuthenticationConverter

so I was setting up security for my API where you have to log in with username/password obtain JWT token and then use that for everything else.
This is how I set up SecurityWebFilterChain:
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.pathMatchers("/login", "/")
.permitAll()
.and()
.addFilterAt(UsernamePasswordAuthenticationFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
.authorizeExchange()
.pathMatchers("/api/**")
.authenticated()
.and()
.addFilterAt(bearerAuthenticationFilter(), SecurityWebFiltersOrder.AUTHENTICATION);
return http.build();
}
as you can see here, I set up two custom filters. One for username/password and another for Bearer token (JWT).
This is how I set up UsernamePassword filter, where I provide my custom authManager with custom UserDetails service, encoder and successhandler:
private AuthenticationWebFilter UsernamePasswordAuthenticationFilter() {
UserDetailsRepositoryReactiveAuthenticationManager authManager;
AuthenticationWebFilter usernamePasswordAuthenticationFilter;
ServerAuthenticationSuccessHandler successHandler;
authManager = new UserDetailsRepositoryReactiveAuthenticationManager(applicationUserService);
authManager.setPasswordEncoder(passwordEncoder);
successHandler = new AuthenticationSuccessHandler();
usernamePasswordAuthenticationFilter = new AuthenticationWebFilter(authManager);
usernamePasswordAuthenticationFilter.setAuthenticationSuccessHandler(successHandler);
return usernamePasswordAuthenticationFilter;
}
This actually works, I can issue GET request with curl or Postman like this:
curl -v -u username:password localhost:8080/login
and get my JWT token back. awesome. Just one problem. By default this filter uses
ServerHttpBasicAuthenticationConverter
What I actually want:
i need to send POST request with username and password in request body in simple JSON structure, like this:
{
"username":"usr"
"password":"pass"
}
and use that to create Authentication object.
This is what I tried:
I created and added custom Converter to my AuthenticationFilter:
usernamePasswordAuthenticationFilter.setServerAuthenticationConverter(new UsernamePasswordAuthenticationConverter());
UsernamePasswordAuthenticationConverter:
public class UsernamePasswordAuthenticationConverter implements ServerAuthenticationConverter {
#Override
public Mono<Authentication> convert(ServerWebExchange exchange) {
...
}
}
This is where Im stuck for hours.
Question1
How do I get username/password from JSON body of request and produce Mono<Authentication> here? I'm pretty new to reactive programming, sorry if this is stupid question.
Question2
Only GET requests seesm to even reach this custom UsernamePasswordAuthenticationConverter. Why? any POST requests get rejected before that and I don't understand why.

Disable CSRF verification in Spring

I want to send http request from Ruby code with these values:
http://some_domain.com?key=value&t5052&key=value&key=value
I have this Spring configuration:
#PostMapping(consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, value = "/v1/notification")
public ResponseEntity<String> handleNotifications(#RequestBody MultiValueMap<String, Object> keyValuePairs) {
.....
return new ResponseEntity<>(HttpStatus.OK);
}
Spring convert config:
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.removeIf(converter -> converter instanceof MappingJackson2XmlHttpMessageConverter);
converters.removeIf(converter -> converter instanceof MappingJackson2HttpMessageConverter);
converters.add(new MappingJackson2XmlHttpMessageConverter(
((XmlMapper) createObjectMapper(Jackson2ObjectMapperBuilder.xml()))
.enable(ToXmlGenerator.Feature.WRITE_XML_DECLARATION)));
converters.add(new MappingJackson2HttpMessageConverter(createObjectMapper(Jackson2ObjectMapperBuilder.json())));
}
But I get error:
<h1>Forbidden <span>(403)</span></h1>
<p>CSRF verification failed. Request aborted.</p>
<p>You are seeing this message because this site requires a CSRF cookie when submitting forms. This cookie is required for security reasons, to ensure that your browser is not being hijacked by third parties.</p>
<p>If you have configured your browser to disable cookies, please re-enable them, at least for this site, or for 'same-origin' requests.</p>
Do you know how I can fix this issue? Can I somehow disable this CSRF check in spring?
You can disable CSRF by creating a configuration like this:
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
}
}

Can we use httpbasic and OAuth2 both for an API depending on path variable?

In my application I want to provide OAuth2 security only to some specific API calls. My question is can I provide HttpBasic or Oauth2 authentication based on the path variable?
Below are two scenarios I will be considering.
1) Lets say for user(whose name is provided in path variable) xyz, if xyz does not have the feature of OAuth, I want to authenticate it using httpBasic
2) If another user abc has feature of OAuth, I want to authenticate it using Oauth/OpenId connect.
I have a table which assigns features to user, below is the glimpse of the table.
Name , Feature
xyz, HttpBasic
abc, Oauth
Okay I did some research on my own and able to find a solution. Here's what I did,
-Created one httpbasic configuration with WebSecurityConfigurerAdapter, now before any interceptor begins it task I have created one request matcher which will check if the authorization header is Basic or Bearer.
//By default this filter order is 100 and OAuth has filter order 3
#Order(2)
public class MicroserviceSecurityConfigurationHttpBasic extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().exceptionHandling()
.authenticationEntryPoint(customAccessDeniedHandler())
.and().headers().frameOptions().disable()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.requestMatcher(new BasicRequestMatcher())
.authorizeRequests()
.antMatchers("/api/**").authenticated()
.and().httpBasic();
}
private class BasicRequestMatcher implements RequestMatcher {
#Override
public boolean matches(HttpServletRequest httpRequest) {
String auth = httpRequest.getHeader("Authorization");
String requestUri = httpRequest.getRequestURI();
//Fetching Identifier to provide OAuth Security to only specific urls
String identifier= requestUri.substring(requestUri.lastIndexOf("/") + 1, requestUri.length());
//Lets say for identifier ABC only, I want to secure it using OAuth2.0
if (auth != null && auth.startsWith("Basic") && identifier.equalsIgnoreCase("ABC")) {
auth=null;
}
//For ABC identifier this method will return null so then the authentication will be redirected to OAuth2.0 config.
return (auth != null && auth.startsWith("Basic"));
}
}
}
-After this I have created OAuth2.0 configuration with ResourceServerConfigurerAdapter, here is the glimpse of it.
//Default filter order=3 so this will be executed after WebSecurityConfigurerAdapter
public class MicroserviceSecurityConfiguration extends ResourceServerConfigurerAdapter {
...
//Here I am intercepting the same url but the config will look for bearer token only
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable().exceptionHandling()
.and().headers().frameOptions().disable()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers("/api/**").authenticated();
}
}
References : https://github.com/spring-projects/spring-security-oauth/issues/1024
Spring security with Oauth2 or Http-Basic authentication for the same resource

Could not verify the provided CSRF token because your session was not found in spring security

I am using spring security along with java config
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/*").hasRole("ADMIN")
.and()
.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class)
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.formLogin()
.successHandler(authenticationSuccessHandler)
.failureHandler(new SimpleUrlAuthenticationFailureHandler());
I am using PostMan for testing my REST services. I get 'csrf token' successfully and I am able to login by using X-CSRF-TOKEN in request header. But after login when i hit post request(I am including same token in request header that i used for login post request) I get the following error message:
HTTP Status 403 - Could not verify the provided CSRF token because your session was not found.
Can any one guide me what I am doing wrong.
According to spring.io:
When should you use CSRF protection? Our recommendation is to use CSRF
protection for any request that could be processed by a browser by
normal users. If you are only creating a service that is used by
non-browser clients, you will likely want to disable CSRF protection.
So to disable it:
#Configuration
public class RestSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
}
}
Note: CSRF protection is enabled by default with Java Configuration
try this: #Override protected boolean sameOriginDisabled() { return true;}
#Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
...
// Determines if a CSRF token is required for connecting. This protects against remote
// sites from connecting to the application and being able to read/write data over the
// connection. The default is false (the token is required).
#Override
protected boolean sameOriginDisabled() {
return true;
}
}
source: WebSocket Security: Disable CSRF within WebSockets
Disabling CSRF protection is a bad idea.
Spring will automatically generate a new CSRF token after each request, and you need to include it in all HTTP requests with side-effects (PUT, POST, PATCH, DELETE).
In Postman you can use a test in each request to store the CSRF token in a global, e.g. when using CookieCsrfTokenRepository
pm.globals.set("xsrf-token", postman.getResponseCookie("XSRF-TOKEN").value);
And then include it as a header with key X-XSRF-TOKEN and value {{xsrf-token}}.
Came to same error just with POST methods, was getting 403 Forbidden "Could not verify the provided CSRF token because your session was not found."
After exploring some time found solution by adding #EnableResourceServer annotation to config.
Config looks like that (spring-boot.version -> 1.4.1.RELEASE, spring-security.version -> 4.1.3.RELEASE, spring.version -> 4.3.4.RELEASE)
#Configuration
#EnableWebSecurity
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends ResourceServerConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(inMemoryUserDetailsManager()).passwordEncoder(passwordEncoder());
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.httpBasic();
http.sessionManagement().sessionCreationPolicy(STATELESS);
http.csrf().disable();
http.authorizeRequests().anyRequest()
.permitAll();
}
private InMemoryUserDetailsManager inMemoryUserDetailsManager() throws IOException {
// load custom properties
Properties properties = new Properties();
return new InMemoryUserDetailsManager(properties);
}
private PasswordEncoder passwordEncoder() {
return new TextEncryptorBasedPasswordEncoder(textEncryptor());
}
private TextEncryptor textEncryptor() {
return new OpenSslCompatibleTextEncryptor();
}
}
I get this error message (HTTP Status 403 - Could not verify the provided CSRF token because your session was not found.) when I do a JS fetch AJAX call without using the credentials: "same-origin" option.
Wrong way
fetch(url)
.then(function (response) { return response.json(); })
.then(function (data) { console.log(data); })
Correct way
fetch(url, {
credentials: "same-origin"
})
.then(function (response) { return response.json(); })
.then(function (data) { console.log(data); })
This is an old question but this might help someone. I had the similar issue and this is how I was able to resolve it.
In order for the CSRF to work with the REST API you need to obtain a CSRF token via API before every single call and use that token. Token is different every time and cannot be re-used.
Here is the controller to get the CSRF token:
#RequestMapping(value = "/csrf", method = RequestMethod.GET)
public ResponseEntity<CSRFDTO> getCsrfToken(HttpServletRequest request) {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
return ResponseEntity.ok(CSRFDTO.builder()
.headerName(csrf.getHeaderName())
.token(csrf.getToken())
.build());
}
Additionally, you might consider configuring your Spring app to disable the CSRF for the REST API endpoints. To quote an article I've read somewhere:
I'm very certain that CSRF tokens on a REST endpoint grant zero additional protection. As such, enabling CSRF protection on a REST endpoint just introduces some useless code to your application, and I think it should be skipped.
Hope this helps.
I have solved it by adding the last attribute in my login page,maybe it will do yo a favor.
<%# page language="java" import="java.util.*" pageEncoding="UTF-8" isELIgnored="false"%>

Categories

Resources