[keycloak-user] Spring Security Adapter: Set locale on redirect to login page + Link back to application from login form
Danny Trunk
dt at zyres.com
Thu Apr 13 05:38:13 EDT 2017
Hello everyone,
1. Set locale on redirect:
We have a multilingual application where you can choose your locale.
The login entry point then looks like
https://localhost:8443/en_US/login.html
Now I need to tell Keycloak which locale to use.
The way I realized it isn't really clean:
I'm extending from KeycloakWebSecurityConfigurerAdapter and overriding
the keycloakAuthenticationProcessingFilter method in order to
instantiate my own authentication processing filter implementation:
@Bean
@Override
protected KeycloakAuthenticationProcessingFilter
keycloakAuthenticationProcessingFilter() throws Exception {
RequestMatcher requestMatcher = new OrRequestMatcher(new
AntPathRequestMatcher("/*/login.html"), new
RequestHeaderRequestMatcher(KeycloakAuthenticationProcessingFilter.AUTHORIZATION_HEADER));
KeycloakAuthenticationProcessingFilter filter = new
LocaleAwareKeycloakAuthenticationProcessingFilter(keycloakAuthenticationManager(),
requestMatcher);
filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy());
return filter;
}
The custom authentication processing filter is the following:
public class LocaleAwareKeycloakAuthenticationProcessingFilter extends
KeycloakAuthenticationProcessingFilter implements ApplicationContextAware {
private final Logger log = LogManager.getLogger(getClass());
private ApplicationContext applicationContext;
private AdapterDeploymentContext adapterDeploymentContext;
private AdapterTokenStoreFactory adapterTokenStoreFactory = new
SpringSecurityAdapterTokenStoreFactory();
public
LocaleAwareKeycloakAuthenticationProcessingFilter(AuthenticationManager
authenticationManager, RequestMatcher
requiresAuthenticationRequestMatcher) {
super(authenticationManager, requiresAuthenticationRequestMatcher);
}
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
adapterDeploymentContext =
applicationContext.getBean(AdapterDeploymentContext.class);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest
request, HttpServletResponse response) {
log.debug("Attempting Keycloak authentication");
HttpFacade facade = new SimpleHttpFacade(request, response);
KeycloakDeployment deployment =
adapterDeploymentContext.resolveDeployment(facade);
AdapterTokenStore tokenStore =
adapterTokenStoreFactory.createAdapterTokenStore(deployment, request);
RequestAuthenticator authenticator = new
LocaleAwareRequestAuthenticator(facade, request, deployment, tokenStore,
-1);
AuthOutcome result = authenticator.authenticate();
log.debug("Auth outcome: {}", result);
if (AuthOutcome.FAILED.equals(result)) {
throw new KeycloakAuthenticationException("Auth outcome: "
+ result);
} else if (AuthOutcome.AUTHENTICATED.equals(result)) {
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
Assert.notNull(authentication, "Authentication
SecurityContextHolder was null");
return getAuthenticationManager().authenticate(authentication);
} else {
AuthChallenge challenge = authenticator.getChallenge();
if (challenge != null) {
challenge.challenge(facade);
}
return null;
}
}
@Override
public void setApplicationContext(ApplicationContext
applicationContext) {
super.setApplicationContext(applicationContext);
this.applicationContext = applicationContext;
}
@Override
public void setAdapterTokenStoreFactory(AdapterTokenStoreFactory
adapterTokenStoreFactory) {
super.setAdapterTokenStoreFactory(adapterTokenStoreFactory);
this.adapterTokenStoreFactory = adapterTokenStoreFactory;
}
}
The custom request authentication is the following:
public class LocaleAwareRequestAuthenticator extends
SpringSecurityRequestAuthenticator {
public LocaleAwareRequestAuthenticator(HttpFacade facade,
HttpServletRequest request, KeycloakDeployment deployment,
AdapterTokenStore tokenStore, int sslRedirectPort) {
super(facade, request, deployment, tokenStore, sslRedirectPort);
}
@Override
protected OAuthRequestAuthenticator createOAuthAuthenticator() {
return new LocaleAwareOAuthRequestAuthenticator(this, facade,
deployment, sslRedirectPort, tokenStore);
}
}
And finally the LocaleAwareOAuthRequestAuthenticator is the following:
public class LocaleAwareOAuthRequestAuthenticator extends
OAuthRequestAuthenticator {
public LocaleAwareOAuthRequestAuthenticator(RequestAuthenticator
requestAuthenticator, HttpFacade facade, KeycloakDeployment deployment,
int sslRedirectPort, AdapterSessionStore tokenStore) {
super(requestAuthenticator, facade, deployment,
sslRedirectPort, tokenStore);
}
@Override
protected String getRedirectUri(String state) {
String redirect = super.getRedirectUri(state);
if (redirect == null) {
return null;
}
// getting the locale from our relative path and appending to
the redirect uri
String url = facade.getRequest().getRelativePath();
return redirect + "&kc_locale=" +
ServletUtils.getLocaleFromURL(url).getLanguage();
}
}
As you can see I had to override many methods and had to duplicate much
code.
Is there really no other way to set the locale when redirecting to the
login page?
---
2. Link back to application
And another problem I had to fight with: We only use the login page from
Keycloak. All other stuff should happen in our application as there are
some processes we don't want to copy. As we use a custom user storage
provider which accesses the external db from our application this isn't
a problem.
I had to make some template in order to set the URLs to link to our
password reminder and registering pages.
In this case I'm using "${client.baseUrl}/${.locale}" as base URL to
link back to pwreminder.html and register.html.
As ${client.baseUrl} isn't a mandatory field in the Keycloak Admin
Console this isn't a clean way as well.
But there's no ${client.rootUrl} to access. So this is the only chance
to unsafely link back to our application.
Why the client root url isn't accessible in the templates? Any good
reason not to add it to the template data model?
---
If good solutions for those problems need to be implemented I'll take a
look at the code, opening issues and providing a pull requests on GitHub.
More information about the keycloak-user
mailing list