Spring Security + Keycloak - How to handle Ajax Re

2019-07-25 13:01发布

问题:

I recently implemented Keycloak into my Primefaces Spring-Boot Web Application. It all works great except one thing: Ajax request "timeout" after whatever is set in keycloak under "Access Token Lifespan". There is no redirect. The ajaxStatus never ends and the user is forced to reload the page. Looking at the Request, I am getting back a 401 "Unauthorized".

E.g. the user loads a site and does whatever he wants to do. After 5 minutes of time without reloading or navigation to another page, ajax requests get unauthorized.

So my question is: What are the best practices in this situation and is this even the expected behavior?

I see following options:

  1. Increase the "Access Token Lifespan"
  2. Intercept the ajax call and reload page if user isnt authorized anymore -> user looses possible input he made on the site
  3. Refresh Access Token lifespan on every ajax call within its current lifespan -> Is that even possible?

Here is my SpringSecurity and Keycloak configuration:

@Configuration
@KeycloakConfiguration
@EnableWebSecurity
@EnableGlobalMethodSecurity(proxyTargetClass = true)
public class SpringSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {


    private KeycloakClientRequestFactory keycloakClientRequestFactory;

    @Inject
    public void setKeycloakClientRequestFactory(KeycloakClientRequestFactory keycloakClientRequestFactory) {
        this.keycloakClientRequestFactory = keycloakClientRequestFactory;
    }

    @Inject
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        SimpleAuthorityMapper grantedAuthorityMapper = new SimpleAuthorityMapper();
        grantedAuthorityMapper.setPrefix("ROLE_");
        grantedAuthorityMapper.setConvertToUpperCase(true);
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(grantedAuthorityMapper);
        auth.authenticationProvider(keycloakAuthenticationProvider);
    }

    @Bean
    public KeycloakConfigResolver KeycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }

    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(
                new SessionRegistryImpl());
    }

    @Bean
    public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean(
            KeycloakAuthenticationProcessingFilter filter) {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
        registrationBean.setEnabled(true);
        return registrationBean;
    }

    @Bean
    public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean(
            KeycloakPreAuthActionsFilter filter) {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
        registrationBean.setEnabled(true);
        return registrationBean;
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http
                .authorizeRequests()
                    .antMatchers("/resources/**").permitAll()
                    .antMatchers("/javax.faces.resource/**").permitAll()
                    .antMatchers("/pages/accessdenied*").permitAll()
                    .antMatchers("/pages/admin/**").hasRole("ADMIN")
                    .antMatchers("/pages/shared/**").permitAll()
                    .antMatchers("/pages/**").hasRole("ADMIN")
                    .anyRequest().authenticated()
                .and()
                    .csrf().disable();
    }


}

Keycloak settings

keycloak.enabled=true
keycloak.auth-server-url=http://host:8090/auth
keycloak.realm="realm"
keycloak.public-client=true
keycloak.resource="client-name"
keycloak.principal-attribute=preferred_username

回答1:

As the Spring Security Adapter uses web sessions, I would recommend you two steps (you'll need to implement at least the second one):

1. Increase the KC session timeouts for the realm:

This tells how much the session is going to last for an access (takes care of the refresh token lifespan). This values are relative to how much you want to let a user in without having to authenticate himself again.

Increasing Access Token Lifespan is not recommended since an attacker who could intercept one token would have a wider time window to perform valid actions.

2. In your view side, control 401 exceptions

Even you've got a long lasting session, 401 exceptions can still happen. So you need to control them in the view side. Using AngularJs, for example, a very basic way to redirect the user to the login page in case a 401 code happens might be something like this (put in some interceptor):

function responseError(response) {
    console.log('error: ' + JSON.stringify(response));
    if (!(response.status === 401)) {
        $rootScope.$emit('httpError', response);
    } else {
        //401 error
        window.location.href = window.location.protocol + '//'
                + window.location.host + '/sso/logout';
    }
    return $q.reject(response);
}

For Primefaces, your best might be implementing some logic in a server filter which sends a redirect to /SSO/logout if your business logic sends a 401 response.