4

我正在开发使用 Spring (MVC)、Hibernate、Spring Security 和 ZK 作为前端的 Web 应用程序。我正在使用所有库的最新版本(3.1.2 Spring、3.1.3 Spring Security、4.1.7 Hibernate),但我遇到了国际化问题(i18n)。配置后我会详细介绍(仅相关部分):

网页.xml:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/spring/root-context.xml /WEB-INF/spring/spring-security.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/spring/appServlet/servlet-context.xml 
        </param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>appServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

<servlet>
    <servlet-name>zkLoader</servlet-name>
    <servlet-class>org.zkoss.zk.ui.http.DHtmlLayoutServlet</servlet-class>
    <init-param>
        <param-name>update-uri</param-name>
        <param-value>/zkau</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet>
    <servlet-name>auEngine</servlet-name>
    <servlet-class>org.zkoss.zk.au.http.DHtmlUpdateServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>zkLoader</servlet-name>
    <url-pattern>*.zul</url-pattern>
</servlet-mapping>

<servlet-mapping>
    <servlet-name>auEngine</servlet-name>
    <url-pattern>/zkau/*</url-pattern>
</servlet-mapping>

<listener>
    <description>ZK JSP Tags environment initiation </description>
    <display-name>ZK JSP Initiator</display-name>
    <listener-class>org.zkoss.jsp.spec.JspFactoryContextListener</listener-class>
</listener>

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class> org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

servlet 上下文:

<beans:bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
    <beans:property name="defaultLocale" value="hr" />
</beans:bean>

<interceptors>
    <beans:bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
        <beans:property name="paramName" value="lang" />
    </beans:bean>
</interceptors>

<beans:bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
    <beans:property name="basename" value="classpath:message" />
</beans:bean>

弹簧安全.xml:

<http pattern="/resources/**" security="none" />

<http auto-config="true">
    <intercept-url pattern="/login*" access="IS_AUTHENTICATED_ANONYMOUSLY" />
    <intercept-url pattern="/**" access="ROLE_USER" />
    <form-login login-page="/login" default-target-url="/" authentication-failure-url="/loginfailed" />
</http>

根上下文.xml:

与这里的问题无关,只是数据源定义、会话工厂和 bean 声明。

现在解决问题:

我有 2 个文件:message_en.properties 和 message_hr.properties,它们都驻留在 src/main/resources 目录中。我使用“Spring 模板项目”创建了这个项目,然后选择了“Spring MVC 项目”(使用 STS 2.9.2)。我已经阅读了有关如何自定义 Spring Security 消息的信息,并且那些需要被覆盖的消息放在 message.properties 文件中,并附加了自定义消息。我用于测试的一个是:

spring-security-core.jar 中的原始消息 AbstractUserDetailsAuthenticationProvider.badCredentials = Bad credentials

在 message_en.properties 中被覆盖:

AbstractUserDetailsAuthenticationProvider.badCredentials = 无效的用户名或密码

在 message_hr.properties 中重写:

AbstractUserDetailsAuthenticationProvider.badCredentials = Bla Bla Bla

场景一:

如果我在配置文件中保留上述所有内容,更改 url 栏中的 lang 参数或仅通过单击登录页面中的链接,正确读取我的自定义 message_xx.properties 中的所有消息,除了 SS 的消息。因此,我得到的不是“无效的用户名或密码”或“Bla Bla Bla”,而是“错误的凭据”。

场景二:

如果我将 messageSource bean 从 servlet-context.xml 移动到 spring-security.xml,它会加载正确的错误消息,但无论设置什么语言环境,它总是读取“Bla Bla Bla”。即使我更改 localeChangeInterceptor bean 的语言参数,也会发生这种情况。

我应该在这里做什么才能使这项工作正常进行?

忘了提:为了获得 Spring Security 消息,我在 jsp 页面中使用它

${sessionScope["SPRING_SECURITY_LAST_EXCEPTION"].message}

4

2 回答 2

4

Got the same problem. After reading this from Spring Security i18n I created a Filter which:

  1. retrieve the locale from the cookie (I'm using the CookieLocaleResolver but this can be done with other type of localeResolver like SessionLocaleResolver)
  2. and call the LocaleContextHolder.setLocale(locale, true)
  3. before the chain.doFilter.

This filter must appear before the filter-mapping of springSecurityFilterChain whithin web.xml. That way when spring-security calls LocaleContextHolder.getLocale() it retrieves the right one. In your case you can retrieve the locale from the session since you are using org.springframework.web.servlet.i18n.SessionLocaleResolver.

In spring context xml file (you can still use SessionLocaleResolver instead):

    <!-- Saves a locale change using a cookie -->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver" />

Java Filter code (important line is LocaleContextHolder.setLocale because spring-security makes use of LocaleContextHolder.getLocale):

package com.xyz;

import java.io.IOException;
import java.util.Locale;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.util.WebUtils;

/**
 * Transfert the value of user Locale into LocaleContextHolder which is used by spring-security
 */
public class FilterI18nCookie implements Filter {
    private static final Logger LOG = LoggerFactory.getLogger(FiltreI18nCookie.class);

    /**
     * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
     *      javax.servlet.ServletResponse, javax.servlet.FilterChain)
     */
    public void doFilter(ServletRequest pRequest, ServletResponse pResponse, FilterChain pFilterChain) throws IOException, ServletException {
        if (!(pRequest instanceof HttpServletRequest)) {
            pFilterChain.doFilter(pRequest, pResponse);
            return;
        }

        HttpServletRequest request = (HttpServletRequest) pRequest;

        Cookie cookie = WebUtils.getCookie(request, CookieLocaleResolver.LOCALE_REQUEST_ATTRIBUTE_NAME);
        if (cookie != null) {
            Locale locale = org.springframework.util.StringUtils.parseLocaleString(cookie.getValue());
            if (locale != null) {
                LOG.info("Locale cookie: [" + cookie.getValue() + "] == '" + locale + "'");
                request.setAttribute(CookieLocaleResolver.LOCALE_REQUEST_ATTRIBUTE_NAME, locale);
                LocaleContextHolder.setLocale(locale, true);
            }
        }

        pFilterChain.doFilter(pRequest, pResponse);
    }

    /**
     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
     */
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    /**
     * @see javax.servlet.Filter#destroy()
     */
    public void destroy() {
    }

}

Inside the web.xml file (the filter-mapping definition of custom filter FilterI18nCookie appreas before the springSecurityFilterChain):

<filter>
    <filter-name>FilterI18nCookie</filter-name>
    <filter-class>com.xyz.FilterI18nCookie</filter-class>
</filter>
    <filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<servlet>
    <servlet-name>mvc-dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>2</load-on-startup>
</servlet>
<filter-mapping>
    <filter-name>FilterI18nCookie</filter-name>
    <url-pattern>/ *</url-pattern>
</filter-mapping>
<filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/ *</url-pattern>
</filter-mapping>
<servlet-mapping>
    <servlet-name>mvc-dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>
于 2013-04-15T16:12:15.710 回答
0

Based on A.Masson suggestion, I made a change to support any type of LocalResolver implementation.

/**
 * Transfert the value of user Locale into LocaleContextHolder which is used by spring-security
 */
public class FilterI18nSpringSecurity implements Filter {
    private static final Logger LOG = LoggerFactory.getLogger(FilterI18nSpringSecurity.class);

    private WebApplicationContext springContext;

    /**
     * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
     *      javax.servlet.ServletResponse, javax.servlet.FilterChain)
     */
    public void doFilter(ServletRequest pRequest, ServletResponse pResponse, FilterChain pFilterChain) throws IOException, ServletException {
        if (!(pRequest instanceof HttpServletRequest)) {
            pFilterChain.doFilter(pRequest, pResponse);
            return;
        }

        LocaleResolver bean = springContext.getBean(LocaleResolver.class);
        Locale locale = bean.resolveLocale((HttpServletRequest) pRequest);
        LOG.info("Locale -> " + locale);
        LocaleContextHolder.setLocale(locale, true);

        pFilterChain.doFilter(pRequest, pResponse);
    }

    /**
     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
     */
    public void init(FilterConfig filterConfig) throws ServletException {
        springContext = WebApplicationContextUtils.getWebApplicationContext(filterConfig.getServletContext());
    }

    /**
     * @see javax.servlet.Filter#destroy()
     */
    public void destroy() {
    }

}

EDIT: Previous approach didn't works in some circumstances. I changed to this one, getting better behavior. I used the Spring exception class name as a message key, so then I can map these messages in my current property file. (my template engine is thymeleaf)

In the page:

<th:block th:if="${session.SPRING_SECURITY_LAST_EXCEPTION != null &amp;&amp; param.error != null}">
    <div th:replace="fragments/alert :: alert (type='danger', message=#{${session.SPRING_SECURITY_LAST_EXCEPTION.class.name}})">Alert</div>
</th:block>

messages.properties:

org.springframework.security.authentication.BadCredentialsException=Usuario o contraseña... org.springframework.security.authentication.LockedException=Usuario bloqueado org.springframework.security.authentication.DisabledException=El usuario está deshabilitado

于 2014-10-23T15:32:48.427 回答