JSP使用<s:token/>
标签,在struts配置文件中增加token拦截器
页面代码:
<s:form action="reg" theme="simple">
username:<s:textfield name="username"></s:textfield><br />
password:<s:password name="password"></s:password><br />
<s:token></s:token>
<s:submit value="注册"></s:submit>
</s:form>
在struts.xml中配置:
<package name="test" namespace="/test" extends="struts-default">
<interceptors> //配置拦截器
<interceptor-stack name="tokenStack"> //命名拦截器栈,名字随便
<interceptor-ref name="token"/> //此拦截器为token拦截器,struts已经实现
<interceptor-ref name="defaultStack" /> //默认拦截器,注意顺序,默认拦截器放在最下面
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="tokenStack" /> //让该包中所有action都是用我们配置的拦截器栈,名字和上面的对应
</package>
流程说明:<br />
具体思路:<br />
一: 配置拦截器
<mvc:interceptors>
<!-- csrf攻击防御 -->
<mvc:interceptor>
<!-- 需拦截的地址 -->
<mvc:mapping path="/**" />
<!-- 需排除拦截的地址 -->
<mvc:exclude-mapping path="/resources/**" />
<bean class="com.cnpc.framework.interceptor.CSRFInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
二: 拦截器实现 CSRFInterceptor.java
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import com.alibaba.fastjson.JSONObject;
import com.cnpc.framework.annotation.RefreshCSRFToken;
import com.cnpc.framework.annotation.VerifyCSRFToken;
import com.cnpc.framework.base.pojo.ResultCode;
import com.cnpc.framework.constant.CodeConstant;
import com.cnpc.framework.utils.CSRFTokenUtil;
import com.cnpc.framework.utils.StrUtil;
/**
* CSRFInterceptor 防止跨站请求伪造拦截器
*/
public class CSRFInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("---------->" + request.getRequestURI());
System.out.println(request.getHeader("X-Requested-With"));
// 提交表单token 校验
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
VerifyCSRFToken verifyCSRFToken = method.getAnnotation(VerifyCSRFToken.class);
// 如果配置了校验csrf token校验,则校验
if (verifyCSRFToken != null) {
// 是否为Ajax标志
String xrq = request.getHeader("X-Requested-With");
// 非法的跨站请求校验
if (verifyCSRFToken.verify() && !verifyCSRFToken(request)) {
if (StrUtil.isEmpty(xrq)) {
// form表单提交,url get方式,刷新csrftoken并跳转提示页面
String csrftoken = CSRFTokenUtil.generate(request);
request.getSession().setAttribute("CSRFToken", csrftoken);
response.setContentType("application/json;charset=UTF-8");
PrintWriter out = response.getWriter();
out.print("非法请求");
response.flushBuffer();
return false;
} else {
// 刷新CSRFToken,返回错误码,用于ajax处理,可自定义
String csrftoken = CSRFTokenUtil.generate(request);
request.getSession().setAttribute("CSRFToken", csrftoken);
ResultCode rc = CodeConstant.CSRF_ERROR;
response.setContentType("application/json;charset=UTF-8");
PrintWriter out = response.getWriter();
out.print(JSONObject.toJSONString(rc));
response.flushBuffer();
return false;
}
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
// 第一次生成token
if (modelAndView != null) {
if (request.getSession(false) == null || StrUtil.isEmpty((String) request.getSession(false).getAttribute("CSRFToken"))) {
request.getSession().setAttribute("CSRFToken", CSRFTokenUtil.generate(request));
return;
}
}
// 刷新token
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
RefreshCSRFToken refreshAnnotation = method.getAnnotation(RefreshCSRFToken.class);
// 跳转到一个新页面 刷新token
String xrq = request.getHeader("X-Requested-With");
if (refreshAnnotation != null && refreshAnnotation.refresh() && StrUtil.isEmpty(xrq)) {
request.getSession().setAttribute("CSRFToken", CSRFTokenUtil.generate(request));
return;
}
// 校验成功 刷新token 可以防止重复提交
VerifyCSRFToken verifyAnnotation = method.getAnnotation(VerifyCSRFToken.class);
if (verifyAnnotation != null) {
if (verifyAnnotation.verify()) {
if (StrUtil.isEmpty(xrq)) {
request.getSession().setAttribute("CSRFToken", CSRFTokenUtil.generate(request));
} else {
Map<String, String> map = new HashMap<String, String>();
map.put("CSRFToken", CSRFTokenUtil.generate(request));
response.setContentType("application/json;charset=UTF-8");
OutputStream out = response.getOutputStream();
out.write((",'csrf':" + JSONObject.toJSONString(map) + "}").getBytes("UTF-8"));
}
}
}
}
/**
* 处理跨站请求伪造 针对需要登录后才能处理的请求,验证CSRFToken校验
*
* @param request
*/
protected boolean verifyCSRFToken(HttpServletRequest request) {
// 请求中的CSRFToken
String requstCSRFToken = request.getHeader("CSRFToken");
if (StrUtil.isEmpty(requstCSRFToken)) {
return false;
}
String sessionCSRFToken = (String) request.getSession().getAttribute("CSRFToken");
if (StrUtil.isEmpty(sessionCSRFToken)) {
return false;
}
return requstCSRFToken.equals(sessionCSRFToken);
}
}
一: 产生一个新的csrfToken,在用户第一次登陆的时候保存在session中
private String csrfToken = resetCSRFToken();
private String resetCSRFToken() {
String csrfToken = ESAPI.randomizer().getRandomString(8,DefaultEncoder.CHAR_ALPHANUMERICS);
return csrfToken;
}
二:对于所有需要保护的表单,增加一个隐藏的csrfToken字段。下面的addCSRFToken方法可以被所有需要保护的URL链接调用,如果是表单的话,可以增加一个字段,名字叫"ctoken",值为DefaultHTTPUtilities.getCSRFToken()。<br />
URL上:
<a href='<%=ESAPI.httpUtilities().addCSRFToken("/zhutougg/main?function=listUser")%>' >查询用户</a>
表单中:
<input type="hidden" name="ctoken" value="<%=ESAPI.DefaultHTTPUtilities.getCSRFToken() %>">
三: 在服务器端那些需要保护的方法执行之前,首先检查一下提交过来的token与session中的token是否一致,如果不一致,则被认为是一个伪造的请求(CSRF攻击)
@RequestMapping(value="/admin/login",method = RequestMethod.POST)
public String listUser(HttpServletRequest request, HttpServletResponse response,Model model){
ESAPI.httpUtilities().verifyCSRFToken(request);
......
}
四: 在用户退出或者session过期的时候 移除csrfToken
ESAPI.authenticator().getCurrentUser().logout();