开发者

Spring Security and the Synchronizer Token J2EE pattern, problem when authentication fails

开发者 https://www.devze.com 2022-12-28 18:31 出处:网络
we are using Spring Security 2.0.4. We have a TransactionTokenBean which generates a unique token each POST, the bean is session scoped. The token is used for the duplicate form submission problem (an

we are using Spring Security 2.0.4. We have a TransactionTokenBean which generates a unique token each POST, the bean is session scoped. The token is used for the duplicate form submission problem (and security). The TransactionTokenBean is called from a Servlet filter. Our problem is the following, after a session timeout occured, when you do a POST in the application Spring Security redirects to the logon page, saving the original request. After logging on again the TransactionTokenBean is created again, since it is session scoped, but then Spring forwards to the originally accessed url, also sending the token that was generated at that time. Since the TransactionTokenBean is created 开发者_JS百科again, the tokens do not match and our filter throws an Exception. I don't quite know how to handle this elegantly, (or for that matter, I can't even fix it with a hack), any ideas?

This is the code of the 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;
}

}

and this is the Servlet filter (ignore the Ajax stuff):

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

}

relevant part of 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>

the TransactionTokenBean:

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


Do you want to accept that first POST request or not (since you say the token is intended for security purposes as well as preventing duplicate form submission)? It wouldn't normally be the case that you would want to accept a POST from a previous session when you're using synchronizer tokens, so why not just start the user at a clearly defined URL when they log in (which Spring Security supports)?

If you really want to continue the previous transaction, you can extend Spring Security's AuthenticationProcessingFilter's onSuccessfulAuthentication method and introspect the SavedRequest (stored in the session) to determine the previous token value. You could then initialize your TransactionTokenBean with this value so that it would be accepted on the subsequent request.

The request caching code in Spring Security 3 is a lot more flexible, so if you can upgrade that would be advisable.


Why make the bean session scoped? Sounds more like you want a token that lives forever - even through a new login. This sounds more like a job for a cookie with no timeout.

0

精彩评论

暂无评论...
验证码 换一张
取 消