Spring Security Saml forceAuthn specific requests - spring

Is there a way in spring security Saml to set forceAuthn=true based on request parameters.
https://stackoverflow.com/a/27833355/2587871 override the value based on the request ?
Basically as a Service provide we work with both Oauth and SAML idp. So when the client send prompt=login we want to honor it in SAML side too.

I was able to implement this using the following option.
Extend SAMLEntryPoint
Override getProfileOptions
WebSSOProfileOptions webProfile = super.getProfileOptions(context, exception);
if(prompt=login){
webProfile.setForceAuthN(true);
}
return webProfile;

Related

Spring Authorization Server: How to use login form hosted on a separate application?

I am using Spring Security along with Spring Authorization Server and experimenting with creating an auth server.
I have a basic flow allowing me to login with the pre-built login page (from a baledung guide - this is the code I'm working off ). I'm assuming this login page form comes from formLogin() like so:
http.authorizeRequests(authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
)
//.formLogin(withDefaults());
return http.build();
I would like to not use this pre-built form as I have a need to host and run the login form front-end application completely separately. ie on a different server, domain and codebase.
Another way to ask this question could be:
How do I disable the built in form in authorization-server so I can use it with a completely separate form?
Are there any recommended ways of learning about how customise my SecurityFilterChain along these lines? Is this the correct place to look? I find the baledung article (and articles like that) helpful as a starting point, but seldom works for more practical use case. I'm confident Spring Security and the oauth2 libraries will allow me to do what I want, but not entirely clear.
After discussing this with you, I've gathered that what you're trying to do is essentially pre-authenticate the user that was authenticated through another (separately hosted) login page, actually a separate system. The idea is that the other system would redirect back with a signed JWT in a query parameter.
This really becomes more of a federated login problem at that point, which is what SAML 2.0 and OAuth 2.0 are aimed at solving. However, if you have to stick with things like a signed JWT (similar to a SAML assertion), we could model a fairly simple pre-authenticated authorization_code flow using the Spring Authorization Server.
Note: I haven't explored options for JWT Profile for OAuth 2.0 Client Authentication and Authorization Grants but it could be a viable alternative. See this issue (#59).
Additional note: There are numerous security considerations involved with the approach outlined below. What follows is a sketch of the approach. Additional considerations include CSRF protection, using Form Post Response Mode (similar to SAML 2.0) to protect the access token instead of a query parameter, aggressively expiring the access token (2 minutes or less), and others. In other words, using a federated login approach like SAML 2.0 or OAuth 2.0 will always be RECOMMENDED over this approach when possible.
You could to start with the existing Spring Authorization Server sample and evolve it from there.
Here's a variation that redirects to an external authentication provider and includes a pre-authentication mechanism on the redirect back:
#Bean
#Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
// #formatter:off
http
.exceptionHandling(exceptionHandling -> exceptionHandling
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("https://some-other-sso.example/login"))
);
// #formatter:on
return http.build();
}
#Bean
#Order(2)
public SecurityFilterChain standardSecurityFilterChain(HttpSecurity http) throws Exception {
// #formatter:off
http
.authorizeRequests(authorize -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
// #formatter:on
return http.build();
}
#Bean
public JwtDecoder jwtDecoder(PublicKey publicKey) {
return NimbusJwtDecoder.withPublicKey((RSAPublicKey) publicKey).build();
}
#Bean
public BearerTokenResolver bearerTokenResolver() {
DefaultBearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver();
bearerTokenResolver.setAllowUriQueryParameter(true);
return bearerTokenResolver;
}
The first filter chain operates on authorization server endpoints, such as /oauth2/authorize, /oauth2/token, etc. Note that the /oauth2/authorize endpoint requires an authenticated user to function, meaning that if the endpoint is invoked, the user has to be authenticated, or else the authentication entry point is invoked, which redirects to the external provider. Also note that there must be a trusted relationship between the two parties, since we're not using OAuth for the external SSO.
When a redirect from the oauth client comes to the /oauth2/authorize?... endpoint, the request is cached by Spring Security so it can be replayed later (see controller below).
The second filter chain authenticates a user with a signed JWT. It also includes a customized BearerTokenResolver which reads the JWT from a query parameter in the URL (?access_token=...).
The PublicKey injected into the JwtDecoder would be from the external SSO provider, so you can plug that in however it makes sense to in your setup.
We can create a stub authentication endpoint that converts the signed JWT into an authenticated session on the authorization server, like this:
#Controller
public class SsoController {
private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
#GetMapping("/login")
public void login(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws ServletException, IOException {
this.successHandler.onAuthenticationSuccess(request, response, authentication);
}
}
The .oauth2ResourceServer() DSL causes the user to be authenticated when the /login endpoint is invoked. It requires an access_token parameter (used by the BearerTokenResolver) to pre-authenticate the user by validating the signed JWT as an assertion that the user has been externally authenticated. At this point, a session is created that will authenticate all future requests by this browser.
The controller is then invoked, and simply redirects back to the real authorization endpoint using the SavedRequestAwareAuthenticationSuccessHandler, which will happily initiate the authorization_code flow.
Re your comnent: "I'm attempting to build an Authorization Server":
Coding your own Authorization Server (AS) or having to build its code yourself is highly inadvisable, since it is easy to get bogged down in plumbing or to make security mistakes.
By all means use Spring OAuth Security in your apps though. It is hard enough to get these working as desired, without taking on extra work.
SUGGESTED APPROACH
Choose a free AS and run it as a Docker Container, then connect to its endpoints from your apps.
If you need to customize logins, use a plugin model, write a small amount of code, then deploy a JAR file or two to the Docker container.
This will get you up and running very quickly. Also, since Spring Security is standards based, you are free to change your mind about providers, and defer decisions on the final one.
EXAMPLE IMPLEMENTATION
Curity, along with other good choices like Keycloak or Ory Hydra are Java based and support plugins:
Curity Community Edition
Custom Authenticator Example

Spring Boot Security Oauth2 - adding dynamic OIDC parameters

How do I add OIDC token request parameters dynamically in my app code? I want to add domain_hint based on some data received by my controller from as yet un-authenticated user.
You can implement custom OAuth2AuthorizationRequestResolver
and then add to your spring security configuration
.oauth2Login(req->
req.authorizationEndpoint()
.authorizationRequestResolver(new YourCustomAuthorizationRequestResolver)
)

Issuer URI of Spring Security OAuth2 Authorization Server

I'm developing OAuth 2.0 authorization server and resource server using Spring Security OAuth 2.0 2.3.4.REELEASE. Little did I know that Spring has deprecated it in favor of Spring Security. However, authorization server is not included in the migration to Spring Security. Only the resource server is included as they are encouraging users to use products instead (one is KeyCloak).
But like many others, I really have to develop my own authorization server so I keep on using Spring Security OAuth 2.0 but only for the authorization server. As for resource server, I'll be using the resource server from Spring Security. I think authorization and resource servers are independent and they are based on the standards of OAuth 2.0 so the implementation could be from different frameworks.
My problem is specifying the authorization server via issuer-uri. I could not determine how Spring OAuth 2.0 authorization server exposes its issuer-uri if there is any. I could not find either any docs how to create one if it does not have one be default.
Please help. Thank you.
Since Spring Security Oauth has ended support you should use the Spring Authorization Server now (https://spring.io/projects/spring-authorization-server). For the isser uri there you can use the base uri of the server. To verify this you can check the well-known-Endpoint under /.well-known/openid-configuration.
The isser uri in the authorization server can be edited with the ProviderSettings:
#Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder()
.issuer("https://authorization-server:8443")
.build();
}
I believe the solution you are looking for lies within the TokenEnhancer. So, implement a custom token enhancer and add the iss key like so.
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("iss", "issuer uri here");
...
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}

Retrieve Entire SAML Response in Spring Security SAML Extension

I have a Spring Boot application that is setup as a Service Provider. My end goal is to be able to call the AWS STS Assume Role with SAML service to generate AWS temporary credentials on behalf of the user with the SAML response used to initially authenticate users of my application.
I found this other question. With that answer I am able to get only the assertion, not the entire response. From my testing, the AWS API call linked above wants the entire response, not just the assertion piece.
I used this Chrome Extension to view the SAML response. When I include everything (outline below)
<samlp:Response>
...
<saml:Assertion>
...
</saml:Assertion>
</samlp:Response>
The AWS STS Assume Role with SAML works. The other related question's answer only provides me the
<saml:Assertion>...</saml:Assertion>
block and the AWS STS Assume Role with SAML fails.
So my question is how do I get the entire SAML Response XML object back in a controller of my Spring Boot application?
I don't know any direct way in spring-security-saml, but maybe you could try to implement your own SAMLProcessingFilter ie simply extending the existing one and overriding the method attemptAuthentication().
Principle:
In this method, you have access to the response returned from the IdP and post back to the SP (at least in a Redirect-POST profile)
You probably have a way to extract what you need from the httpRequest
Then you can store (session, ThreadLocal variable, ...)
And finally you delegate the authentication process to the parent (by calling super.attemptAuthentication())
`
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if ("POST".equalsIgnoreCase(request.getMethod())) {
String samlResponse = request.getParameter("SAMLResponse");
System.out.println("Original SAML Response (base64 decoded) : " + new
String(Base64.getDecoder().decode(samlResponse), StandardCharsets.UTF_8));
}
return super.attemptAuthentication(request, response);
}
`

Spring 4.x token-based WebSocket SockJS fallback authentication [duplicate]

This question already has answers here:
JSON Web Token (JWT) with Spring based SockJS / STOMP Web Socket
(5 answers)
Closed 6 years ago.
Getting Spring Boot 1.4 + Spring 4.x + Spring Security 4.x WebSocket authentication to work with stateless token-based authentication seems to be an adventure!
So far, as I understand it, SockJS is not able to set the Authentication header with the token because browser's do not expose that API to Javascript (see https://github.com/sockjs/sockjs-client/issues/196). I have worked around that by setting the authentication token in a query parameter as suggested on the above issue, and then using a Spring HandshakeHandler with determineUser() to map the query parameter to a User entity. Ugly and less secure, but at least it works for WebSockets.
However, when SockJS falls back to another mechanism e.g. XHR streaming, the same mechanism no longer works. A HandshakeInterceptor has access to the request and can obtain the authentication from the query param, but determineUser on the HandshakeHandler is never called for non-WebSocket handshakes.
The closest I have gotten so far is to bypass the built-in connection-level Spring machinery to determine the authentication. Instead, I set the authentication token at the message-level by setting it in the Stomp headers on the client side e.g.:
stompClient.send("/wherever", {token: 'token'}, ...);
and extract it on the server-side with a channel interceptor:
configureClientInboundChannel(ChannelRegistration registration) {
registration.setInterceptors(new ChannelInterceptorAdapter() {
Message<*> preSend(Message<*> message, MessageChannel channel) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
// not documented anywhere but necessary otherwise NPE in StompSubProtocolHandler!
accessor.setLeaveMutable(true);
List tokenList = accessor.getNativeHeader("token");
if(tokenList == null || tokenList.size < 1) {
return message;
}
Principal yourAuth = [...];
return MessageBuilder.createMessage(message.payload, accessor.messageHeaders)
}
})
Now Spring injects the Principal into any controller methods that require it, BUT the user is still not saved to the websocket session, so messages still cannot be sent to a particular user.
How do I get Spring to "see" the authentication extracted from the query parameter?
Use the Stomp Headers
Bypass the built-in connection-level Spring machinery to determine the authentication. Instead, set the authentication token at the message-level by setting it in the Stomp headers on the client side. See the approach I outlined here:
https://stackoverflow.com/a/39456274/430128

Resources