-
Notifications
You must be signed in to change notification settings - Fork 6.1k
Description
Context
In my project, I am supporting multiple ways of logging in:
- "internal" user store (in-memory or in-database),
- OAuth2 login
- SAML login
These three ways of authenticating yield different Authentication objects: UsernamePasswordAuthenticationToken
, OAuth2[Login]AuthenticationToken
and Saml2Authentication
.
I have custom rules for extracting information from the authentications (e.g. scopes, SAML attributes, etc).
I use the authentication results through AbstractAuthenticationEvent
(e.g. log some specific information when authentication fails), and through the Security context (e.g. to display values mapped from the authentication to the user).
To minimize code duplication and switch-case when using those authentications, I rely on a custom interface, e.g. CustomAuthentication
.
I register custom AuthenticationProvider
s which extend from (or delegate to) spring-security basic authentication providers. Those providers obtain the base Authentication object (through calling super.authenticate()
, for example), perform custom logic, and return a subclass of the base Authentication.
For example:
class CustomSamlAuthentication extends Saml2Authentication implements CustomAuthentication {
[...]
}
public class CustomSamlAuthenticationProvider implements AuthenticationProvider {
private final OpenSamlAuthenticationProvider delegate;
public CustomSamlAuthenticationProvider() {
this.delegate = new OpenSamlAuthenticationProvider();
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Authentication authenticationResult = this.delegate.authenticate(authentication);
return new CustomSamlAuthentication((Saml2AuthenticationToken) authentication, (Saml2Authentication) authenticationResult);
}
@Override
public boolean supports(Class<?> authentication) {
return delegate.supports(authentication);
}
}
Alternatives would be to transform the authentication when we consume it, but that could be in multiple places. Also, some of the authentication information may be lost or more difficult to obtain by that time (e.g. which relying party registration was used to obtain this authentication?)
Expected Behavior
I want to get this result simply by providing two things:
- A custom
Authentication
implementation - A custom
AuthenticationProvider
This works very well with UserDetails and SAML, but it is not possible to accomplish this with OAuth2.
Current Behavior
To accomplish this with OAuth2, I need:
- A custom
OAuth2LoginAuthenticationToken
+ customOAuth2LoginAuthenticationProvider
and/orOidcAuthorizationCodeAuthenticationProvider
- This is required for the authentication events to have the information I need
- A custom
OAuth2LoginToken
- A custom
OAuth2LoginAuthenticationFilter
to override the behavior of the base class
See this sample project for a full implementation of the three use-cases.
Ideas
Two rough ideas:
- Updating the
OAuth2LoginAuthenticationFilter
to support a custom converter, fromOAuth2LoginAuthenticationToken
toOAuth2AuthenticationToken
- I could configure the login filter with "just" this converter. It is a bit involved but I don't have to track changes in the filter implementation over time
- Update the
OAuth2LoginAuthenticationProvider
andOidcAuthorizationCodeAutheProvider
to take aOAuth2LoginAuthenticationToken
and return a straightOAuth2AuthenticationToken
as the authentication result ... but this probably has very wide implications.
I have not looked into reactive support for this.