Spring save locale and change locale when user logs in
Asked Answered
H

2

12

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;
    }
});
Hydrogenize answered 12/1, 2018 at 15:22 Comment(0)
W
15

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();
}
Watermelon answered 15/1, 2018 at 12:25 Comment(3)
Thanks @MehmetSunkur I had to make a few mods to the code you provided like null checks etc and update if no value exists but otherwise that has solved my issue.Hydrogenize
Thanks a lot it helped me. You can also get user locale option from user details without using a service for that (user details can be fetched from database while authenticating and are available anywhere in the application).Fredra
It's not working for me. Because the method "resolveLocale()" invoked only View implementers. While i'm only create web service, so that method is never executedLinnealinnean
L
0

@Mehmet Sunkur answer is great. But in my case, the method "resolveLocale()" is never invoked, because of I don't implements view area. So I must override different method. below is the additional method.

    @Configuration
    public class ApplicationLocaleResolver extends SessionLocaleResolver {

    ...
    @Override
    protected Locale determineDefaultLocale(HttpServletRequest request) {
        if (getDefaultLocale() == null) {
            SecurityContext securityContext = SecurityContextHolder.getContext();
            String userName = securityContext.getAuthentication().getName();
            String languageTag = userService.getUsersPreferredLocaleOption(userName);
            Locale locale = Locale.forLanguageTag(languageTag);
            // set default locale
            setDefaultLocale(locale);
            // set session values
            WebUtils.setSessionAttribute(request, LOCALE_SESSION_ATTRIBUTE_NAME, locale);
        }

        return getDefaultLocale();
    }
    ...
Linnealinnean answered 15/7, 2022 at 8:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.