You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中在DispatcherServlet上下文外获取Locale的方法

在Spring认证失败处理器中正确获取CookieLocaleResolver设置的Locale

问题原因

Spring的DispatcherServlet会在处理请求时,通过配置的LocaleResolver(比如CookieLocaleResolver)解析请求中的Locale,并将其存入LocaleContextHolder(基于ThreadLocal)。但Spring Security的认证流程(包括UsernamePasswordAuthenticationFilter和AuthenticationFailureHandler)执行时机早于DispatcherServlet的Locale解析逻辑,导致此时LocaleContextHolder中只有浏览器默认的Locale,而非Cookie中存储的用户自定义Locale。

最优解决方案:提前通过过滤器设置Locale上下文

最通用的解决方式是编写一个自定义过滤器,在认证过滤器执行前就解析Locale并设置到LocaleContextHolder中,这样整个认证流程的所有组件都能通过LocaleContextHolder.getLocale()获取正确的Locale。

1. 自定义Locale上下文过滤器

public class PreAuthenticationLocaleFilter extends OncePerRequestFilter {

    private final LocaleResolver localeResolver;

    public PreAuthenticationLocaleFilter(LocaleResolver localeResolver) {
        this.localeResolver = localeResolver;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 从请求中解析用户自定义Locale
        Locale userLocale = localeResolver.resolveLocale(request);
        // 设置到LocaleContextHolder
        LocaleContextHolder.setLocale(userLocale);
        try {
            // 继续执行后续过滤器链
            filterChain.doFilter(request, response);
        } finally {
            // 清理ThreadLocal,避免内存泄漏
            LocaleContextHolder.resetLocaleContext();
        }
    }
}

2. 配置过滤器顺序

在Spring Security配置中,将这个自定义过滤器放在UsernamePasswordAuthenticationFilter之前执行:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final LocaleResolver localeResolver;

    public SecurityConfig(LocaleResolver localeResolver) {
        this.localeResolver = localeResolver;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            // 注入自定义过滤器,确保在认证过滤器前执行
            .addFilterBefore(new PreAuthenticationLocaleFilter(localeResolver), UsernamePasswordAuthenticationFilter.class)
            .formLogin(form -> form
                .failureHandler(customAuthenticationFailureHandler())
            );
        return http.build();
    }

    @Bean
    public CustomAuthenticationFailureHandler customAuthenticationFailureHandler(MessageSource messageSource) {
        return new CustomAuthenticationFailureHandler(messageSource);
    }
}

3. 修改认证失败处理器

此时无需再注入LocaleResolver,直接使用LocaleContextHolder.getLocale()即可拿到正确的Locale:

public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    private final MessageSource messageSource;

    public CustomAuthenticationFailureHandler(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    @Override
    public void onAuthenticationFailure(
            HttpServletRequest request,
            HttpServletResponse response,
            AuthenticationException exception) throws IOException, ServletException {

        Locale locale = LocaleContextHolder.getLocale();
        String errorMessage = messageSource.getMessage("login.error", null, locale);

        // 后续业务逻辑...
        super.onAuthenticationFailure(request, response, exception);
    }
}

备选方案:直接在处理器中注入LocaleResolver

如果不想新增过滤器,可以直接在认证失败处理器中注入LocaleResolver,手动解析Locale。这种方式适合仅在单个组件中需要Locale的场景:

public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    private final MessageSource messageSource;
    private final LocaleResolver localeResolver;

    public CustomAuthenticationFailureHandler(MessageSource messageSource, LocaleResolver localeResolver) {
        this.messageSource = messageSource;
        this.localeResolver = localeResolver;
    }

    @Override
    public void onAuthenticationFailure(
            HttpServletRequest request,
            HttpServletResponse response,
            AuthenticationException exception) throws IOException, ServletException {

        Locale locale = localeResolver.resolveLocale(request);
        String errorMessage = messageSource.getMessage("login.error", null, locale);

        // 后续业务逻辑...
        super.onAuthenticationFailure(request, response, exception);
    }
}

方案对比

  • 自定义过滤器方案:一次配置,整个认证流程通用,代码更简洁一致,符合Spring上下文管理的设计思路,推荐使用。
  • 直接注入LocaleResolver方案:实现简单,但仅适用于单个组件,若其他认证相关组件也需要Locale,会产生重复代码。

内容的提问来源于stack exchange,提问作者Slevin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 04:50:04