加入收藏 | 设为首页 | 会员中心 | 我要投稿 阜新站长网 (https://www.0418zz.cn/)- 基础存储、数据处理、视频终端、内容创作、网络安全!
当前位置: 首页 > 服务器 > 安全 > 正文

Spring Security 中 CSRF 防御源码说明

发布时间:2021-06-08 18:12:16 所属栏目:安全 来源:互联网
导读:1.随机字符串生成 我们先来看一下 Spring Security 中的 csrf 参数是如何生成的。 首先,Spring Security 中提供了一个保存 csrf 参数的规范,就是 CsrfToken: public interface CsrfToken extends Serializable { String getHeaderName(); String getParam
1.随机字符串生成
我们先来看一下 Spring Security 中的 csrf 参数是如何生成的。
首先,Spring Security 中提供了一个保存 csrf 参数的规范,就是 CsrfToken:
public interface CsrfToken extends Serializable { 
 String getHeaderName(); 
 String getParameterName(); 
 String getToken(); 
 
这里三个方法都好理解,前两个是获取 _csrf 参数的 key,第三个是获取 _csrf 参数的 value。
CsrfToken 有两个实现类,如下:
 
默认情况下使用的是 DefaultCsrfToken,我们来稍微看下 DefaultCsrfToken:
public final class DefaultCsrfToken implements CsrfToken { 
 private final String token; 
 private final String parameterName; 
 private final String headerName; 
 public DefaultCsrfToken(String headerName, String parameterName, String token) { 
  this.headerName = headerName; 
  this.parameterName = parameterName; 
  this.token = token; 
 } 
 public String getHeaderName() { 
  return this.headerName; 
 } 
 public String getParameterName() { 
  return this.parameterName; 
 } 
 public String getToken() { 
  return this.token; 
 } 
这段实现很简单,几乎没有添加额外的方法,就是接口方法的实现。
CsrfToken 相当于就是 _csrf 参数的载体。那么参数是如何生成和保存的呢?这涉及到另外一个类:
public interface CsrfTokenRepository { 
 CsrfToken generateToken(HttpServletRequest request); 
 void saveToken(CsrfToken token, HttpServletRequest request, 
   HttpServletResponse response); 
 CsrfToken loadToken(HttpServletRequest request); 
这里三个方法:
鸿蒙官方战略合作共建——HarmonyOS技术社区
generateToken 方法就是 CsrfToken 的生成过程。
saveToken 方法就是保存 CsrfToken。
loadToken 则是如何加载 CsrfToken。
CsrfTokenRepository 有四个实现类,在上篇文章中,我们用到了其中两个:HttpSessionCsrfTokenRepository 和 CookieCsrfTokenRepository,其中 HttpSessionCsrfTokenRepository 是默认的方案。
 
我们先来看下 HttpSessionCsrfTokenRepository 的实现:
public final class HttpSessionCsrfTokenRepository implements CsrfTokenRepository { 
 private static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf"; 
 private static final String DEFAULT_CSRF_HEADER_NAME = "X-CSRF-TOKEN"; 
 private static final String DEFAULT_CSRF_TOKEN_ATTR_NAME = HttpSessionCsrfTokenRepository.class 
   .getName().concat(".CSRF_TOKEN"); 
 private String parameterName = DEFAULT_CSRF_PARAMETER_NAME; 
 private String headerName = DEFAULT_CSRF_HEADER_NAME; 
 private String sessionAttributeName = DEFAULT_CSRF_TOKEN_ATTR_NAME; 
 public void saveToken(CsrfToken token, HttpServletRequest request, 
   HttpServletResponse response) { 
  if (token == null) { 
   HttpSession session = request.getSession(false); 
   if (session != null) { 
    session.removeAttribute(this.sessionAttributeName); 
   } 
  } 
  else { 
   HttpSession session = request.getSession(); 
   session.setAttribute(this.sessionAttributeName, token); 
  } 
 } 
 public CsrfToken loadToken(HttpServletRequest request) { 
  HttpSession session = request.getSession(false); 
  if (session == null) { 
   return null; 
  } 
  return (CsrfToken) session.getAttribute(this.sessionAttributeName); 
 } 
 public CsrfToken generateToken(HttpServletRequest request) { 
  return new DefaultCsrfToken(this.headerName, this.parameterName, 
    createNewToken()); 
 } 
 private String createNewToken() { 
  return UUID.randomUUID().toString(); 
 } 
这段源码其实也很好理解:
鸿蒙官方战略合作共建——HarmonyOS技术社区
saveToken 方法将 CsrfToken 保存在 HttpSession 中,将来再从 HttpSession 中取出和前端传来的参数做比较。
loadToken 方法当然就是从 HttpSession 中读取 CsrfToken 出来。
generateToken 是生成 CsrfToken 的过程,可以看到,生成的默认载体就是 DefaultCsrfToken,而 CsrfToken 的值则通过 createNewToken 方法生成,是一个 UUID 字符串。
在构造 DefaultCsrfToken 是还有两个参数 headerName 和 parameterName,这两个参数是前端保存参数的 key。
这是默认的方案,适用于前后端不分的开发,具体用法可以参考上篇文章。
如果想在前后端分离开发中使用,那就需要 CsrfTokenRepository 的另一个实现类 CookieCsrfTokenRepository ,代码如下:
public final class CookieCsrfTokenRepository implements CsrfTokenRepository { 
 static final String DEFAULT_CSRF_COOKIE_NAME = "XSRF-TOKEN"; 
 static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf"; 
 static final String DEFAULT_CSRF_HEADER_NAME = "X-XSRF-TOKEN"; 
 private String parameterName = DEFAULT_CSRF_PARAMETER_NAME; 
 private String headerName = DEFAULT_CSRF_HEADER_NAME; 
 private String cookieName = DEFAULT_CSRF_COOKIE_NAME; 
 private boolean cookieHttpOnly = true; 
 private String cookiePath; 
 private String cookieDomain; 
 public CookieCsrfTokenRepository() { 
 } 
 @Override 
 public CsrfToken generateToken(HttpServletRequest request) { 
  return new DefaultCsrfToken(this.headerName, this.parameterName, 
    createNewToken()); 
 } 
 @Override 
 public void saveToken(CsrfToken token, HttpServletRequest request, 
   HttpServletResponse response) { 
  String tokenValue = token == null ? "" : token.getToken(); 
  Cookie cookie = new Cookie(this.cookieName, tokenValue); 
  cookie.setSecure(request.isSecure()); 
  if (this.cookiePath != null && !this.cookiePath.isEmpty()) { 
    cookie.setPath(this.cookiePath); 
  } else { 
    cookie.setPath(this.getRequestContext(request)); 
  } 
  if (token == null) { 
   cookie.setMaxAge(0); 
  } 
  else { 
   cookie.setMaxAge(-1); 
  } 
  cookie.setHttpOnly(cookieHttpOnly); 
  if (this.cookieDomain != null && !this.cookieDomain.isEmpty()) { 
   cookie.setDomain(this.cookieDomain); 
  } 
 
  response.addCookie(cookie); 
 } 
 @Override 
 public CsrfToken loadToken(HttpServletRequest request) { 
  Cookie cookie = WebUtils.getCookie(request, this.cookieName); 
  if (cookie == null) { 
   return null; 
  } 
  String token = cookie.getValue(); 
  if (!StringUtils.hasLength(token)) { 
   return null; 
  } 
  return new DefaultCsrfToken(this.headerName, this.parameterName, token); 
 } 
 public static CookieCsrfTokenRepository withHttpOnlyFalse() { 
  CookieCsrfTokenRepository result = new CookieCsrfTokenRepository(); 
  result.setCookieHttpOnly(false); 
  return result; 
 } 
 private String createNewToken() { 
  return UUID.randomUUID().toString(); 
 } 
和 HttpSessionCsrfTokenRepository 相比,这里 _csrf 数据保存的时候,都保存到 cookie 中去了,当然读取的时候,也是从 cookie 中读取,其他地方则和 HttpSessionCsrfTokenRepository 是一样的。

(编辑:阜新站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读