【问题标题】:How to use key instead of `Set-Cookie` in Response Headers with Spring Security?如何在 Spring Security 的响应标头中使用密钥而不是“Set-Cookie”?
【发布时间】:2021-04-29 07:08:31
【问题描述】:

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

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

  1. 我会将JSESSIONID 存储为cookie,并将HttpOnly 设置为true。
  2. 我会将XSRF-TOKEN 存储为cookie,并将HttpOnly 设置为false。

【问题讨论】:

    标签: spring spring-boot cookies spring-security


    【解决方案1】:

    基本安全概念:

    • 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。通过标头获取您的 CSRF 令牌,因为它不会以表格形式生成

    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 编辑/读取/添加它。 带有一些像get the cookie called JSESSION ID if the host is myvictim.com and post it over here... 这样的顽皮代码的小广告/xss 可能意味着您受到了损害。

    阅读本文了解更多详情: Add HttpOnly Cookie via JS 浏览器应按预期处理 Set-Cookie 标头,这是最佳实践(出于安全原因,也只是为了简单易用)。

    WebSocket STOMP 身份验证

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

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

    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,您可以使用 Spring SessionRepository 来获取它。

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

    【讨论】:

    • 我看到你不能用 JS 设置 Cookie 和 HttpOnly。我目前的理解是,如果您的服务器遭到破坏(例如,由于 CDN 受损或库受损),XSS 攻击是可能的。那么在 LocalStorage 中存储 JSESSIONID 和 XSRF-TOKEN 是否安全?
    • 没有。从不用于会话 cookie。例如,如果您加载广告或用户找到一种方法将评论添加到带有脚本标签的帖子,然后可以运行任意 JS,就会发生 XSS。例如,它可以像蠕虫的原始和第一个示例一样传播:萨米蠕虫。 JS可以访问本地存储。 JSESSIONID/SESSION 令牌应该存在的唯一地方是浏览器的 HttpOnly Cookie 存储。这也适用于身份验证 JWT,您会看到许多新手开发者(它的新时尚)也存储在 local/session/ram 中。
    • 为什么您需要在标头中使用 JSESSION cookie 而不是作为 Set-Cookie 标头?你能用它做什么有意义?例如,将 Axios 设置为 withCredentials=true 意味着浏览器将在每个请求中发送 JESSION httponly cookie(如果域与 c 的 cookie 匹配)。 HttpCookie 和 JSESSIONID 的使用意味着您在前端 javascript 上唯一应该关心的事情 => 从端点获取 401 => 转到登录页面。您的 HTML/JS 应该对会话 Cookie 一无所知。
    • 好的。我已经用更多细节更新了我的答案。我会查找更多关于 XSS 和 CSRF 攻击以及如何缓解它们的信息。另外我会查找/思考为什么存储 JWT Auth 令牌(我的上帝......他们这样做)包括在本地存储中刷新令牌或通过标头发送它们不是很好......
    • 我会回答你的担心,我需要在标头中使用 JSESSIONID cookie 而不是 Set-Cookie 来进行 React Native 应用程序身份验证,当我使用 React Native Fetch API 和React Native 中的 cookie 行为不同,它不会拦截 HTTP 请求,因此我需要将 WebSocket 握手的基于 Cookie 的身份验证重构为基于 http-header 的身份验证。从长远来看,浏览器中的每个 WebSocket 握手(使用 stomp-js/stompjs 库)都将包含“Cookie:JSESSIONID=ABC123;”而在 React Native 应用程序中,它不会包含“Cookie”
    猜你喜欢
    • 1970-01-01
    • 2017-11-23
    • 2019-06-10
    • 1970-01-01
    • 2012-09-30
    • 2019-08-01
    • 2016-05-22
    • 1970-01-01
    • 2015-09-11
    相关资源
    最近更新 更多