Spring Security和Synchronizer Token J2EE模式,身份验证失败时出现问题

时间:2010-04-26 17:28:30

标签: java java-ee spring-security

我们正在使用Spring Security 2.0.4。我们有一个TransactionTokenBean,它为每个POST生成一个唯一的令牌,bean是会话作用域。令牌用于重复表单提交问题(和安全性)。从Servlet过滤器调用TransactionTokenBean。 我们的问题如下,在会话超时发生后,当您在应用程序中执行POST时,Spring Security会重定向到登录页面,从而保存原始请求。再次登录后,将再次创建TransactionTokenBean,因为它是会话作用域,但随后Spring转发到最初访问的URL,同时发送当时生成的令牌。由于再次创建了TransactionTokenBean,因此令牌不匹配,我们的过滤器会抛出异常。我不太清楚如何优雅地处理这个问题(或者就此而言,我甚至无法用黑客攻击它),任何想法?

这是TransactionTokenBean的代码:

public class TransactionTokenBean implements Serializable {

public static final int TOKEN_LENGTH = 8;

private RandomizerBean randomizer;

private transient Logger logger;

private String expectedToken;

public String getUniqueToken() {
    return expectedToken;
}

public void init() {
    resetUniqueToken();
}

public final void verifyAndResetUniqueToken(String actualToken) {
    verifyUniqueToken(actualToken);
    resetUniqueToken();
}

public void resetUniqueToken() {
    expectedToken = randomizer.getRandomString(TOKEN_LENGTH, RandomizerBean.ALPHANUMERICS);
    getLogger().debug("reset token to: " + expectedToken);
}

public void verifyUniqueToken(String actualToken) {
    if (getLogger().isDebugEnabled()) {
        getLogger().debug("verifying token.  expected=" + expectedToken + ", actual=" + actualToken);
    }

    if (expectedToken == null || actualToken == null || !isValidToken(actualToken)) {
        throw new IllegalArgumentException("missing or invalid transaction token");
    }

    if (!expectedToken.equals(actualToken)) {
        throw new InvalidTokenException();
    }
}

private boolean isValidToken(String actualToken) {
    return StringUtils.isAlphanumeric(actualToken);
}

public void setRandomizer(RandomizerBean randomizer) {
    this.randomizer = randomizer;
}

private Logger getLogger() {
    if (logger == null) {
        logger = Logger.getLogger(TransactionTokenBean.class);
    }
    return logger;
}

}

这是Servlet过滤器(忽略Ajax的东西):

public class SecurityFilter implements Filter {

static final String AJAX_TOKEN_PARAM = "ATXTOKEN";
static final String TOKEN_PARAM = "TXTOKEN";

private WebApplicationContext webApplicationContext;

private Logger logger = Logger.getLogger(SecurityFilter.class);

public void init(FilterConfig config) {
    setWebApplicationContext(WebApplicationContextUtils.getWebApplicationContext(config.getServletContext()));
}

public void destroy() {
}

public void doFilter(ServletRequest req, ServletResponse response, FilterChain chain) throws IOException,
        ServletException {

    HttpServletRequest request = (HttpServletRequest) req;


    if (isPostRequest(request)) {
        if (isAjaxRequest(request)) {
            log("verifying token for AJAX request " + request.getRequestURI());
            getTransactionTokenBean(true).verifyUniqueToken(request.getParameter(AJAX_TOKEN_PARAM));
        } else {
            log("verifying and resetting token for non-AJAX request " + request.getRequestURI());
            getTransactionTokenBean(false).verifyAndResetUniqueToken(request.getParameter(TOKEN_PARAM));
        }
    }

    chain.doFilter(request, response);
}

private void log(String line) {
    if (logger.isDebugEnabled()) {
        logger.debug(line);
    }
}

private boolean isPostRequest(HttpServletRequest request) {
    return "POST".equals(request.getMethod().toUpperCase());
}

private boolean isAjaxRequest(HttpServletRequest request) {
    return request.getParameter("AJAXREQUEST") != null;
}

private TransactionTokenBean getTransactionTokenBean(boolean ajax) {
    return (TransactionTokenBean) webApplicationContext.getBean(ajax ? "ajaxTransactionTokenBean"
            : "transactionTokenBean");
}

void setWebApplicationContext(WebApplicationContext context) {
    this.webApplicationContext = context;
}

}

web.xml的相关部分:

<filter>
    <filter-name>SecurityFilter</filter-name>
    <filter-class>
        xxx.common.web.security.SecurityFilter
    </filter-class>
</filter>

<filter-mapping>
    <filter-name>SecurityFilter</filter-name>
    <servlet-name>SpringServlet</servlet-name>
    <dispatcher>REQUEST</dispatcher>
</filter-mapping>

<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>

TransactionTokenBean:

<bean id="transactionTokenBean" class="xxx.common.web.bean.support.TransactionTokenBean"
    init-method="init" scope="session">
    <property name="randomizer" ref="randomizer" />
</bean>

2 个答案:

答案 0 :(得分:1)

您是否要接受第一个POST请求(因为您说该令牌旨在用于安全目的以及防止重复表单提交)?通常情况下,当您使用同步器令牌时,您希望接受来自上一个会话的POST,那么为什么不在用户登录时以明确定义的URL启动用户(Spring Security支持) ?

如果您真的想继续上一个事务,可以扩展Spring Security的AuthenticationProcessingFilter的onSuccessfulAuthentication方法并内省SavedRequest(存储在会话中)以确定先前的令牌值。然后,您可以使用此值初始化TransactionTokenBean,以便在后续请求中接受它。

Spring Security 3中的请求缓存代码更加灵活,因此如果您可以进行升级,那么这是可取的。

答案 1 :(得分:0)

为什么要使bean会话作用域?听起来更像是你想要一个永远存在的令牌 - 即使是通过新的登录。这听起来更像是没有超时的cookie的工作。