CSRF TOKEN

package com.uncle5.pubrub.web.common;

import java.util.UUID;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

public final class CsrfTokenManager {

// 隐藏域参数名称
static final String CSRF_PARAM_NAME = "CSRFToken";

// session中csrfToken参数名称
public static final String CSRF_TOKEN_FOR_SESSION_ATTR_NAME = CsrfTokenManager.class
.getName() + ".tokenval";

private CsrfTokenManager() {
};

// 在session中创建csrfToken
public static String createTokenForSession(HttpSession session) {
String token = null;

synchronized (session) {
token = (String) session
.getAttribute(CSRF_TOKEN_FOR_SESSION_ATTR_NAME);
if (null == token) {
token = UUID.randomUUID().toString();
session.setAttribute(CSRF_TOKEN_FOR_SESSION_ATTR_NAME, token);
}
}
return token;
}

public static String getTokenFromRequest(HttpServletRequest request) {
return request.getParameter(CSRF_PARAM_NAME);
}
}




package com.uncle5.pubrub.web.common;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.support.RequestDataValueProcessor;

import com.google.common.collect.Maps;

@Component("requestDataValueProcessor")
public class CsrfRequestDataValueProcessor implements RequestDataValueProcessor {

@Override
public String processAction(HttpServletRequest request, String action) {
// TODO 暂时原样返回action
return action;
}

@Override
public String processFormFieldValue(HttpServletRequest request,
String name, String value, String type) {
// TODO 暂时原样返回value
return value;
}

@Override
public Map<String, String> getExtraHiddenFields(HttpServletRequest request) {
//此处是当使用spring的taglib标签<form:form>创建表单时候,增加的隐藏域参数
Map<String, String> hiddenFields = Maps.newHashMap();
hiddenFields.put(CsrfTokenManager.CSRF_PARAM_NAME,
CsrfTokenManager.createTokenForSession(request.getSession()));

return hiddenFields;
}

@Override
public String processUrl(HttpServletRequest request, String url) {
// TODO 暂时原样返回url
return url;
}

}



package com.uncle5.pubrub.web.security;

import java.net.URLEncoder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import com.uncle5.pubrub.web.common.CsrfTokenManager;
import com.uncle5.pubrub.web.common.WebUser;

public class CsrfInterceptor extends HandlerInterceptorAdapter {

private static final Logger logger = LoggerFactory
.getLogger(CsrfInterceptor.class);

@Autowired
WebUser webUser;

@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {

if ("POST".equalsIgnoreCase(request.getMethod())) {
String CsrfToken = CsrfTokenManager.getTokenFromRequest(request);
if (CsrfToken == null
|| !CsrfToken.equals(request.getSession().getAttribute(
CsrfTokenManager.CSRF_TOKEN_FOR_SESSION_ATTR_NAME))) {
String reLoginUrl = "/login?backurl="
+ URLEncoder.encode(getCurrentUrl(request), "utf-8");

response.sendRedirect(reLoginUrl);
return false;
}
}

return true;
}

private String getCurrentUrl(HttpServletRequest request) {
String currentUrl = request.getRequestURL().toString();
if (!StringUtils.isEmpty(request.getQueryString())) {
currentUrl += "?" + request.getQueryString();
}

return currentUrl;
}
}




<!-- 安全拦截用的 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/forum/post" />
<mvc:mapping path="/forum/reply/*" />
<bean class="com.uncle5.pubrub.web.security.CsrfInterceptor" />
</mvc:interceptor>
</mvc:interceptors>


<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>

<div style="margin: 0 auto; border: 1px solid orange; width: 80%; height: 400px;">
<form:form action="/forum/post" method="post">
<span>标题:</span><input type="text" name="title" id="title">
<textarea name="content" id="content" rows="30" cols="40"></textarea>
<input type="submit" name="submit" value="submit">
</form:form>
</div>


<div style="margin: 0 auto; border: 1px solid orange; width: 80%; height: 400px;">
<form id="command" action="/forum/post" method="post">
<span>标题:</span><input type="text" name="title" id="title">
<textarea name="content" id="content" rows="30" cols="40"></textarea>
<input type="submit" name="submit" value="submit">
<input type="hidden" name="CSRFToken" value="35afec82-da7b-449e-9ae9-b38664b5af63" />
</form>
</div>



1.只有当使用spring的form标签时候,才会在渲染jsp页面时候触发
org.springframework.web.servlet.tags.form.FormTag 类中的

@Override
public int doEndTag() throws JspException {
RequestDataValueProcessor processor = getRequestContext().getRequestDataValueProcessor();
ServletRequest request = this.pageContext.getRequest();
if ((processor != null) && (request instanceof HttpServletRequest)) {
writeHiddenFields(processor.getExtraHiddenFields((HttpServletRequest) request));
}
this.tagWriter.endTag();
return EVAL_PAGE;
}
该方法会调用上文中所说的CsrfRequestDataValueProcessor中的创建隐藏域的方法getExtraHiddenFields来创建csrfToken隐藏域。

2.对相关需要进行防范csrf攻击的post请求地址进行拦截,此处是依赖springMVC中提供的拦截器机制来操作的
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/forum/post" />
<mvc:mapping path="/forum/reply/*" />
<bean class="com.uncle5.pubrub.web.security.CsrfInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
当用户访问"/forum/post"这个请求时候,会直接进入CsrfInterceptor的preHandle方法,此处针对post请求进行了判断,如果从request中获取的csrfToken不存在或者与session中的csrfToken不相匹配,那么可以不进行后续流程,至于返回404还是返回到登录页面,就随便作者了。


转载地址:http://www.oschina.net/code/piece_full?code=27153#45408
### CSRF Token 的生成、使用及安全性 #### 1. CSRF Token 的生成 CSRF Token 是一种随机生成的字符串,通常在用户会话初始化时由服务器生成并绑定到用户的会话中。生成方法可以包括使用加密算法结合时间戳、用户标识符(如用户ID)以及其他动态信息来确保其唯一性[^3]。例如,可以使用以下 Python 示例代码生成一个基于 HMAC 的 CSRF Token: ```python import hmac import hashlib import time import base64 def generate_csrf_token(secret_key, user_id): timestamp = str(int(time.time())) token_data = f"{user_id}:{timestamp}" hashed = hmac.new(secret_key.encode(), token_data.encode(), hashlib.sha256) return base64.urlsafe_b64encode(hashed.digest()).decode() ``` 此代码片段通过结合用户ID和时间戳生成一个 HMAC 哈希值,并将其编码为 Base64 格式以提高可读性。 #### 2. CSRF Token 的使用 CSRF Token 的主要用途是防止跨站请求伪造攻击。它通过以下方式实现: - **存储**:Token 通常存储在用户的会话中或作为 HTTP-only Cookie 发送到客户端。 - **传输**:在表单提交或 AJAX 请求中,Token 被嵌入到请求体或头部中[^3]。 - **验证**:服务器接收到请求后,将从请求中提取 Token 并与存储在会话中的 Token 进行比较。如果匹配,则认为请求合法;否则拒绝请求。 以下是 HTML 表单中嵌入 CSRF Token 的示例: ```html <form action="/submit" method="POST"> <input type="hidden" name="csrf_token" value="{{ csrf_token }}"> <button type="submit">Submit</button> </form> ``` 在 JavaScript 中通过 AJAX 请求发送 Token 的示例: ```javascript fetch('/submit', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': '{{ csrf_token }}' }, body: JSON.stringify({ data: 'example' }) }); ``` #### 3. CSRF Token 的安全性 为了确保 CSRF Token 的安全性,需要遵循以下原则: - **绑定会话**:Token 必须与用户的会话绑定,避免被其他用户冒用。 - **HTTPS 传输**:所有包含 Token 的通信必须通过 HTTPS 加密,防止中间人攻击。 - **有效期管理**:Token 应具有合理的有效期,过期后需重新生成[^3]。 - **二次验证**:对于关键操作,建议结合短信验证码或 TOTP(基于时间的一次性密码)进行二次验证[^3]。 此外,还需要注意 Token 的存储位置。如果存储在 DOM 中,可能会受到 XSS 攻击的影响;因此推荐使用 HTTP-only Cookie 存储 Token。 #### 示例:CSRF Token 的校验逻辑 以下是一个简单的 Flask 后端校验 CSRF Token 的示例: ```python from flask import request, session, abort def validate_csrf_token(): submitted_token = request.form.get('csrf_token') or request.headers.get('X-CSRF-Token') if not submitted_token: abort(403, "Missing CSRF Token") stored_token = session.get('csrf_token') if not stored_token or submitted_token != stored_token: abort(403, "Invalid CSRF Token") ``` ### 总结 CSRF Token 是一种有效的防御机制,用于防止跨站请求伪造攻击。生成时应确保其唯一性和不可预测性,使用时需绑定用户会话并通过 HTTPS 传输,同时定期更新以提高安全性[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值