更改登录区域设置

13

我希望在Spring MVC应用程序(3.0)与Spring Security(3.0)中,登录后更改区域设置为存储在用户账户中的默认区域设置。

我已经使用了LocaleChangeInterceptor,因此可以让用户(无论是登录用户还是未登录用户)更改其区域设置(默认情况下从接受标头获取)。但是客户确实希望有特定于账户的默认值。

所以我的问题是,在登录后更改区域设置的最佳方法是什么,或者Spring / Security中是否已经内置了某些功能?


1
既然您已经有更改区域设置的机制,您可以创建一个自定义AuthenticationSuccessHandler来拦截登录并根据用户偏好更改区域设置。请查看这里这里获取更多信息。 - bluefoot
3个回答

9
我找到的最佳解决方案是在AuthenticationSuccessHandler中处理它。
以下是我为我的创业公司编写的一些代码:
public class LocaleSettingAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    @Resource
    private LocaleResolver localeResolver;

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

    protected void setLocale(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
        if (authentication != null) {
            Object principal = authentication.getPrincipal();
            if (principal instanceof LocaleProvider) {
                LocaleProvider localeProvider = (LocaleProvider) principal;
                Locale providedLocale = localeProvider.getLocale();
                localeResolver.setLocale(request, response, providedLocale);
            }
        }
    }
}

您的主要类应提供以下接口。虽然这不是必需的,但由于我有多个对象能够为会话提供语言环境,所以我正在使用它。

public interface LocaleProvider {    
    Locale getLocale();    
}

配置片段:

<security:http ...>
    <security:custom-filter ref="usernamePasswordAuthenticationFilter" position="FORM_LOGIN_FILTER"/>
</security:http>

<bean id="usernamePasswordAuthenticationFilter"
    class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
    <property name="filterProcessesUrl" value="/login/j_spring_security_check"/>
    <property name="authenticationManager" ref="authenticationManager"/>
    <property name="authenticationFailureHandler">
        <bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
            <property name="defaultFailureUrl" value="/login?login_error=t"/>
        </bean>
    </property>
    <property name="authenticationSuccessHandler">
        <bean class="LocaleSettingAuthenticationSuccessHandler">
    </property>
</bean>

谢谢,那个想法可行,我稍微修改了一下,使用了事件(与InteractiveAuthenticationSuccessEvent和AuthenticationSuccessEvent相比,它包含请求和响应对象)。 - Ralph
我已添加配置片段以确保完整性,以指示authenticationSuccessHandler的设置位置和方式。如果您不喜欢它,请从答案中删除该部分。很抱歉我编辑了您的答案,但是对于评论来说,这是太多的文本了。 - Ralph
似乎不再起作用了,localeResolver始终为空。 - robert trudel

2
使用SessionLocaleResolver,并将其构建为名为“localeResolver”的bean。该LocaleResolver将通过首先检查构建解析器时使用的默认语言环境来解析语言环境。如果为空,则将检查是否在会话中存储了区域设置,如果还是空,则根据请求中的Accept-Language标头设置会话区域设置。
完成用户登录后,您可以调用localeResolver.setLocale向会话存储语言环境,您可以在servlet过滤器中执行此操作(请确保在spring安全性过滤器之后在web.xml文件中定义它)。
要从过滤器中访问您的localeResolver(或其他bean),请在init方法中执行以下操作:
@Override
public void init(FilterConfig fc) throws ServletException {
    ServletContext servletContext = fc.getServletContext();
    ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);
    this.localeResolver = context.getBean(SessionLocaleResolver.class);
}

然后在doFilter方法中,您应该能够将ServletRequest强制转换为HttpServletRequest,调用getRemoteUser,执行任何业务逻辑来定义用户的区域设置,并调用LocaleResolver上的setLocale。

个人而言,我不喜欢SessionLocaleResolver首先使用默认语言环境(我更喜欢最后一个),但是它很容易扩展和覆盖。如果您有兴趣检查会话,然后是请求,最后是默认值,请使用以下内容:

import org.springframework.stereotype.Component;
import org.springframework.web.util.WebUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.Locale;

// The Spring SessionLocaleResolver loads the default locale prior
// to the requests locale, we want the reverse.
@Component("localeResolver")
public class SessionLocaleResolver extends org.springframework.web.servlet.i18n.SessionLocaleResolver{

    public SessionLocaleResolver(){
        //TODO: make this configurable
        this.setDefaultLocale(new Locale("en", "US"));
    }

    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        Locale locale = (Locale) WebUtils.getSessionAttribute(request, LOCALE_SESSION_ATTRIBUTE_NAME);
        if (locale == null) {
            locale = determineDefaultLocale(request);
        }
        return locale;
    }

    @Override
    protected Locale determineDefaultLocale(HttpServletRequest request) {
        Locale defaultLocale = request.getLocale();
        if (defaultLocale == null) {
            defaultLocale = getDefaultLocale();
        }
        return defaultLocale;
    }

}

关键问题不在于调用哪种方法来更改本地设置,而在于触发某个方法以在登录后进行操作(该方法具有访问登录详细信息(如用户名)和本地解析器以更改本地设置的权限)。 - Ralph
抱歉,但这并没有解决问题。它没有在登录后触发该过程。过滤器将在用户登录后的每个请求中更改用户的本地设置。这不是我要求的:我使用LocaleChangeInterceptor,这意味着用户可以独立于任何默认设置更改其登录,用户特定的本地设置仅是默认设置 - 用户必须能够通过该拦截器稍后更改它。 --- 无论如何,我已经实现了这样的解决方案(使用Spring Interceptor而不是ServletFilter),但这是一个hack。 - Ralph

0

我的当前解决方法是这样的(但仍然是一个hack,因为它不是由登录过程触发的):

我有一个Spring HandlerInterceptor拦截每个请求。 它总是检查用户会话中是否已经有一个标志(LOCALE_ALREADY_SET_SESSION_ATTRIBUTE),指示本地是否已经更新。 如果没有这样的标志,则拦截器检查请求是否属于已验证的用户。 如果是已验证的用户,则通过localResolver更新本地并在会话中设置标志(LOCALE_ALREADY_SET_SESSION_ATTRIBUTE

这个会话标志很重要,因为本地必须仅在登录后直接更改。所以后来用户可以通过正常的本地更改拦截器再次更改本地。

public class LocalChangeUserInterceptor extends HandlerInterceptorAdapter {

    /** Session key, used to mark if the local is set. */
    private static final String LOCALE_ALREADY_SET_SESSION_ATTRIBUTE = "LocalChangeUserInterceptor.localeAlreadySet";

    /** The locale resolver. */
    @Resource
    private LocaleResolver localeResolver;

    @Resource
    private UserService userService;

    @Override
    public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler)
            throws Exception {
        if (!isLocaleAlreadySet(request)) {
            User currentAuthenticatedUser = getCurrentUserOrNull();
            if (currentAuthenticatedUser != null) {
                this.localeResolver.setLocale(request, response, currentAuthenticatedUser.getLocale());
                request.getSession().setAttribute(LOCALE_ALREADY_SET_SESSION_ATTRIBUTE, "true");
            }
        }
        return true;
    }

    /**
     * Check if there is an session attribute that states that the local is already set once.
     * @param request the request
     * @return true, if is locale already set
     */
    private boolean isLocaleAlreadySet(final HttpServletRequest request) {
        HttpSession sessionOrNull = request.getSession(false);
        return ((sessionOrNull != null) && (sessionOrNull.getAttribute(LOCALE_ALREADY_SET_SESSION_ATTRIBUTE) != null));
    }

     /**
     * Get the current user or null if there is no current user.
     * @return the current user
     */
    public User getCurrentUserOrNull() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if ((authentication == null) || (authentication instanceof AnonymousAuthenticationToken)) {
            return null;
        } else {
            return this.userService.getUser(authentication);
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接