我们正在使用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>
答案 0 :(得分:1)
您是否要接受第一个POST请求(因为您说该令牌旨在用于安全目的以及防止重复表单提交)?通常情况下,当您使用同步器令牌时,您希望接受来自上一个会话的POST,那么为什么不在用户登录时以明确定义的URL启动用户(Spring Security支持) ?
如果您真的想继续上一个事务,可以扩展Spring Security的AuthenticationProcessingFilter的onSuccessfulAuthentication方法并内省SavedRequest(存储在会话中)以确定先前的令牌值。然后,您可以使用此值初始化TransactionTokenBean,以便在后续请求中接受它。
Spring Security 3中的请求缓存代码更加灵活,因此如果您可以进行升级,那么这是可取的。
答案 1 :(得分:0)
为什么要使bean会话作用域?听起来更像是你想要一个永远存在的令牌 - 即使是通过新的登录。这听起来更像是没有超时的cookie的工作。