I have a web app that I am migrating to Grails 3.3.9 (from Grails 2.3.11). The current version of the application utilizes Spring Security LDAP for authentication (user sees login form when they try to access site and they type in username and password). The newer version of the application will utilize the Spring Security SAML plugin for authentication (commonly referred to as Single Sign On).
I have the Single Sign On (SSO) working however the SSO Login Page is only accessible when a user is at our office (has a certain IP address). In the cases where the user is not at our office (has an IP address not in our network). I would like the user to have the option to login with the Spring Security LDAP login form.
I'm sort of lost how to do it though. From the info I've gathered I need to define my security provider in application.groovy(I've used the default Spring Security providers as they seem to do the job individually). What I don't understand though is how do I tell Grails which of the two login methods to use per user. In my instance it would be checking the IP address of the user (which I already have the code for), but how do I then say, for example:
if(ipAddress matches internalIPRange) use samlAuthenticationProvider
else{use ldapAuthProvider}
Here is the provider set up in application.groovy
grails.plugin.springsecurity.providerNames = ['samlAuthenticationProvider', 'ldapAuthProvider', 'daoAuthenticationProvider', 'anonymousAuthenticationProvider']
Also I don't know how to actually call the provider manually (something like provider.invoke() if I had to guess).
It appears to be fairly easy to implement.
You extend the ExceptionTranslationFilter like so:
class IpAwareExceptionTranslationFilter extends ExceptionTranslationFilter {
AuthenticationEntryPoint ldapAuthenticationEntryPoint
#Override
void sendStartAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain,
AuthenticationException reason) throws ServletException, IOException {
SecurityContextHolder.context.authentication = null
requestCache.saveRequest request, response
logger.debug 'Calling Authentication entry point.'
if( isAllowedIpAddress( request ) )
authenticationEntryPoint.commence request, response, reason // default SAML
else
ldapAuthenticationEntryPoint.commence request, response, reason // LDAP
}
}
Then you declare your filter as a bean in resources.groovy:
beans = {
exceptionTranslationFilter( IpAwareExceptionTranslationFilter ){
authenticationEntryPoint = new LoginUrlAuthenticationEntryPoint( '/saml/login' )
ldapAuthenticationEntryPoint = new LoginUrlAuthenticationEntryPoint( '/ldap/login' )
}
}
and it should replace the default exceptionTranslationFilter in the filter chain.
Related
I'm working on an application with Spring Boot 5 and OIDC. I've configured OIDC with Google and it works fine. I'm redirected to login at Google and then it redirects me to the app creating a new session. Now I'm trying to use Google One-Tap. It works fine as well. When user clicks on the one-tap's modal to continue with his Google identity I receive a POST with the id_token. What I want to do is to create a Spring session from this POST as it is created when user is logged by OIDC.
I think the right way is to create a Filter extending AbstractAuthenticationProcessingFilter. I've found some references here and here but I don't have the access_token when receiving the one-tap POST:
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
...
OAuth2AuthenticationToken oauth2Authentication = new OAuth2AuthenticationToken(
authenticationResult.getPrincipal(),
authenticationResult.getAuthorities(),
authenticationResult.getClientRegistration().getRegistrationId());
oauth2Authentication.setDetails(authenticationDetails);
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
authenticationResult.getClientRegistration(),
oauth2Authentication.getName(),
authenticationResult.getAccessToken(),
authenticationResult.getRefreshToken());
this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, oauth2Authentication, request, response);
return oauth2Authentication;
}
Has anyone tried to do it before ?
I've finally found a solution. I've shared the code here. Hope it helps to anyone dealing with the same issue.
I'm trying to build a two factor authentication flow for shibboleth idp 3. It's set up with the MFA flow with an initial ldap authentication and then my 2FA flow, which is based on the external authn flow.
How can I get user data from the previous ldap flow in my servlet? It seems like request.getAttribute(ExternalAuthentication.PRINCIPAL_NAME_KEY) etc. is not set yet. The docs say that LDAP attributes are returned as part of the authentication process and exposed in the LDAPResponseContext. How can I access the context in my servlet?
I also tried to use an attribute-resolver to release a specific value from the AD user profile, but I was not able to find those values in my servlet. Any ideas?
I figured it out, maybe someone else finds it helpful:
The password flow populates the c14n context with the principal name, which is enough for me. To get the principal name in a servlet:
protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException {
try {
String authenticationKey = ExternalAuthentication.startExternalAuthentication(request);
// get userPrincipalName of previous authn
final ProfileRequestContext profileRequestContext = ExternalAuthentication.getProfileRequestContext(authenticationKey, request);
final SubjectCanonicalizationContext c14nContext = profileRequestContext.getSubcontext(SubjectCanonicalizationContext.class);
if (c14nContext != null && c14nContext.getPrincipalName() != null) {
usernameShib = c14nContext.getPrincipalName();
//Subject subjectShib = c14nContext.getSubject();
logger.info(usernameShib);
}
//...
}
I used Authentication providers in Spring Security. I have two Authentication Providers: LocalAuthenticationProvider and RemoteAuthenticationProvider. The authentication flow is that
the application checks credential at local db
if local authentication is passed, need to call RESTful Web service to authenticate
the credential.
if local authentication is failed, assume
authentication failed and response 401.
#Override
protected void configure(
AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(localProvider).authenticationProvider(remoteProvider);
}
LocalAuthenticationProvider throws AuthenticationException if credential is failed. If credentials is passed, it returns null.
My problem is Spring Security Framework calls RemoteAuthenticationProvider even LocalAuthenticationProvider is failed.
When I remove RemoteAuthenticationProvider from AuthenticationManagerBuilder provider list, it works even LocalAuthenticationProvider is failed.
I would like to know how can I achieve this Authentication flow. These two providers are depend on each other.
Looking at the Javadoc for ProviderManager it would seem that if your first provider was to throw an AccountStatusException then authentication would not proceed to the second provider:
http://docs.spring.io/autorepo/docs/spring-security/4.0.3.RELEASE/apidocs/org/springframework/security/authentication/ProviderManager.html
The exception to this process [providers being invoked in order until one passes] is when a provider throws an
AccountStatusException, in which case no further providers in the list
will be queried.
I've been trying to implement OAuth2 password expiration filter and I'm unsure about what the proper way would be to do so. The idea is as follows:
User tries to login.
User gets response with a header containing token if the password is expired.
User get's redirected to password change page using that token (i.e. /password-change/{token}).
He submits his old and new passwords, it gets changed.
Some rest controller retrieves user id by that token and does the rest password changing logic.
User should be redirected back to the initial login page where he logins with his new password (if he would be logged in instantly after the password change, he could navigate through secured pages even if the password would not be changed in background due to some exception, etc.).
So... I set a custom flag in user details for password expiration because I can't use credentialsNonExpired as it gets validated in DaoAuthenticationProvider and thrown as an exception which gets processed as InvalidGrantException which doesn't give me much control. I've figured out that in order to access user details right after it's authentication my filter should be in the inner Spring Security filter chain placed after OAuth2AuthenticationProcessingFilter:
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
...
http.addFilterAfter(new PasswordExpirationFilter(), BasicAuthenticationFilter.class
}
}
Why does my filter get placed after OAuth2AuthenticationProcessingFilter while there's no BasicAuthenticationFilter in the chain? I've digged through Spring Security and OAuth2 documentation and sources and couldn't find the right answer.
If that user's password is expired my filter generates some random string and it saves it to retrieve user details later during the password change request (at least it should be):
public class PasswordExpirationFilter extends OncePerRequestFilter implements Filter, InitializingBean {
private static final String TOKEN_HEADER = ...;
private ExpiredPasswordRepository repo; // gets set in a constructor and is basically holding a concurrent map of tokens
...
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
UserDetails details = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (details.isPasswordExpired) {
String uuid = UUID.randomUUID().toString();
repo.push(uuid, details.getId());
SecurityContextHolder.clearContext();
SecurityContextHolder.getContext().setAuthentication(null);
request.getSession(false).invalidate(); // don't create a new session
response.addHeader(TOKEN_HEADER, uuid);
response.sendError(HttpStatus.SC_PRECONDITION_FAILED, "Credentials have expired");
} else {
filterChain.doFilter(request, response);
}
}
}
Do I have to revoke the OAuth token as well? It gets reused in later requests and I keep getting the last userDetails object and therefore I keep getting the same response from my filter.
Is it even the right place to do all this validation? How should one validate the password for the concrete user and not the OAuth client?
Ok, I think I resolved this issue by revoking the access token via injected TokenStore in my filter (I used BearerTokenExtractor to get the token value) which seems pretty logical in this situtation. I still had no time to figure out, why my filter gets placed after OAuth2AuthenticationProcessingFilter, though.
I have a java webapp using Spring 3.1. My Spring security context defines multiple authentication filters, each corresponding to a different authentication path (e.g. username/password vs. Single Sign On). Each auth filter defines its own AuthenticationSuccessHandler. Now, I want to inject 2 additional actions to take upon successful authentication, and they should apply across all authentication types:
set a tracking event code for Google Analytics to use on the front-end
update the user's preferred locale in our database
These could be any actions that you want a hook for, after the user has been successfully authenticated. The important point is that, unlike the regular AuthenticationSuccessHandlers (which are different for each authentication path), they don't forward or redirect the request. So it's safe to call a bunch of them.
Is there a clean way to integrate these additional authentication success "actions", using Spring Web/Security 3.1?
I looked into implementing an ApplicationListener<AuthenticationSuccessEvent>, but my events need to access the request, and all AuthenticationSuccessEvent provides is the Authentication object itself.
I couldn't find a way, so I decided to roll my own proxy:
public class AuthenticationSuccessHandlerProxy implements AuthenticationSuccessHandler {
private List<AuthenticationSuccessHandler> authenticationSuccessHandlers;
public AuthenticationSuccessHandlerProxy(List<AuthenticationSuccessHandler> successHandlers) {
this.authenticationSuccessHandlers = successHandlers;
}
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
for (AuthenticationSuccessHandler successHandler : this.authenticationSuccessHandlers) {
successHandler.onAuthenticationSuccess(request, response, authentication);
}
}
}
After looking breafly into the source code of AbstractAuthenticationProcessingFilter and all other places where AuthenticationSuccessHandler.onAuthenticationSuccess(...) is called I do not see any possibility to do it using Spring Security.
As a workaround you can try to wrap your success handlers into some AspectJ or AOP pointcut and then apply this pointcut to AuthenticationSuccessHandler.onAuthenticationSuccess(...) execution. Maybe like this you can target all authentication types.