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

Spring如何为登录登出页面设置动态语言前缀URL?

动态语言前缀下Spring Security登录/登出路径配置问题

问题场景

应用采用动态语言前缀作为路径变量,示例URL如下:

  • /en/user/login.html
  • /de/user/login.html
  • 模板路径:/{lang}/user/login.html

当前通过WebSecurityConfigurerAdapter配置时,loginPage("/{lang}/user/login.html")这类路径会被当作静态字符串解析,无法实现动态匹配语言前缀的效果。

现有配置代码

public static class UserSecurityConfiguration extends WebSecurityConfigurerAdapter {

        ...

        @Override
        public void configure(HttpSecurity http) throws Exception {
            ...

            http.anonymous().principal("user-guest").authorities("GUEST").and()
                .regexMatcher("\\/(en|de)\\/user\\/.*")
                .authorizeRequests()
                .regexMatchers("\\/(en|de)\\/user\\/app-assets\\/.*").permitAll()
                .anyRequest().hasRole("ADMIN")
                .and().formLogin().loginPage("/{lang}/user/login.html")
                .defaultSuccessUrl("/{lang}/user/login.html", true).permitAll()
                .and().logout().logoutUrl("/{lang}/user/logout").logoutSuccessUrl("/{lang}/user/login.html")
                .and().exceptionHandling().accessDeniedPage("/{lang}/user/login.html")
                .and().rememberMe().rememberMeParameter("remember-me-admin").key("appAdminSecret")
                .userDetailsService(adminDetailsService()).tokenValiditySeconds(86400);
            http.headers().frameOptions().sameOrigin();
            http.csrf().disable();

        }
}

解决方案

由于Spring Security的loginPage、logoutSuccessUrl等方法不直接支持路径变量,需要通过自定义处理器来实现动态路径跳转:

1. 自定义AuthenticationEntryPoint处理未授权跳转

从请求URL中提取语言前缀,重定向到对应语言的登录页:

public class DynamicLangAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        String lang = extractLangFromUri(request.getRequestURI());
        // 设置默认语言为en
        lang = lang == null ? "en" : lang;
        response.sendRedirect("/" + lang + "/user/login.html");
    }

    private String extractLangFromUri(String uri) {
        Pattern pattern = Pattern.compile("^/(en|de)/.*");
        Matcher matcher = pattern.matcher(uri);
        return matcher.find() ? matcher.group(1) : null;
    }
}

2. 自定义LogoutSuccessHandler处理登出跳转

同样提取语言前缀,跳转至对应语言的登录页:

public class DynamicLangLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        String lang = extractLangFromUri(request.getRequestURI());
        lang = lang == null ? "en" : lang;
        response.sendRedirect("/" + lang + "/user/login.html");
    }

    private String extractLangFromUri(String uri) {
        Pattern pattern = Pattern.compile("^/(en|de)/.*");
        Matcher matcher = pattern.matcher(uri);
        return matcher.find() ? matcher.group(1) : null;
    }
}

3. 自定义AccessDeniedHandler处理权限不足跳转

实现权限不足时的动态语言路径跳转:

public class DynamicLangAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        String lang = extractLangFromUri(request.getRequestURI());
        lang = lang == null ? "en" : lang;
        response.sendRedirect("/" + lang + "/user/login.html");
    }

    private String extractLangFromUri(String uri) {
        Pattern pattern = Pattern.compile("^/(en|de)/.*");
        Matcher matcher = pattern.matcher(uri);
        return matcher.find() ? matcher.group(1) : null;
    }
}

4. 更新Security配置

替换原配置中的静态路径设置,使用自定义处理器:

@Override
public void configure(HttpSecurity http) throws Exception {
    http.anonymous().principal("user-guest").authorities("GUEST").and()
        .regexMatcher("\\/(en|de)\\/user\\/.*")
        .authorizeRequests()
        .regexMatchers("\\/(en|de)\\/user\\/app-assets\\/.*").permitAll()
        .anyRequest().hasRole("ADMIN")
        .and().formLogin()
        // 登录请求处理路径,支持路径变量
        .loginProcessingUrl("/{lang}/user/login")
        .defaultSuccessUrl("/{lang}/user/dashboard", true).permitAll()
        .and().logout()
        .logoutUrl("/{lang}/user/logout")
        .logoutSuccessHandler(new DynamicLangLogoutSuccessHandler())
        .and().exceptionHandling()
        .authenticationEntryPoint(new DynamicLangAuthenticationEntryPoint())
        .accessDeniedHandler(new DynamicLangAccessDeniedHandler())
        .and().rememberMe().rememberMeParameter("remember-me-admin").key("appAdminSecret")
        .userDetailsService(adminDetailsService()).tokenValiditySeconds(86400);
    http.headers().frameOptions().sameOrigin();
    http.csrf().disable();
}

注意事项

  • 登录表单的提交地址需对应loginProcessingUrl,可在页面中动态生成:action="${pageContext.request.contextPath}/${lang}/user/login"
  • 若新增支持的语言,需同步更新正则表达式中的语言匹配规则(如(en|de)改为(en|de|fr))
  • 语言前缀提取逻辑可根据实际URL结构调整,比如语言参数在请求参数中时,改用request.getParameter("lang")获取

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 22:57:22