Is it possible to change destination endpoint in Spring SAML? Default value is /saml/SSO
I need to change that to /sso.
I have edited
<bean id="samlFilter" class="org.springframework.security.web.FilterChainProxy">
<security:filter-chain pattern="/sso" filters="samlWebSSOProcessingFilter"/>
But it does not work. URL*/sso* is treated as URL which requires authentication, not the one that should recieve SAML assertion.
I am using Identity provider initialized SSO, so that URL is the one that recieves base64 encoded XML document (HTTP post request).
If there is no other option, I will do server-side redirect from /sso to saml/SSO, but it would be better to have ability to customize that URL in Spring Saml configuration.
filterProcessesUrl property of samlWebSSOProcessingFilter is correct answer and works fine.
In my case, filterProcessesUrl must be set to "/sso".
It was not working for me, because I was setting it to full endpoint URL (scheme://server:port/contextPath/sso) rather that just path.
As stated in documentation, a few other properties must be set to same value (samlFilter filter, and service provider metadata).
Finally, SAML assertions (XML document from Identity Provider) must contain same URLs as configured in Service Provider.
Related
I'm trying to enable multi-tenancy for a previously single-user system. The application used to run on a local server and had a relatively simple frontend baked in.
Now I want to allow multiple users to simultaneously use it in a cloud environment. I went ahead and implemented Auth2 with OIDC and PKCE to redirect users to an external Auth Provider. What I want now is that for every request, the user sends his Access token with the request in order for me to decide what data to provide with the answer.
I could not figure out how to obtain that data, as it seems that the spring framework (by default) only sends the ID token with the request. I suspect the fact that my software would simultaneously be the client and the resource server has something to do with it.
This is my first question, so I'm very happy to modify or extend my question if I've forgotten anything.
What I've tried to far:
I've used Postman to verify that the three tokens, ID token, refresh token and access token are issued correctly and can be retrieved with my credentials.
I tried getting the access token from the request itself. Any parameters (like #AuthenticationPrincipal OidcUser oidcUser) in the controller that include the token, however, are only showing the ID token and not the access token.
Getting the token via the OAuth2AuthorizedClientService does not work either, same problem, as I can only get the ID token, but not the access token.
Update #1, 13.12.2022/11:40: I am using PingOne by PingIdentity as the authentication provider and the following dependencies are or might be related or helpful to this matter:
spring-boot-starter-web
spring-boot-starter-security
spring-boot-starter-thymeleaf
spring-boot-starter-web-services
spring-boot-starter-oauth2-resource-server
Split your security in two with http.securityMatcher in the first picked SecurityFilterChain bean to restrict the endpoints it applies to (use #Order to control that), and configure WebClient to add access-token as Authorization header to its requests to resource-server (REST endpoints). This is demoed in the tutorial I already linked in my comment to your question. This tutorial matches exactly what you are asking, the fact that it uses Keycloak instead of PingOne as authorization server is a detail (solved by editing a property or two).
Resource-server security filter-chain
As reminder, a resource-server is a REST API secured with OAuth2. All end-points of #RestController and #Controller with #ResponseBody should be secured with a SecurityFilterChain containing http.oauth2ResourceServer(). The easiest way to configure a resource-server with Spring is using spring-boot-starter-oauth2-resource-server (or one of the very thin wrappers I wrote around it which enable to configure it from properties with 0 java conf)
By default with such filter-chains, Spring populates the security-context with a sub-class of AbstractOAuth2TokenAuthenticationToken<?>. You can retrieve the access-token from it. At least 2 options to access this Authentication instance:
SecurityContextHolder.getContext().getAuthentication()
have Spring auto-magically inject AbstractOAuth2TokenAuthenticationToken<?> auth as controller method parameter
You can also have the original authorization header injected as controller method parameter with #RequestHeader(value = HttpHeaders.AUTHORIZATION) String authorizationHeader.
I expose various ways to configure resource-servers security filter-chain in this tutorials.
Client security filter-chain
End-points of #Controller returning a template name and secured with OAuth2 are clients. The easiest way to configure a Spring client is with spring-boot-starter-oauth2-client and http.oauth2Login().
Note that in this configuration, the request between the browser and the Spring client is not OAuth2 (it is most frequently secured with a session cookie, not a Bearer access-token in Authorization header). Only requests sent by the Spring client (on the server) to resource-servers are secured with OAuth2.
With client security filter-chain, security-context is populated with OAuth2AuthenticationToken which, on purpose, exposes ID-token and not access-token. As reminder, ID tokens are holding user identity data and are intended to be used by clients when access-tokens audience is resource-servers and is designed for access-control. Clients should consider access-tokens as black box and use it only to authorize their requests to resource-servers (set Bearer Authorization header).
You can get the access-token string from OAuth2AuthorizedClient: authorizedClient.getAccessToken().getTokenValue(), which is itself retrieved from the OAuth2AuthorizedClientService you can auto-wire in your controllers: authorizedClientService.loadAuthorizedClient("your-client-id", auth.getName()) (auth being the OAuth2AuthenticationToken instance retrieved from security-context via SecurityContextHolder or controller method parameter injection)
If you need to authorize a WebClient request from client to resource-server, you can do simpler than retrieve access token and position authorization header: do as in the UiController of the tutorial already linked in my comment to your question:
final var authorizedClient = authorizedClientService.loadAuthorizedClient("spring-addons-public", auth.getName());
final var response = webClient.get().uri("http://localhost:8080/api/greet")
.attributes(ServerOAuth2AuthorizedClientExchangeFilterFunction.oauth2AuthorizedClient(authorizedClient))
...
With WebClient configured as follow:
#Bean
WebClient webClient(ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientService authorizedClientService) {
var oauth = new ServletOAuth2AuthorizedClientExchangeFilterFunction(
new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository,
authorizedClientService));
oauth.setDefaultClientRegistrationId("spring-addons-public");
return WebClient.builder().apply(oauth.oauth2Configuration()).build();
}
Thanks to those who tried to help me, but eventually I figured it out myself.
I extended my Controllers by two attributes: OAuth2AuthenticationToken authentication and HttpServletRequest request.
Also, I #Autowired in the OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository.
This then allows the following call returning the value of the accessToken:
(oAuth2AuthorizedClientRepository.loadAuthorizedClient(myClientRegistrationId, authentication, request)).client.getAccessToken().getTokenValue();.
After that, it's just parsing the token and retrieving the values using JWTParser.parse() and provided methods from the JWT-result.
Personal note: Don't do the parsing and retrieving value parts in your controller to keep any more complex logic out of it.
I hope this helps somebody!
We currently have several Spring Boot applications connecting to other services using a service account. Till now we used the AccessTokenRequest of the OAuth2ClientContext on a RestTemplate to put the user and password of the service account in and used the returned OAuth token to connect to the other services.
Right now we're building a new application using Spring Boot 5.2 and as the new Spring Security OAuth should be used now the separate OAuth library has become deprecated, we would also like to replace the RestTemplate solution with a WebClient one as the RestTemplate will become deprecated in the near future as well. I've tried several approaches of retrieving the token, but couldn't find a solution that works.
I found set-ups like the one mentioned on Spring Security 5 Replacement for OAuth2RestTemplate, but no way of putting a username and password inside the WebClient.
I found other approaches using a ClientRegistrationRepository instead of a ReactiveClientRegistrationRepository and some of those approaches actually have options (like How to re-initialize password grant in Spring security 5.2 OAuth) of putting a username and password in an AuthorizedClientManager that gets to be a parameter when instantiating the filter, but somehow I always end up with the message that no Bean of the ClientRegistrationRepository could be found, no matter what properties I put in the application.yaml (maybe this doesn't work because the application is an MVC application instead of a WebFlux one?)
I know that I need to set the authorization-grant-type to be 'password', but there's already someone else asking how to get that working (Spring Security 5.2.1 + spring-security-oauth2 + WebClient: how to use password grant-type) and no answers to that question, yet.
So ... did they 'deprecate' this easy way of using a username and password to retrieve a token and use that token to connect to another service in Spring Security 5.2? And if yes, what should be used now?
Yes, RestTemplate and OAuthRestTemplate are all deprecated. WebClient supports OAuth out of the box. You don't need to do anything special or add any headers yourself. It's actually extremely trivial.
Create a configuration class that exposes a WebClient bean, make sure you take the client repository as a param. In the method, you pass the repo into a filter function:
#Bean
public WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations) {
return WebClient.builder().filter(filterFunction(clientRegistrations))
.baseUrl(String.format("http://%s:8080", getHostName()))
.build();
}
private ServerOAuth2AuthorizedClientExchangeFilterFunction filterFunction(ReactiveClientRegistrationRepository clientRegistrations) {
ServerOAuth2AuthorizedClientExchangeFilterFunction filterFunction = new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
filterFunction.setDefaultClientRegistrationId("myKey");
return filterFunction;
}
NOTE: In the filter function, replace the "myKey" with something that matches the following property structure in application.properties (replace myKey in the property paths with your name):
spring.security.oauth2.client.registration.myKey.authorization-grant-type=password
spring.security.oauth2.client.registration.myKey.client-id=xxx
spring.security.oauth2.client.registration.myKey.client-secret=xxx
spring.security.oauth2.client.provider.myKey.token-uri=http://localhost:8080/oauth/token
Aaaannddd.... you're done! OAuth token refresh is also built in.
Well, it turned out to be a little bit different. A comment on the last SO question I linked requested the author to use debugging in the PasswordOAuth2AuthorizedClientProvider to see what went on / wrong. So I started debugging as well and with the setup you provided there are 4 Providers for 4 login types that are supplied, but out of the 4 it's not the PasswordReactiveOAuth2AuthorizedProvider that's used, and also not the ClientCredentialsReactiveOAuth2AuthorizedProvider or RefreshTokenReactiveOAuth2AuthorizedProvider one, but the AuthorizationCodeReactiveOAuth2AuthorizedProvider is called and that's quite weird when authorization-grant-type is set to password. That seems like a bug to me ...
Anyway, I found another SO question from rigon with a problem a bit different than mine, but close enough: Create route in Spring Cloud Gateway with OAuth2 Resource Owner Password grant type and he provided code I could get working as the Provider used with that codebase is in fact the PasswordReactiveOAuth2AuthorizedClientProvider
In the end I only needed to enter 3 items in the yaml-file: the client-id (with the client_id that used to be put as attribute on the AccessTokenRequest), the authorization-grant-type and the token-uri
Besides that I copied the WebClient and ReactiveOAuth2AuthorizedClientManager setup from the code provided by rigon and put the username and password from the configuration file into the settings (I left the seperate contextAttributesMapper out as I only needed to provide 2 context parameters directly into a map).
I know that we can use Spring security to restrict access to a controller by IP address or even I can create my custom annotation to add some custom logic for this purpose.
I need to restrict access to a controller in my application to only specific domain name ( a third party ), I checked with this third party provider for the IP ranges which I can use to configure at my end, however they want to have more freedom around IP address and would like me to create access based on the domain name.
I checked through the doc but unable to find any such use cases, Can any one help me to understand if this is possible or I need to go back to IP based access mechanism ?
i think you can use Spring's CORS support for this.
ie if the domain that you expect the request is example.com you can have in your controller method the following annotation
#CrossOrigin(origins = "http://example.com")
This #CrossOrigin annotation enables cross-origin requests only for
this specific method. By default, its allows all origins, all headers,
the HTTP methods specified in the #RequestMapping annotation and a
maxAge of 30 minutes is used. You can customize this behavior by
specifying the value of one of the annotation attributes: origins,
methods, allowedHeaders, exposedHeaders, allowCredentials or maxAge.
In this example, we only allow http://localhost:8080 to send
cross-origin requests.
Have a look in the following url https://spring.io/guides/gs/rest-service-cors/
I am using Spring Security to protect my endpoints.
My problem is, is it possible to response differently when users are using valid/invalid access token?
For example, for a single /api/info
(1) When an invalid/expired access token is passed in the request, only very limited information will be returned
(2) When a valid access token is passed in the request, very customized and rich content is returned according to a different user.
I've tried to use access=permitAll(), but it doesn't work because invalid tokens can not pass oauth2 validation.
Using security="none" is also not working because it will not try to get user info at all.
Write a custom OAuth2AccessDeniedHandler and plug it to your /info endpoint using security:access-denied-handler XML tag or Similar Java Config.
I have implemented Spring Security Expression in my application Spring controller:
#Controller
#RequestMapping("init")
public class InitController {
#PreAuthorize("hasRole('ROLE_ADMIN')")
#RequestMapping(value = "/", method = RequestMethod.GET)
public #ResponseBody String home(){
return "This is the init page";
}
}
With this security configuration:
<http auto-config="true" create-session="stateless" use-expressions="true">
<intercept-url pattern="/_ah*" access="permitAll" />
<intercept-url pattern="/init/*" access="hasRole('ROLE_ADMIN')"/>
<intercept-url pattern="/init*" access="hasRole('ROLE_ADMIN')"/>
</http>
When this resource is accessed the the default Spring login form is displayed (http://localhost:8888/spring_security_login) however I don't want this to happen and that I just want to have the credentials to be inserted in the request header like "x-authorization-key" or whatever that fits the scenario.
What is the possible solution for this?
Is it a good to just have the x-authorization-key to be in the request
If so, how does it fit with the Spring security mechanism, that is how that it fit with the "hasRole" expression
It is important the the my web service is stateless, and each request gets authenticated
Finally, how to do deal with Spring security without having to deal with the Spring login form
header
You should probably read the description on what auto-config does, then remove it to disable form-login. Your configuration will be clearer if you specifically configure what you want to use.
It's not clear from your question what you want to be included in the x-authorization-key header. If you are just authenticating with a client Id and shared secret then you might as well use basic authentication since it is already supported out of the box and you can just add <http-basic /> to your configuration. If you have something more customized in mind, then you will probably have to implement a custom filter and add it to the Spring Security filter chain to extract the credentials and process them.
How your authentication mechanism fits is also dependent on what it actually consists of. Normally your users will have assigned roles which are loaded when they authenticate, usually from a database of some kind. The hasRole expression simply checks whether the current user has the specified role. Often you will only need to create a UserDetailsService which loads your user information in a standard format which is easily plugged into the framework. This is covered at length elsewhere. If you really need something more customized this blog article on GAE integration includes details of how you might go about integrating with a more complicated system.
Spring Security will not create or use a session if you use create-session='stateless'.
P.S. You don't really need to include the same security attributes both at the URL level and on your controller which handles the same URL.