首页 > 解决方案 > 如何在 Spring Security 的响应标头中使用密钥而不是“Set-Cookie”?

问题描述

我很难存储cookies在 React Native 应用程序中。

我的目标是发送JSESSIONIDandXSRF-TOKEN作为响应头的键而不是Set-Cookie,客户端将手动将其存储为 cookie。

  1. 我会将设置为 true的JSESSIONIDas cookie存储。HttpOnly
  2. 我会将设置为 false的XSRF-TOKENas cookie存储。HttpOnly

标签: springspring-bootcookiesspring-security

解决方案


基本安全概念:

  • 1. 会话 Cookie(例如 JSESSIONID)。

应始终为 HttpOnly,设置域(或使用默认值作为提供它的服务器)。切勿存储在处理它的浏览器以外的任何地方。您的 JS/HTML 应该对 JSESSION Cookie 一无所知,并且只有在用户从端点获得 401(未授权)时才将用户移动到登录屏幕。

  • 2. CSRF 代币。

回到过去(大概 5 年吧!),大多数网站都是在服务器上呈现的。服务器创建了 HTML,然后通过 URI 将其发回。就像您访问 /profile 一样,服务器知道您是谁,然后在其服务器上创建个人资料页面并反馈 HTML 文档。

当想要获得一些用户输入时,服务器呈现的这个 HTML 将包含一个<form/>收集用户数据(密码/银行详细信息等),然后以某种格式onSubmit将其传递回服务器application/x-www-form-urlencoded

例如。 https://thewebsite.com/sendmoney_to?account=512&amount=1milliondollars

只需将该链接发送给与该站点有活动会话的人,thewebsite.com浏览器就会访问它并执行请求。

就站点而言,受害者已登录,并且会愉快地运行攻击者发送的请求。这些链接有时甚至是追随者,只需<img>通过在他们的墙上或电子邮件等中发布来加载一个喜欢。

那么他们是如何解决这个问题的呢?

通过将一些字段添加到名为hidden fields. 这些隐藏字段是由服务器在呈现页面时创建的。它们包含一个值,即CSRF TOKEN以及 CSRF COOKIE 中的某些内容。所以当application/x-www-form-urlencoded表单被发送时,它必须在渲染表单时在服务器上产生这些值。然后,服务器可以验证表单是他们创建的表单,而不是攻击者创建的恶意链接。攻击者在建立他们的顽皮链接时无法知道/猜测这些。

  • 3. 现在的日子

由于只有 JSON 请求,因此许多站点(例如 React 应用程序)在客户端呈现并使用 Axios/Fetch...CSRF 有点多余。您不发布表单/ application/x-www-form-urlencoded...仅使用 JSON 正文发出 POST 请求。

作为 XSS 攻击 > CSRF 攻击,会话仍然很重要。正确存储会话(JWT 令牌与否......不要跳上 JWT 的潮流并开始将 JWT Auth Token 扔到本地存储周围,这似乎已成为新开发人员的某种奇怪的默认设置)。

如果你确定你只有有application/json能力的端点,那么攻击者让你发布他们的内容而不是你想要的内容的唯一方法是通过 XSS 攻击。但是一旦他们受到 XSS 攻击,游戏就结束了。就服务器而言,他们只是你,因为他们使用他们的顽皮注入<script>在请求发送之前操纵请求,并且服务器无法知道它已被动态操纵(通常)。

  • 4. 通过 Header 获取您的 CSRF Token,因为它不会在表单中生成

XSRF-TOKEN 您可能需要使用过滤器公开 XSRF 令牌,该过滤器将提取 CSRF 令牌(通过会话)并将其添加到响应实体的标头中。

我自己写了,但在一些用来做同样事情的库中基本上是一样的。


@Log4j2
public class CsrfBindingFilter extends OncePerRequestFilter {
    protected static final String REQUEST_ATTRIBUTE_NAME = "_csrf";
    protected static final String RESPONSE_HEADER_NAME = "X-CSRF-HEADER";
    protected static final String RESPONSE_PARAM_NAME = "X-CSRF-PARAM";
    protected static final String RESPONSE_TOKEN_NAME = "X-CSRF-TOKEN";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, javax.servlet.FilterChain filterChain) throws ServletException, IOException {
        CsrfToken csrfToken = (CsrfToken) request.getAttribute(REQUEST_ATTRIBUTE_NAME);
        log.debug("CRSF Token from session : {}", csrfToken != null ? csrfToken.getToken() : "CsrfToken is NULL");
        if (csrfToken != null) {
            response.setHeader(RESPONSE_HEADER_NAME, csrfToken.getHeaderName());
            response.setHeader(RESPONSE_PARAM_NAME, csrfToken.getParameterName());
            response.setHeader(RESPONSE_TOKEN_NAME, csrfToken.getToken());
        }

        filterChain.doFilter(request, response);
    }
}

至于会话,浏览器应该处理它,你不应该在客户端弄乱它,除非它是出于非常特定的原因(我无法理解)。Sessions 的 Cookie 设置为 HttpOnly 是为了禁止在客户端运行的任何 JS 编辑/读取/添加它。一个带有一些顽皮代码的小广告/xssget the cookie called JSESSION ID if the host is myvictim.com and post it over here...可能意味着您受到了损害。

阅读此内容以获取更多详细信息: 通过 JS 添加 HttpOnly Cookie 浏览器应按Set-Cookie预期处理标头,这是最佳实践(出于安全原因,也只是为了简单易用)。

WebSocket STOMP 身份验证

一旦用户通过您的身份验证/login,Spring 应该发送带有 Cookie 的 Set-Cookie 标头JSESSIONID。此 cookie 将由浏览器存储,您的前端 javascript 应用程序应该无法访问。

如果随后使用 STOMP Spring WebSocket 实现,则可以通过参数StompHeaderAccessor中的 STOMP 消息从 STOMP 消息中获取主体用户名@MessageMapping Controller

stompHeaderAccessor.getUser().getName()会给 Spring Security 认证用户的主体名称(通常是他们的用户名或 id,用户名是默认的)。


 @MessageMapping("/agents/start")
    public void start(StompHeaderAccessor stompHeaderAccessor) {
        log.info("Subscriber Start! {}-{}", stompHeaderAccessor.getUser() != null ? stompHeaderAccessor.getUser().getName() : "ANON", stompHeaderAccessor.getSessionId());
        mysessionstore.addSessionId(stompHeaderAccessor.getSessionId());
    }

如果您想编辑用户的会话属性,则需要从 SPRING_SESSION 表中获取他们的会话 ID,然后您可以使用 SpringSessionRepository来获取它。

https://docs.spring.io/spring-session/docs/current/api/org/springframework/session/SessionRepository.html


推荐阅读