I am using the following dependencies in one application: Spring-Cloud-Gateway, Spring Boot OAuth2 Client, Spring Boot OAuth2 Resource Server.
I use the following security config:
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, ReactiveClientRegistrationRepository clientRegistrationRepository) {
http.oauth2Login();
http.logout(logout -> logout.logoutSuccessHandler(
new OidcClientInitiatedServerLogoutSuccessHandler(clientRegistrationRepository)));
http.authorizeExchange()
.pathMatchers("/actuator/health").permitAll()
.pathMatchers("/auth/realms/ahearo/protocol/openid-connect/token").permitAll()
.pathMatchers("/v3/api-docs").permitAll()
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(userJwtAuthenticationConverter());
http.csrf().disable().formLogin().disable().httpBasic().disable();
return http.build();
}
#Bean
public UserJwtAuthenticationConverter userJwtAuthenticationConverter() {
return new UserJwtAuthenticationConverter();
}
When I execute calls I am correctly advised to login which works fine. But it's just the Authentication that works, not the Authorization. When I use the debugger I can see that the userJwtAuthenticationConverter() method is never called to use roles out of the JWT.
When I use the same method in another application/microservice which is just a OAuth2 Resource Server, but not a OAuth2 Client the method is correctly called and executed.
The security config in the application.yaml looks like the following in the Spring Cloud Gateway application:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost/auth/realms/example-realm
jwk-set-uri: http://localhost/auth/realms/example-realm/protocol/openid-connect/certs
client:
registration:
keycloak:
client-id: 'example-proxy-client'
client-secret: 'xxx'
authorizationGrantType: authorization_code
redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}'
scope: openid,profile,email
provider:
keycloak:
issuer-uri: http://localhost/auth/realms/example-realm
user-name-attribute: preferred_username
Isn't it possible for the Spring Cloud Gateway application to perform as a OAuth2 Client and Resource Server at the same time or am I doing a mistake regarding the configuring of the application?
Turns out that I misunderstood some basic OAuth2 concepts.
The Authorization itself is working correctly when I send a JWT in the Authorization header (Implicit Flow). What did not work is the case when I tried to access a resource via the browser.
I got redirected to the login page of Keycloak (Authorization Code Flow). After you sign in through the login page of Keycloak you do not receive the JWT but a Keycloak Session ID. Spring Cloud Gateway cannot perform Authorization on the basis of a Keycloak Session ID (do not know how that would work if I wanted to use Authorization Code Flow but I am using Implicit flow, so I am fine for now).
Related
I want to have a WebClient which uses a client_credentials OAuth2 flow to authorize with an API. I've followed various sets of instructions from the documentation to several tutorials.
I feel like I'm pretty close to getting Spring Boot to do what I want, but the default behaviour is doing something I don't want - I get redirected to /login when I make a request to any of my controllers. I want to be able to (at the moment) do an unauthorized request to my API, and have the service-to-service call use the configured OAuth2 flow.
spring:
main:
web-application-type: reactive
security:
oauth2:
client:
registration:
my-private-api:
client-id: <foo>
client-secret: <bar>
authorization-grant-type: client_credentials
provider:
my-private-api:
token-uri: <uri>
#Bean
WebClient webClient( final ReactiveClientRegistrationRepository clientRegistrations,
final ReactiveOAuth2AuthorizedClientService authorizedClientService )
{
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
new ServerOAuth2AuthorizedClientExchangeFilterFunction( new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
clientRegistrations,
authorizedClientService ) );
oauth.setDefaultClientRegistrationId( "my-private-api" );
return WebClient.builder().filter( oauth ).build();
}
Do I need to do lots of manual configuration in order to avoid this default behaviour I don't want?
So turns out my OAuth configuration was fine, what I was actually asking for was "how do I disable the default behaviour of Spring Security's SecurityWebFilterChain" - which is to add a bean overriding the behaviour:
#Bean
SecurityWebFilterChain springWebFilterChain( final ServerHttpSecurity http )
{
http.httpBasic().disable().formLogin().disable().csrf().disable().logout().disable();
return http.build();
}
I have simple resource server application with spring boot, this is yaml file:
server:
port: 8081
servlet:
context-path: /resource-server-jwt
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:8083/auth/realms/rasool
Now, i want to make change in configuration or code to force spring security to validate JWT token with calling introspection endpoint of authorization server instead of local validation with keys, but i didn't find any way as spring security docs says.
Spring-boot spring.security.oauth2.resourceserver.jwt.* configuration properties are for JWT decoder.
For token introspection, use spring.security.oauth2.resourceserver.opaque-token.* properties instead (token being in whatever format, including JWT). "opaque" means that tokens are considered a "black-box" by resource-server which delegates validataion and attributes retrieval to authorization-server on introspection endpoint:
server:
port: 8081
servlet:
context-path: /resource-server-jwt
spring:
security:
oauth2:
resourceserver:
opaque-token:
introspection-uri: http://localhost:8083/auth/realms/rasool/protocol/openid-connect/token/introspect
client-id: change-me
client-secret: change-me
Introspection uri from .well-known/openid-configuration
If you are using Java configurationn the switch is about the same: replace http.oauth2ResourceServer().jwt()... with http.oauth2ResourceServer().opaqueToken()...
A few notes about declared clients on authorisation-server
Resource-servers introspect token on authorisation-server introspection endpoint using client-credentials flow: for each and every request it process, resource-servers will send a request to authorization-server to get token details. This can have serious performance impact. Are you sure you want to switch to token introspection?
As a consequence, in the properties above, you must configure a client with:
"Access Type" set to confidential
"Service Accounts Enabled" activated
Create one if you don't have yet. You'll get client-secret from "credentials tab" once configuration saved.
Note you should have other (public) clients to identify users (from web / mobile apps or REST client) and query your resource-server on behalf of those users.
From the authorization-server point of view, this means that access-tokens will be issued to a (public) client and introspected by antoher (confidential) client.
Complete working sample here
It does a few things useful for resource-servers:
authorities mapping (choose attributes to parse user authorities from, prefix & case processing)
CORS configuration
stateless-session management
CSRF with Cookie repo
anonymous enabled for a list of configured public routes
401 (unauthorized) instead of 302 (redirect to login) when trying to access protected resources with missing or invalid Authorization
I am configuring multiple authentication clients for a spring-boot application, and am attempting to override the default redirect URI using:
spring.security.oauth2.client.registration.google.redirectUri={baseUrl}/oauth2/callback/{registrationId}
and then setting the following in SecurityConfig:
http.oauth2Login()
.authorizationEndpoint().baseUri("/oauth2/authorize")
.and()
.redirectionEndpoint().baseUri("/oauth2/callback/*")
However, this is not working - when accessing {baseUrl}/oauth2/authorize/google, the client is redirected to
https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount
?response_type=code
&client_id<clientId>
&scope=email%20profile
&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Flogin%2Foauth2%2Fcode%2Fgoogle
&flowName=GeneralOAuthFlow
with redirect uri parameter "{baseUrl}/login/oauth2/code/google" which is the default set by spring security when redirectUri is not set. If I switch to using application.yml with the below configuration:
spring:
security:
oauth2:
client:
registration:
google:
redirectUri: "{baseUrl}/oauth2/callback/{registrationId}"
it works fine. However, for various reasons I want to stick with the application.properties format. Any idea why the setting is ignored?
I want to add Azure AD as an OAuth2 provider in Spring Boot 2.4. I followed Spring Boot's OAuth2 docs and came up with the following configuration:
spring.security.oauth2.client.provider.azuread.issuer-uri=https://login.microsoftonline.com/<tenant uuid>/v2.0
spring.security.oauth2.client.registration.azuread.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.azuread.client-id=<client uuid>
spring.security.oauth2.client.registration.azuread.client-name=Azure AD
spring.security.oauth2.client.registration.azuread.client-secret=<client secret>
spring.security.oauth2.client.registration.azuread.provider=azuread
spring.security.oauth2.client.registration.azuread.scope=openid
Just for completeness, this is my web security configuration:
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.authorizeRequests(a -> a
.antMatchers("/", "/login", "/error", "/webjars/**").permitAll()
.anyRequest().authenticated()
)
.oauth2Login();
// #formatter:on
}
}
When coming back from entering the credentials on https://login.microsoftonline.com, I get the following error:
[invalid_id_token] An error occurred while attempting to decode the Jwt: Signed JWT rejected: Another algorithm expected, or no matching key(s) found.
The problem originates in DefaultJWTProcessor.java from Nimus-JOSE-JWT.
Looking through the requests in Firefox's network inspector, Spring Boot picks up the right URLs from the Issuer URI. I'm at a loss what's going wrong and appreciate any pointers.
Azure AD has some pretty unintuitive (in my opinion) default behaviour - I think this is what you are experiencing:
YOUR PROBLEM CAUSE (I THINK)
You are using standard OpenID Connect scopes
This causes Azure AD to issue an access token intended for the Graph API
This token fails standards based validation in Custom APIs since it is only designed for Graph APIs to use - it is recognisable by the nonce field in the JWT header
WHAT YOU NEED TO DO
Expose an API scope such as 'default'
Use the full value of this scope in your web client, with a value such as 'api://cb398b43-96e8-48e6-8e8e-b168d5816c0e/default', where the long identifier is that of the API
You will then get a normal OAuth token that Spring can validate - with no nonce field in the JWT header
FURTHER INFO
See steps 3 to 8 of my blog post from a couple of years ago
See the OpenID Connect settings of my web code sample
I had a similar problem and if I remember correctly there was an issue with scope. Don't know whether this is also your issue but in any case following configuration is working for me (note I'm using client credentials not auth code):
spring:
security:
oauth2:
client:
provider:
azure:
token-uri: https://login.microsoftonline.com/${custom.azure.account.tenant-id}/oauth2/token
registration:
azure:
client-id: ${custom.azure.service-principal.client-id}
client-secret: ${custom.azure.service-principal.client-secret}
authorization-grant-type: client_credentials
scope:
- https://graph.microsoft.com/.default
According to your description, it seems that you wanna use azure ad as the an OAuth2 provider in Spring Boot, and I found a doc said that 'Spring Starter for Azure Active Directory (AD) is now integrated with Spring Security 5.0'. It also offered a sample of springboot web app. I also tried it and it did work.
What I think need to note is, the tutorial said the redirect url setted in azure ad is 'http://localhost:8080/login/oauth2/code/', but when my tested, it proved to be 'http://localhost:8080/login/oauth2/code/azure'.
Upon your error, I'm not sure but maybe you can change your configuration of 'scope' to 'openid,profile,offline_access'.
I try implementing the following: I want a distributed environment with one or more spring cloud gateway(s), behind these are several micro services (partly) exposed to the outside. For user authentication I want to use OIDC (just moved to Keycloak).
I actually just sticked to the standard configuration from the reference documentations of spring security, webflux and boot.
In detail, I have in the gateway:
private final ReactiveClientRegistrationRepository clientRegistrationRepository;
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.csrf().disable()
.authorizeExchange()
.pathMatchers("/login").permitAll()
.pathMatchers("/actuator/**").permitAll()
.anyExchange().authenticated()
.and()
.oauth2Login()
.and()
.logout(logout -> logout.logoutSuccessHandler(oidcLogoutSuccessHandler()));
return http.build();
}
private ServerLogoutSuccessHandler oidcLogoutSuccessHandler() {
OidcClientInitiatedServerLogoutSuccessHandler oidcLogoutSuccessHandler =
new OidcClientInitiatedServerLogoutSuccessHandler(clientRegistrationRepository);
oidcLogoutSuccessHandler.setPostLogoutRedirectUri(URI.create("https://<host>/login?logout"));
return oidcLogoutSuccessHandler;
}
with application.yml (only essential part):
spring:
cloud:
gateway:
default-filters:
- TokenRelay=
- RemoveRequestHeader=Cookie
discovery:
locator:
enabled: true
consul:
host: 127.0.0.1
port: 8500
loadbalancer:
ribbon:
enabled: false
security:
oauth2:
client:
provider:
keycloak:
issuer-uri: ${KEYCLOAK_ISSUER_URI}
registration:
keycloak:
client-id: ${KEYCLOAK_CLIENT_ID}
client-secret: ${KEYCLOAK_CLIENT_SECRET}
resourceserver:
jwt:
jwk-set-uri: ${KEYCLOAK_ISSUER_URI}/protocol/openid-connect/certs
server:
forward-headers-strategy: framework
For the proof of concept I have an "environment-test-application" with a controller just returning the claims as a json. It is configured like:
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.pathMatchers("/actuator/**").permitAll()
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt(Customizer.withDefaults());
return http.build();
}
The application.yml only contains the consul and security part from above.
Basically this works. If I try to access the environment-test-application, I get redirected to Keycloak, I have to enter my credentials and get redirected back to the environment-test-application and see the claims. I can logout by means of "/logout".
The problem: After 5 minutes the keycloak session expires, I get a 401 and chrome says "this page is not working". This happens even, if I am reloading all the time (user activity).
To my understanding the session should:
be extended as long as there is activity (I thought this is what the refresh token is used for, and assumed spring security to handle this automatically)
expire when the user is inactive for some time. When the user is active again, he/she should be redirected to the login and then back to the original resource.
when hitting "remember me", the an expired session should be reinitiated without user activity (I think, this is what the offline token from Keycloak is meant for).
I guess all this is possible by adding just some simple lines of configuration, I just can't figure which. The reference documentation wasn't entirely clear to me, but I got the feeling this is all handled like so, by default. However, it is not.
Note: I am using spring reactive stack with the default Reactor Netty. Thus, neither the spring security, nor the spring boot plugin provided by Keycloak, can be used. Plus, it is not clear to me, how they interact with the standard configuration scheme.
Note: Before I was trying all this with okta (using their starter). The problem described above magically seemed to be working with okta. However, I had different issues and thus moved to open source now.
TokenRelayGatewayFilterFactory adds the access token but doesn't refresh it when it expires...which is why you get the 401 I believe. There's an open spring cloud gateway issue asking for a solution that refreshes as well. One of the comments on that issue provides an implementation: https://github.com/spring-cloud/spring-cloud-security/issues/175#issuecomment-557135243.
When the refresh token filter is working, the keycloak session only becomes important after your spring cloud gateway "session" expires because, if the keycloak session is still good, it allows the oauth2 redirect to re-establish the session seemlessly (i.e. without having to enter your credentials again).
If you're looking to customize the session of your webflux application, there are some solutions including this one: https://stackoverflow.com/a/62344617/1098564
Not sure what you're using for your frontend but he's how I use spring security with spring cloud gateway and react: https://sdoxsee.github.io/blog/2019/12/17/merry-microservices-part2-ui-gateway