Skip to content
Advertisement

Spring save locale and change locale when user logs in

I have a spring application that I want users to be able to change the preferred locale. Currently users can change the locale for the current session but I want to be able to save the users option so that whenever they log in, the saved locale is used if one exists. I have a mysql database which I’m using to store the user locale preference. I have created a custom AuthenticationSuccessHandler to handle changing the locale to the saved locale, which works for a user where I have already saved the locale to the database. However, the bit I don’t know how to do is save the locale when the option is changed. The code is as follows:

   /**
     * Creates and gets the LocaleResolver
     * @return LocaleResolver
     */
    @Bean
    public LocaleResolver localeResolver() {
        SessionLocaleResolver slr = new SessionLocaleResolver();
        slr.setDefaultLocale(Locale.getDefault());
        logger.debug("Setting locale to: " + Locale.getDefault());
        return slr;
    }

    /**
     * Creates and gets the LocaleChangeInterceptor
     * @return LocaleChangeInterceptor
     */
    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
        lci.setParamName("lang");
        logger.debug("localeChangeInterceptor called "  + Locale.getDefault());
        return lci;
    }

SecurityConfig class:

@Configuration("SecurityConfig")
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
class SecurityConfig extends WebSecurityConfigurerAdapter 
{

    @Autowired
    @Qualifier("CustomAuthenticationSuccessHandler")
    private CustomAuthenticationSuccessHandler authenticationSuccessHandler;


    @Override
    protected void configure(HttpSecurity http) throws Exception {

        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        filter.setEncoding(StandardCharsets.UTF_8.name());
        filter.setForceEncoding(true);
        http.addFilterBefore(filter,CsrfFilter.class);
        // configure authentication providers
        http.authenticationProvider( customAuthenticationProvider() );
        http.authenticationProvider( authenticationProvider() );

        http.authorizeRequests()
            .antMatchers("/").permitAll()
            .antMatchers("/?lang=**").permitAll()
            .antMatchers("/login**").permitAll()
            .antMatchers("/about**").permitAll()
            .antMatchers("/index.html").permitAll()
            .antMatchers("/css/**").permitAll()
            .antMatchers("/js/**").permitAll()
            .antMatchers("/img/**").permitAll()
            .antMatchers("/fonts/**").permitAll()
            .antMatchers("/errors/**").permitAll()
            .antMatchers("/error/**").permitAll()
            .antMatchers("/webjars/**").permitAll()
            .antMatchers("/static/**").permitAll()
            .antMatchers("/information/**").permitAll() 
            .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
            .anyRequest().authenticated()
            .and()
                .formLogin()
                .loginPage("/login")
                .defaultSuccessUrl("/home")
                .failureUrl("/login?failedlogin=true")
                .usernameParameter("username").passwordParameter("password")
                .successHandler(authenticationSuccessHandler)
                .failureHandler(authenticationFailureHandler)
                .permitAll()

            .and()
                .logout()
                .deleteCookies("JSESSIONID")
                .addLogoutHandler(getCustomLogoutSuccessHandler())
                .invalidateHttpSession(true) 
                .permitAll()

            .and()
                .csrf().disable()
                .exceptionHandling()
            .and()
                    .rememberMe().rememberMeParameter("remember-me").tokenRepository(tokenRepository)
                    .tokenValiditySeconds(86400)
                ;
    }

    ....
}

AuthenticationSuccessHandler class

@Component("CustomAuthenticationSuccessHandler")
public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    /** Logger */
    private static final Logger logger = LogManager.getLogger(CustomAuthenticationSuccessHandler.class);

    @Autowired
    private LocaleResolver localeResolver;

    @Autowired
    @Qualifier("UserService")
    private UserService userService;


    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
            throws IOException, ServletException {
        logger.debug("CustomAuthenticationSuccessHandler.onAuthenticationSuccess called");
        setLocale(authentication, request, response);
        super.onAuthenticationSuccess(request, response, authentication);
    }

    protected void setLocale(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
        logger.debug("CustomAuthenticationSuccessHandler.setLocale called");
        if (authentication != null &&authentication.getPrincipal() != null) {
            String username = (String) authentication.getPrincipal();

            if (username != null) {

                String localeOption = userService.getUsersPreferedLocaleOption(username);
                logger.debug("localeOption " + localeOption);
                if (localeOption != null && !localeOption.isEmpty()) {
                    Locale userLocale = Locale.forLanguageTag(localeOption);
                    localeResolver.setLocale(request, response, userLocale);

                }

            }
        }
    }

}

Part of html page that shows the language change options:

<form id="change-language-form" name="change-language-form" class="change-language-form ng-pristine ng-valid" method="POST">
            <div class="row">
                <div class="text-right col-sm-offset-8 col-sm-4">
                    <select id="language-selector" name="language-selector" class="language-selector">
                        <option value="">Language </option>
                        <option value="?lang=en_GB">English </option>
                        <option value="?lang=zh">中文 </option>
                        <option value="?lang=de_DE">Deutsch </option>
                        <option value="?lang=es_ES">Español </option>
                        <option value="?lang=fr_FR">Français </option>
                    </select>
                </div>
            </div>
            <div class="row">
               <div class="spacer-sml"></div>
            </div>
        </form>

Javascript that changes the language option:

$('#language-selector').change(function() {

    var languageSelectedUrl = $(this).find("option:selected").val();      
    if (languageSelectedUrl) {
        window.location = languageSelectedUrl;
    }
});

Advertisement

Answer

You can implement LocaleResolver interface to bind users Locale to database. Sample implementation “ApplicationLocaleResolver.java” is below

import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import net.app.locale.service.UserService;

@Configuration
public class ApplicationLocaleResolver extends SessionLocaleResolver {
    @Autowired
    UserService userService;

    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        SecurityContext securityContext = SecurityContextHolder.getContext();
        String userName = securityContext.getAuthentication().getName();
        String localeOption = userService.getUsersPreferedLocaleOption(userName);
        Locale userLocale = Locale.forLanguageTag(localeOption);
        return userLocale;
    }

    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
        super.setLocale(request, response, locale);

        SecurityContext securityContext = SecurityContextHolder.getContext();
        String userName = securityContext.getAuthentication().getName();
        userService.saveUsersPreferedLocaleOption(userName, locale);
    }
}

I assume your userService has a method that save users local to db. I prefer userService.saveUsersPreferedLocaleOption(userName, locale); You can change it.

Then you can replace localeResolver bean definition as below.

@Bean(name = "localeResolver")
public LocaleResolver localeResolver() {
    return new ApplicationLocaleResolver();
}
Advertisement