630.6099312f.js 27.4 KB
Newer Older
茶陵後's avatar
茶陵後 已提交
1
(window.webpackJsonp=window.webpackJsonp||[]).push([[630],{1061:function(e,t,r){"use strict";r.r(t);var s=r(56),a=Object(s.a)({},(function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("ContentSlotsDistributor",{attrs:{"slot-key":e.$parent.slotKey}},[r("h1",{attrs:{id:"websocket-安全"}},[r("a",{staticClass:"header-anchor",attrs:{href:"#websocket-安全"}},[e._v("#")]),e._v(" WebSocket 安全")]),e._v(" "),r("p",[e._v("Spring Security4增加了对保护"),r("a",{attrs:{href:"https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html",target:"_blank",rel:"noopener noreferrer"}},[e._v("Spring’s WebSocket support"),r("OutboundLink")],1),e._v("的支持。本节介绍如何使用 Spring Security的 WebSocket 支持。")]),e._v(" "),r("p",[e._v("直接支持JSR-356")]),e._v(" "),r("p",[e._v("Spring 安全性不提供直接的JSR-356支持,因为这样做将提供很少的价值。这是因为格式未知,所以有"),r("a",{attrs:{href:"https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-intro-sub-protocol",target:"_blank",rel:"noopener noreferrer"}},[e._v("little Spring can do to secure an unknown format"),r("OutboundLink")],1),e._v("。此外,JSR-356不提供截获消息的方法,因此安全性将是侵入性的。")]),e._v(" "),r("h2",{attrs:{id:"websocket-配置"}},[r("a",{staticClass:"header-anchor",attrs:{href:"#websocket-配置"}},[e._v("#")]),e._v(" WebSocket 配置")]),e._v(" "),r("p",[e._v("Spring Security4.0通过 Spring 消息抽象引入了对WebSockets的授权支持。要使用 Java 配置配置来配置授权,只需扩展"),r("code",[e._v("AbstractSecurityWebSocketMessageBrokerConfigurer")]),e._v("并配置"),r("code",[e._v("MessageSecurityMetadataSourceRegistry")]),e._v("。例如:")]),e._v(" "),r("p",[e._v("Java")]),e._v(" "),r("div",{staticClass:"language- extra-class"},[r("pre",{pre:!0,attrs:{class:"language-text"}},[r("code",[e._v('@Configuration\npublic class WebSocketSecurityConfig\n      extends AbstractSecurityWebSocketMessageBrokerConfigurer { (1) (2)\n\n    protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {\n        messages\n                .simpDestMatchers("/user/**").authenticated() (3)\n    }\n}\n')])])]),r("p",[e._v("Kotlin")]),e._v(" "),r("div",{staticClass:"language- extra-class"},[r("pre",{pre:!0,attrs:{class:"language-text"}},[r("code",[e._v('@Configuration\nopen class WebSocketSecurityConfig : AbstractSecurityWebSocketMessageBrokerConfigurer() { (1) (2)\n    override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {\n        messages.simpDestMatchers("/user/**").authenticated() (3)\n    }\n}\n')])])]),r("p",[e._v("这将确保:")]),e._v(" "),r("table",[r("thead",[r("tr",[r("th",[r("strong",[e._v("1")])]),e._v(" "),r("th",[e._v("任何入站连接消息都需要一个有效的CSRF令牌来执行"),r("a",{attrs:{href:"#websocket-sameorigin"}},[e._v("同源政策")])])])]),e._v(" "),r("tbody",[r("tr",[r("td",[r("strong",[e._v("2")])]),e._v(" "),r("td",[e._v("对于任何入站请求,SecurityContextholder都在Simpuser header属性中填充用户。")])]),e._v(" "),r("tr",[r("td",[r("strong",[e._v("3")])]),e._v(" "),r("td",[e._v("我们的信息需要得到适当的授权。具体地说,任何以“/user/”开头的入站消息都需要角色_user。有关授权的更多详细信息,请参见"),r("a",{attrs:{href:"#websocket-authorization"}},[e._v("WebSocket 授权")]),e._v("")])])])]),e._v(" "),r("p",[e._v("Spring 安全性还提供了"),r("RouterLink",{attrs:{to:"/appendix/namespace/websocket.html#nsa-websocket-security"}},[e._v("XML命名空间")]),e._v("用于保护WebSockets的支持。类似的基于XML的配置如下所示:")],1),e._v(" "),r("div",{staticClass:"language- extra-class"},[r("pre",{pre:!0,attrs:{class:"language-text"}},[r("code",[e._v('<websocket-message-broker> (1) (2)\n    (3)\n    <intercept-message pattern="/user/**" access="hasRole(\'USER\')" />\n</websocket-message-broker>\n')])])]),r("p",[e._v("这将确保:")]),e._v(" "),r("table",[r("thead",[r("tr",[r("th",[r("strong",[e._v("1")])]),e._v(" "),r("th",[e._v("任何入站连接消息都需要一个有效的CSRF令牌来执行"),r("a",{attrs:{href:"#websocket-sameorigin"}},[e._v("同源政策")])])])]),e._v(" "),r("tbody",[r("tr",[r("td",[r("strong",[e._v("2")])]),e._v(" "),r("td",[e._v("对于任何入站请求,SecurityContextholder都在Simpuser header属性中填充用户。")])]),e._v(" "),r("tr",[r("td",[r("strong",[e._v("3")])]),e._v(" "),r("td",[e._v("我们的信息需要得到适当的授权。具体地说,任何以“/user/”开头的入站消息都需要角色_user。有关授权的更多详细信息,请参见"),r("a",{attrs:{href:"#websocket-authorization"}},[e._v("WebSocket Authorization")]),e._v("")])])])]),e._v(" "),r("h2",{attrs:{id:"websocket-认证"}},[r("a",{staticClass:"header-anchor",attrs:{href:"#websocket-认证"}},[e._v("#")]),e._v(" WebSocket 认证")]),e._v(" "),r("p",[e._v("WebSockets重用在建立 WebSocket 连接时在HTTP请求中找到的相同的身份验证信息。这意味着"),r("code",[e._v("Principal")]),e._v("上的"),r("code",[e._v("HttpServletRequest")]),e._v("将被传递给WebSockets。如果使用 Spring 安全性,则"),r("code",[e._v("HttpServletRequest")]),e._v("上的"),r("code",[e._v("Principal")]),e._v("将自动被重写。")]),e._v(" "),r("p",[e._v("更具体地说,为了确保用户已经对你的 WebSocket 应用程序进行了身份验证,所有必要的是确保设置 Spring 安全性以对基于HTTP的Web应用程序进行身份验证。")]),e._v(" "),r("h2",{attrs:{id:"websocket-authorization"}},[r("a",{staticClass:"header-anchor",attrs:{href:"#websocket-authorization"}},[e._v("#")]),e._v(" WebSocket Authorization")]),e._v(" "),r("p",[e._v("Spring Security4.0通过 Spring 消息抽象引入了对WebSockets的授权支持。要使用 Java 配置配置来配置授权,只需扩展"),r("code",[e._v("AbstractSecurityWebSocketMessageBrokerConfigurer")]),e._v("并配置"),r("code",[e._v("MessageSecurityMetadataSourceRegistry")]),e._v("。例如:")]),e._v(" "),r("p",[e._v("Java")]),e._v(" "),r("div",{staticClass:"language- extra-class"},[r("pre",{pre:!0,attrs:{class:"language-text"}},[r("code",[e._v('@Configuration\npublic class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {\n\n    @Override\n    protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {\n        messages\n                .nullDestMatcher().authenticated() (1)\n                .simpSubscribeDestMatchers("/user/queue/errors").permitAll() (2)\n                .simpDestMatchers("/app/**").hasRole("USER") (3)\n                .simpSubscribeDestMatchers("/user/**", "/topic/friends/*").hasRole("USER") (4)\n                .simpTypeMatchers(MESSAGE, SUBSCRIBE).denyAll() (5)\n                .anyMessage().denyAll(); (6)\n\n    }\n}\n')])])]),r("p",[e._v("Kotlin")]),e._v(" "),r("div",{staticClass:"language- extra-class"},[r("pre",{pre:!0,attrs:{class:"language-text"}},[r("code",[e._v('@Configuration\nopen class WebSocketSecurityConfig : AbstractSecurityWebSocketMessageBrokerConfigurer() {\n    override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {\n        messages\n            .nullDestMatcher().authenticated() (1)\n            .simpSubscribeDestMatchers("/user/queue/errors").permitAll() (2)\n            .simpDestMatchers("/app/**").hasRole("USER") (3)\n            .simpSubscribeDestMatchers("/user/**", "/topic/friends/*").hasRole("USER") (4)\n            .simpTypeMatchers(MESSAGE, SUBSCRIBE).denyAll() (5)\n            .anyMessage().denyAll() (6)\n    }\n}\n')])])]),r("p",[e._v("这将确保:")]),e._v(" "),r("table",[r("thead",[r("tr",[r("th",[r("strong",[e._v("1")])]),e._v(" "),r("th",[e._v("任何没有目的地的消息(即消息类型或订阅以外的任何消息)都需要对用户进行身份验证。")])])]),e._v(" "),r("tbody",[r("tr",[r("td",[r("strong",[e._v("2")])]),e._v(" "),r("td",[e._v("任何人都可以订阅/user/queue/errors")])]),e._v(" "),r("tr",[r("td",[r("strong",[e._v("3")])]),e._v(" "),r("td",[e._v("任何具有以“/app/”开头的目标的消息都将要求用户具有角色_user")])]),e._v(" "),r("tr",[r("td",[r("strong",[e._v("4")])]),e._v(" "),r("td",[e._v("任何以“/user/”或“/topic/friends/”开头、类型为Subscribe的消息都需要角色_user")])]),e._v(" "),r("tr",[r("td",[r("strong",[e._v("5")])]),e._v(" "),r("td",[e._v("任何其他类型为消息或订阅的消息都将被拒绝。由于6,我们不需要这个步骤,但它说明了如何在特定的消息类型上进行匹配。")])]),e._v(" "),r("tr",[r("td",[r("strong",[e._v("6")])]),e._v(" "),r("td",[e._v("任何其他消息都将被拒绝。这是一个好主意,以确保你不会错过任何消息。")])])])]),e._v(" "),r("p",[e._v("Spring 安全性还提供了"),r("RouterLink",{attrs:{to:"/appendix/namespace/websocket.html#nsa-websocket-security"}},[e._v("XML命名空间")]),e._v("用于保护WebSockets的支持。类似的基于XML的配置如下所示:")],1),e._v(" "),r("div",{staticClass:"language- extra-class"},[r("pre",{pre:!0,attrs:{class:"language-text"}},[r("code",[e._v('<websocket-message-broker>\n    (1)\n    <intercept-message type="CONNECT" access="permitAll" />\n    <intercept-message type="UNSUBSCRIBE" access="permitAll" />\n    <intercept-message type="DISCONNECT" access="permitAll" />\n\n    <intercept-message pattern="/user/queue/errors" type="SUBSCRIBE" access="permitAll" /> (2)\n    <intercept-message pattern="/app/**" access="hasRole(\'USER\')" />      (3)\n\n    (4)\n    <intercept-message pattern="/user/**" access="hasRole(\'USER\')" />\n    <intercept-message pattern="/topic/friends/*" access="hasRole(\'USER\')" />\n\n    (5)\n    <intercept-message type="MESSAGE" access="denyAll" />\n    <intercept-message type="SUBSCRIBE" access="denyAll" />\n\n    <intercept-message pattern="/**" access="denyAll" /> (6)\n</websocket-message-broker>\n')])])]),r("p",[e._v("这将确保:")]),e._v(" "),r("table",[r("thead",[r("tr",[r("th",[r("strong",[e._v("1")])]),e._v(" "),r("th",[e._v("任何类型为“连接”、“取消订阅”或“断开连接”的消息都需要对用户进行身份验证。")])])]),e._v(" "),r("tbody",[r("tr",[r("td",[r("strong",[e._v("2")])]),e._v(" "),r("td",[e._v("任何人都可以订阅/user/queue/errors")])]),e._v(" "),r("tr",[r("td",[r("strong",[e._v("3")])]),e._v(" "),r("td",[e._v("任何具有以“/app/”开头的目标的消息都将要求用户具有角色_user")])]),e._v(" "),r("tr",[r("td",[r("strong",[e._v("4")])]),e._v(" "),r("td",[e._v("任何以“/user/”或“/topic/friends/”开头、类型为Subscribe的消息都需要角色_user")])]),e._v(" "),r("tr",[r("td",[r("strong",[e._v("5")])]),e._v(" "),r("td",[e._v("任何其他类型为消息或订阅的消息都将被拒绝。由于6,我们不需要这个步骤,但它说明了如何在特定的消息类型上进行匹配。")])]),e._v(" "),r("tr",[r("td",[r("strong",[e._v("6")])]),e._v(" "),r("td",[e._v("具有目的的任何其他消息都将被拒绝。这是一个好主意,以确保你不会错过任何消息。")])])])]),e._v(" "),r("h3",{attrs:{id:"websocket-授权说明"}},[r("a",{staticClass:"header-anchor",attrs:{href:"#websocket-授权说明"}},[e._v("#")]),e._v(" WebSocket 授权说明")]),e._v(" "),r("p",[e._v("为了正确地保护你的应用程序,理解 Spring 的 WebSocket 支持非常重要。")]),e._v(" "),r("h4",{attrs:{id:"websocket-对消息类型的授权"}},[r("a",{staticClass:"header-anchor",attrs:{href:"#websocket-对消息类型的授权"}},[e._v("#")]),e._v(" WebSocket 对消息类型的授权")]),e._v(" "),r("p",[e._v("理解消息的订阅和消息类型之间的区别以及它在 Spring 中的工作方式非常重要。")]),e._v(" "),r("p",[e._v("考虑一个聊天应用程序。")]),e._v(" "),r("ul",[r("li",[r("p",[e._v("系统可以通过目标“/topic/system/notifications”向所有用户发送通知消息。")])]),e._v(" "),r("li",[r("p",[e._v("客户端可以通过订阅“/topic/system/notification”来接收通知。")])])]),e._v(" "),r("p",[e._v("虽然我们希望客户机能够订阅“/topic/system/notifications”,但我们并不希望使他们能够向该目的地发送消息。如果我们允许向“/topic/system/notifications”发送消息,那么客户端可以直接向该端点发送消息并模拟系统。")]),e._v(" "),r("p",[e._v("通常,应用程序会拒绝发送到以"),r("a",{attrs:{href:"https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp",target:"_blank",rel:"noopener noreferrer"}},[e._v("代理前缀"),r("OutboundLink")],1),e._v("(即“/topic/”或“/queue/”)开头的目标的任何消息。")]),e._v(" "),r("h4",{attrs:{id:"websocket-目的地授权"}},[r("a",{staticClass:"header-anchor",attrs:{href:"#websocket-目的地授权"}},[e._v("#")]),e._v(" WebSocket 目的地授权")]),e._v(" "),r("p",[e._v("了解目的地是如何转变的也很重要。")]),e._v(" "),r("p",[e._v("考虑一个聊天应用程序。")]),e._v(" "),r("ul",[r("li",[r("p",[e._v("用户可以通过向“/app/chat”的目的地发送消息来向特定用户发送消息。")])]),e._v(" "),r("li",[r("p",[e._v("应用程序看到消息,确保将“from”属性指定为当前用户(我们不能信任客户端)。")])]),e._v(" "),r("li",[r("p",[e._v("然后,应用程序使用"),r("code",[e._v('SimpMessageSendingOperations.convertAndSendToUser("toUser", "/queue/messages", message)')]),e._v("将消息发送给收件人。")])]),e._v(" "),r("li",[r("p",[e._v("消息被转换为“/queue/user/messages-<sessionid>”的目标。")])])]),e._v(" "),r("p",[e._v("对于上面的应用程序,我们希望允许我们的客户机侦听“/user/queue”,它被转换为“/queue/user/messages-<sessionid>”。但是,我们不希望客户机能够侦听“/queue/*”,因为这将允许客户机查看每个用户的消息。")]),e._v(" "),r("p",[e._v("通常,应用程序会拒绝发送到以"),r("a",{attrs:{href:"https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp",target:"_blank",rel:"noopener noreferrer"}},[e._v("代理前缀"),r("OutboundLink")],1),e._v("(即“/topic/”或“/queue/”)开头的消息的任何订阅。当然,我们可能会提供例外情况,以解释诸如")]),e._v(" "),r("h3",{attrs:{id:"出站消息"}},[r("a",{staticClass:"header-anchor",attrs:{href:"#出站消息"}},[e._v("#")]),e._v(" 出站消息")]),e._v(" "),r("p",[e._v("Spring 包含一个标题为"),r("a",{attrs:{href:"https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp-message-flow",target:"_blank",rel:"noopener noreferrer"}},[e._v("消息流"),r("OutboundLink")],1),e._v("的部分,该部分描述了消息如何在系统中流动。需要注意的是, Spring 安全性仅保护"),r("code",[e._v("clientInboundChannel")]),e._v("。 Spring 安全性不试图保护"),r("code",[e._v("clientOutboundChannel")]),e._v("")]),e._v(" "),r("p",[e._v("这其中最重要的原因是业绩。对于每一条传入的消息,通常都会有更多的消息传出。我们鼓励保护对端点的订阅,而不是保护出站消息。")]),e._v(" "),r("h2",{attrs:{id:"执行同源政策"}},[r("a",{staticClass:"header-anchor",attrs:{href:"#执行同源政策"}},[e._v("#")]),e._v(" 执行同源政策")]),e._v(" "),r("p",[e._v("需要强调的是,对于 WebSocket 连接,浏览器并不强制执行"),r("a",{attrs:{href:"https://en.wikipedia.org/wiki/Same-origin_policy",target:"_blank",rel:"noopener noreferrer"}},[e._v("同源政策"),r("OutboundLink")],1),e._v("。这是一个极其重要的考虑因素。")]),e._v(" "),r("h3",{attrs:{id:"为什么是同源"}},[r("a",{staticClass:"header-anchor",attrs:{href:"#为什么是同源"}},[e._v("#")]),e._v(" 为什么是同源?")]),e._v(" "),r("p",[e._v("考虑以下场景。用户访问bank.com并对其帐户进行身份验证。同一个用户在浏览器中打开另一个标签,然后访问Evil.com。相同的源策略确保Evil.com不能将数据读写到bank.com。")]),e._v(" "),r("p",[e._v("对于WebSockets,相同的源策略不适用。事实上,除非bank.com明确禁止,否则evil.com可以代表用户读写数据。这意味着用户可以在 WebSocket 上做的任何事情(即转账),Evil.com都可以代表用户做。")]),e._v(" "),r("p",[e._v("由于Sockjs试图模拟WebSockets,因此它也绕过了相同的源策略。这意味着开发人员在使用Sockjs时需要显式地保护其应用程序不受外部域的影响。")]),e._v(" "),r("h3",{attrs:{id:"spring-websocket-允许原产地"}},[r("a",{staticClass:"header-anchor",attrs:{href:"#spring-websocket-允许原产地"}},[e._v("#")]),e._v(" Spring  WebSocket 允许原产地")]),e._v(" "),r("p",[e._v("幸运的是,由于 Spring 4.1.5 Spring 的 WebSocket 和Sockjs支持限制了对"),r("a",{attrs:{href:"https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-server-allowed-origins",target:"_blank",rel:"noopener noreferrer"}},[e._v("当前域"),r("OutboundLink")],1),e._v("的访问。 Spring 安全性增加了额外的保护层,以提供[纵深防御](https://en.wikipedia.org/wiki/Defense_in_depth_(computing))。")]),e._v(" "),r("h3",{attrs:{id:"将csrf添加到stomp头"}},[r("a",{staticClass:"header-anchor",attrs:{href:"#将csrf添加到stomp头"}},[e._v("#")]),e._v(" 将CSRF添加到Stomp头")]),e._v(" "),r("p",[e._v("Spring 默认情况下,安全性要求在任何连接消息类型中使用"),r("RouterLink",{attrs:{to:"/features/exploits/csrf.html#csrf"}},[e._v("CSRF token")]),e._v("。这确保只有能够访问CSRF令牌的站点才能进行连接。由于只有"),r("strong",[e._v("同源")]),e._v("可以访问CSRF令牌,因此不允许外部域进行连接。")],1),e._v(" "),r("p",[e._v("通常,我们需要在HTTP报头或HTTP参数中包含CSRF令牌。然而,Sockjs不允许这些选项。相反,我们必须在Stomp头中包含令牌。")]),e._v(" "),r("p",[e._v("应用程序可以通过访问名为_CSRF的请求属性"),r("RouterLink",{attrs:{to:"/exploits/csrf.html#servlet-csrf-include"}},[e._v("获取CSRF令牌")]),e._v("。例如,下面将允许访问JSP中的"),r("code",[e._v("CsrfToken")]),e._v(":")],1),e._v(" "),r("div",{staticClass:"language- extra-class"},[r("pre",{pre:!0,attrs:{class:"language-text"}},[r("code",[e._v('var headerName = "${_csrf.headerName}";\nvar token = "${_csrf.token}";\n')])])]),r("p",[e._v("如果使用静态HTML,则可以在REST端点上公开"),r("code",[e._v("CsrfToken")]),e._v("。例如,下面将公开URL/CSRF上的"),r("code",[e._v("CsrfToken")])]),e._v(" "),r("p",[e._v("Java")]),e._v(" "),r("div",{staticClass:"language- extra-class"},[r("pre",{pre:!0,attrs:{class:"language-text"}},[r("code",[e._v('@RestController\npublic class CsrfController {\n\n    @RequestMapping("/csrf")\n    public CsrfToken csrf(CsrfToken token) {\n        return token;\n    }\n}\n')])])]),r("p",[e._v("Kotlin")]),e._v(" "),r("div",{staticClass:"language- extra-class"},[r("pre",{pre:!0,attrs:{class:"language-text"}},[r("code",[e._v('@RestController\nclass CsrfController {\n    @RequestMapping("/csrf")\n    fun csrf(token: CsrfToken): CsrfToken {\n        return token\n    }\n}\n')])])]),r("p",[e._v("JavaScript可以对端点进行REST调用,并使用响应来填充headername和令牌。")]),e._v(" "),r("p",[e._v("我们现在可以在我们的STOMP客户端中包含令牌。例如:")]),e._v(" "),r("div",{staticClass:"language- extra-class"},[r("pre",{pre:!0,attrs:{class:"language-text"}},[r("code",[e._v("...\nvar headers = {};\nheaders[headerName] = token;\nstompClient.connect(headers, function(frame) {\n  ...\n\n}\n")])])]),r("h3",{attrs:{id:"禁用websockets中的csrf"}},[r("a",{staticClass:"header-anchor",attrs:{href:"#禁用websockets中的csrf"}},[e._v("#")]),e._v(" 禁用WebSockets中的CSRF")]),e._v(" "),r("p",[e._v("如果你希望允许其他域访问你的站点,则可以禁用 Spring Security的保护。例如,在 Java 配置中,你可以使用以下方法:")]),e._v(" "),r("p",[e._v("Java")]),e._v(" "),r("div",{staticClass:"language- extra-class"},[r("pre",{pre:!0,attrs:{class:"language-text"}},[r("code",[e._v("@Configuration\npublic class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {\n\n    ...\n\n    @Override\n    protected boolean sameOriginDisabled() {\n        return true;\n    }\n}\n")])])]),r("p",[e._v("Kotlin")]),e._v(" "),r("div",{staticClass:"language- extra-class"},[r("pre",{pre:!0,attrs:{class:"language-text"}},[r("code",[e._v("@Configuration\nopen class WebSocketSecurityConfig : AbstractSecurityWebSocketMessageBrokerConfigurer() {\n\n    // ...\n\n    override fun sameOriginDisabled(): Boolean {\n        return true\n    }\n}\n")])])]),r("h2",{attrs:{id:"与sockjs合作"}},[r("a",{staticClass:"header-anchor",attrs:{href:"#与sockjs合作"}},[e._v("#")]),e._v(" 与Sockjs合作")]),e._v(" "),r("p",[r("a",{attrs:{href:"https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-fallback",target:"_blank",rel:"noopener noreferrer"}},[e._v("SockJS"),r("OutboundLink")],1),e._v("提供后备传输以支持较旧的浏览器。在使用后备选项时,我们需要放松一些安全约束,以允许Sockjs使用 Spring 安全性。")]),e._v(" "),r("h3",{attrs:{id:"sockjs-frame-选项"}},[r("a",{staticClass:"header-anchor",attrs:{href:"#sockjs-frame-选项"}},[e._v("#")]),e._v(" sockjs&frame-选项")]),e._v(" "),r("p",[e._v("Sockjs可以使用"),r("a",{attrs:{href:"https://github.com/sockjs/sockjs-client/tree/v0.3.4",target:"_blank",rel:"noopener noreferrer"}},[e._v("利用iframe的传输"),r("OutboundLink")],1),e._v("。 Spring 默认情况下,安全性将"),r("RouterLink",{attrs:{to:"/features/exploits/headers.html#headers-frame-options"}},[e._v("deny")]),e._v("网站框起来,以防止点击劫持攻击。为了允许基于SockJS帧的传输工作,我们需要配置 Spring 安全性,以允许相同的源来帧内容。")],1),e._v(" "),r("p",[e._v("你可以使用"),r("RouterLink",{attrs:{to:"/appendix/namespace/http.html#nsa-frame-options"}},[e._v("框架-选项")]),e._v("元素自定义X-frame-options。例如,下面将指示 Spring Security使用“X-Frame-Options:SameOrigin”,它允许在相同的域内使用IFrames:")],1),e._v(" "),r("div",{staticClass:"language- extra-class"},[r("pre",{pre:!0,attrs:{class:"language-text"}},[r("code",[e._v('<http>\n    \x3c!-- ... --\x3e\n\n    <headers>\n        <frame-options\n          policy="SAMEORIGIN" />\n    </headers>\n</http>\n')])])]),r("p",[e._v("类似地,你可以使用以下方式自定义框架选项,以便在 Java 配置中使用相同的原点:")]),e._v(" "),r("p",[e._v("Java")]),e._v(" "),r("div",{staticClass:"language- extra-class"},[r("pre",{pre:!0,attrs:{class:"language-text"}},[r("code",[e._v("@EnableWebSecurity\npublic class WebSecurityConfig extends\n   WebSecurityConfigurerAdapter {\n\n    @Override\n    protected void configure(HttpSecurity http) throws Exception {\n        http\n            // ...\n            .headers(headers -> headers\n                .frameOptions(frameOptions -> frameOptions\n                     .sameOrigin()\n                )\n        );\n    }\n}\n")])])]),r("p",[e._v("Kotlin")]),e._v(" "),r("div",{staticClass:"language- extra-class"},[r("pre",{pre:!0,attrs:{class:"language-text"}},[r("code",[e._v("@EnableWebSecurity\nopen class WebSecurityConfig : WebSecurityConfigurerAdapter() {\n    override fun configure(http: HttpSecurity) {\n        http {\n            // ...\n            headers {\n                frameOptions {\n                    sameOrigin = true\n                }\n            }\n        }\n    }\n}\n")])])]),r("h3",{attrs:{id:"sockjs-放松的csrf"}},[r("a",{staticClass:"header-anchor",attrs:{href:"#sockjs-放松的csrf"}},[e._v("#")]),e._v(" Sockjs&放松的CSRF")]),e._v(" "),r("p",[e._v("对于任何基于HTTP的传输,Sockjs都会在Connect消息上使用POST。通常,我们需要在HTTP报头或HTTP参数中包含CSRF令牌。然而,Sockjs不允许这些选项。相反,我们必须像"),r("a",{attrs:{href:"#websocket-sameorigin-csrf"}},[e._v("将CSRF添加到Stomp头")]),e._v("中描述的那样,在stomp头中包含令牌。")]),e._v(" "),r("p",[e._v("这也意味着我们需要放松对Web层的CSRF保护。具体地说,我们希望禁用我们的连接URL的CSRF保护。我们不希望禁用每个URL的CSRF保护。否则,我们的网站将容易受到CSRF攻击。")]),e._v(" "),r("p",[e._v("我们可以通过提供CSRF请求匹配器轻松地实现这一点。我们的 Java 配置使这一点变得非常容易。例如,如果我们的Stomp端点是“/chat”,那么我们可以使用以下配置,仅禁用以“/chat/”开头的URL的CSRF保护:")]),e._v(" "),r("p",[e._v("Java")]),e._v(" "),r("div",{staticClass:"language- extra-class"},[r("pre",{pre:!0,attrs:{class:"language-text"}},[r("code",[e._v('@Configuration\n@EnableWebSecurity\npublic class WebSecurityConfig\n    extends WebSecurityConfigurerAdapter {\n\n    @Override\n    protected void configure(HttpSecurity http) throws Exception {\n        http\n            .csrf(csrf -> csrf\n                // ignore our stomp endpoints since they are protected using Stomp headers\n                .ignoringAntMatchers("/chat/**")\n            )\n            .headers(headers -> headers\n                // allow same origin to frame our site to support iframe SockJS\n                .frameOptions(frameOptions -> frameOptions\n                    .sameOrigin()\n                )\n            )\n            .authorizeHttpRequests(authorize -> authorize\n                ...\n            )\n            ...\n')])])]),r("p",[e._v("Kotlin")]),e._v(" "),r("div",{staticClass:"language- extra-class"},[r("pre",{pre:!0,attrs:{class:"language-text"}},[r("code",[e._v('@Configuration\n@EnableWebSecurity\nopen class WebSecurityConfig : WebSecurityConfigurerAdapter() {\n    override fun configure(http: HttpSecurity) {\n        http {\n            csrf {\n                ignoringAntMatchers("/chat/**")\n            }\n            headers {\n                frameOptions {\n                    sameOrigin = true\n                }\n            }\n            authorizeRequests {\n                // ...\n            }\n            // ...\n')])])]),r("p",[e._v("如果我们使用基于XML的配置,我们可以使用[[电子邮件保护]](../acception/namespace/http.html#NSA-csrf-request-matcher-ref)。例如:")]),e._v(" "),r("div",{staticClass:"language- extra-class"},[r("pre",{pre:!0,attrs:{class:"language-text"}},[r("code",[e._v('<http ...>\n    <csrf request-matcher-ref="csrfMatcher"/>\n\n    <headers>\n        <frame-options policy="SAMEORIGIN"/>\n    </headers>\n\n    ...\n</http>\n\n<b:bean id="csrfMatcher"\n    class="AndRequestMatcher">\n    <b:constructor-arg value="#{T(org.springframework.security.web.csrf.CsrfFilter).DEFAULT_CSRF_MATCHER}"/>\n    <b:constructor-arg>\n        <b:bean class="org.springframework.security.web.util.matcher.NegatedRequestMatcher">\n          <b:bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">\n            <b:constructor-arg value="/chat/**"/>\n          </b:bean>\n        </b:bean>\n    </b:constructor-arg>\n</b:bean>\n')])])]),r("p",[r("RouterLink",{attrs:{to:"/spring-security/mvc.html"}},[e._v("Spring MVC")]),r("RouterLink",{attrs:{to:"/spring-security/cors.html"}},[e._v("Spring’s CORS Support")])],1)])}),[],!1,null,null,null);t.default=a.exports}}]);