(window.webpackJsonp=window.webpackJsonp||[]).push([[194],{620:function(e,t,a){"use strict";a.r(t);var n=a(56),r=Object(n.a)({},(function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("ContentSlotsDistributor",{attrs:{"slot-key":e.$parent.slotKey}},[a("h1",{attrs:{id:"rsocket-security"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#rsocket-security"}},[e._v("#")]),e._v(" RSocket Security")]),e._v(" "),a("p",[e._v("Spring Security’s RSocket support relies on a "),a("code",[e._v("SocketAcceptorInterceptor")]),e._v(".\nThe main entry point into security is found in the "),a("code",[e._v("PayloadSocketAcceptorInterceptor")]),e._v(" which adapts the RSocket APIs to allow intercepting a "),a("code",[e._v("PayloadExchange")]),e._v(" with "),a("code",[e._v("PayloadInterceptor")]),e._v(" implementations.")]),e._v(" "),a("p",[e._v("You can find a few sample applications that demonstrate the code below:")]),e._v(" "),a("ul",[a("li",[a("p",[e._v("Hello RSocket "),a("a",{attrs:{href:"https://github.com/spring-projects/spring-security-samples/tree/5.6.x/reactive/rsocket/hello-security",target:"_blank",rel:"noopener noreferrer"}},[e._v("hellorsocket"),a("OutboundLink")],1)])]),e._v(" "),a("li",[a("p",[a("a",{attrs:{href:"https://github.com/rwinch/spring-flights/tree/security",target:"_blank",rel:"noopener noreferrer"}},[e._v("Spring Flights"),a("OutboundLink")],1)])])]),e._v(" "),a("h2",{attrs:{id:"minimal-rsocket-security-configuration"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#minimal-rsocket-security-configuration"}},[e._v("#")]),e._v(" Minimal RSocket Security Configuration")]),e._v(" "),a("p",[e._v("You can find a minimal RSocket Security configuration below:")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('@Configuration\n@EnableRSocketSecurity\npublic class HelloRSocketSecurityConfig {\n\n\t@Bean\n\tpublic MapReactiveUserDetailsService userDetailsService() {\n\t\tUserDetails user = User.withDefaultPasswordEncoder()\n\t\t\t.username("user")\n\t\t\t.password("user")\n\t\t\t.roles("USER")\n\t\t\t.build();\n\t\treturn new MapReactiveUserDetailsService(user);\n\t}\n}\n')])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('@Configuration\n@EnableRSocketSecurity\nopen class HelloRSocketSecurityConfig {\n @Bean\n open fun userDetailsService(): MapReactiveUserDetailsService {\n val user = User.withDefaultPasswordEncoder()\n .username("user")\n .password("user")\n .roles("USER")\n .build()\n return MapReactiveUserDetailsService(user)\n }\n}\n')])])]),a("p",[e._v("This configuration enables "),a("a",{attrs:{href:"#rsocket-authentication-simple"}},[e._v("simple authentication")]),e._v(" and sets up "),a("a",{attrs:{href:"#rsocket-authorization"}},[e._v("rsocket-authorization")]),e._v(" to require an authenticated user for any request.")]),e._v(" "),a("h2",{attrs:{id:"adding-securitysocketacceptorinterceptor"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#adding-securitysocketacceptorinterceptor"}},[e._v("#")]),e._v(" Adding SecuritySocketAcceptorInterceptor")]),e._v(" "),a("p",[e._v("For Spring Security to work we need to apply "),a("code",[e._v("SecuritySocketAcceptorInterceptor")]),e._v(" to the "),a("code",[e._v("ServerRSocketFactory")]),e._v(".\nThis is what connects our "),a("code",[e._v("PayloadSocketAcceptorInterceptor")]),e._v(" we created with the RSocket infrastructure.\nIn a Spring Boot application this is done automatically using "),a("code",[e._v("RSocketSecurityAutoConfiguration")]),e._v(" with the following code.")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("@Bean\nRSocketServerCustomizer springSecurityRSocketSecurity(SecuritySocketAcceptorInterceptor interceptor) {\n return (server) -> server.interceptors((registry) -> registry.forSocketAcceptor(interceptor));\n}\n")])])]),a("h2",{attrs:{id:"rsocket-authentication"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#rsocket-authentication"}},[e._v("#")]),e._v(" RSocket Authentication")]),e._v(" "),a("p",[e._v("RSocket authentication is performed with "),a("code",[e._v("AuthenticationPayloadInterceptor")]),e._v(" which acts as a controller to invoke a "),a("code",[e._v("ReactiveAuthenticationManager")]),e._v(" instance.")]),e._v(" "),a("h3",{attrs:{id:"authentication-at-setup-vs-request-time"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#authentication-at-setup-vs-request-time"}},[e._v("#")]),e._v(" Authentication at Setup vs Request Time")]),e._v(" "),a("p",[e._v("Generally, authentication can occur at setup time and/or request time.")]),e._v(" "),a("p",[e._v("Authentication at setup time makes sense in a few scenarios.\nA common scenarios is when a single user (i.e. mobile connection) is leveraging an RSocket connection.\nIn this case only a single user is leveraging the connection, so authentication can be done once at connection time.")]),e._v(" "),a("p",[e._v("In a scenario where the RSocket connection is shared it makes sense to send credentials on each request.\nFor example, a web application that connects to an RSocket server as a downstream service would make a single connection that all users leverage.\nIn this case, if the RSocket server needs to perform authorization based on the web application’s users credentials per request makes sense.")]),e._v(" "),a("p",[e._v("In some scenarios authentication at setup and per request makes sense.\nConsider a web application as described previously.\nIf we need to restrict the connection to the web application itself, we can provide a credential with a "),a("code",[e._v("SETUP")]),e._v(" authority at connection time.\nThen each user would have different authorities but not the "),a("code",[e._v("SETUP")]),e._v(" authority.\nThis means that individual users can make requests but not make additional connections.")]),e._v(" "),a("h3",{attrs:{id:"simple-authentication"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#simple-authentication"}},[e._v("#")]),e._v(" Simple Authentication")]),e._v(" "),a("p",[e._v("Spring Security has support for "),a("a",{attrs:{href:"https://github.com/rsocket/rsocket/blob/5920ed374d008abb712cb1fd7c9d91778b2f4a68/Extensions/Security/Simple.md",target:"_blank",rel:"noopener noreferrer"}},[e._v("Simple Authentication Metadata Extension"),a("OutboundLink")],1),e._v(".")]),e._v(" "),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("Basic Authentication drafts evolved into Simple Authentication and is only supported for backward compatibility."),a("br"),e._v("See "),a("code",[e._v("RSocketSecurity.basicAuthentication(Customizer)")]),e._v(" for setting it up.")])])]),e._v(" "),a("tbody")]),e._v(" "),a("p",[e._v("The RSocket receiver can decode the credentials using "),a("code",[e._v("AuthenticationPayloadExchangeConverter")]),e._v(" which is automatically setup using the "),a("code",[e._v("simpleAuthentication")]),e._v(" portion of the DSL.\nAn explicit configuration can be found below.")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("@Bean\nPayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {\n\trsocket\n\t\t.authorizePayload(authorize ->\n\t\t\tauthorize\n\t\t\t\t\t.anyRequest().authenticated()\n\t\t\t\t\t.anyExchange().permitAll()\n\t\t)\n\t\t.simpleAuthentication(Customizer.withDefaults());\n\treturn rsocket.build();\n}\n")])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("@Bean\nopen fun rsocketInterceptor(rsocket: RSocketSecurity): PayloadSocketAcceptorInterceptor {\n rsocket\n .authorizePayload { authorize -> authorize\n .anyRequest().authenticated()\n .anyExchange().permitAll()\n }\n .simpleAuthentication(withDefaults())\n return rsocket.build()\n}\n")])])]),a("p",[e._v("The RSocket sender can send credentials using "),a("code",[e._v("SimpleAuthenticationEncoder")]),e._v(" which can be added to Spring’s "),a("code",[e._v("RSocketStrategies")]),e._v(".")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("RSocketStrategies.Builder strategies = ...;\nstrategies.encoder(new SimpleAuthenticationEncoder());\n")])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("var strategies: RSocketStrategies.Builder = ...\nstrategies.encoder(SimpleAuthenticationEncoder())\n")])])]),a("p",[e._v("It can then be used to send a username and password to the receiver in the setup:")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('MimeType authenticationMimeType =\n\tMimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());\nUsernamePasswordMetadata credentials = new UsernamePasswordMetadata("user", "password");\nMono requester = RSocketRequester.builder()\n\t.setupMetadata(credentials, authenticationMimeType)\n\t.rsocketStrategies(strategies.build())\n\t.connectTcp(host, port);\n')])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('val authenticationMimeType: MimeType =\n MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.string)\nval credentials = UsernamePasswordMetadata("user", "password")\nval requester: Mono = RSocketRequester.builder()\n .setupMetadata(credentials, authenticationMimeType)\n .rsocketStrategies(strategies.build())\n .connectTcp(host, port)\n')])])]),a("p",[e._v("Alternatively or additionally, a username and password can be sent in a request.")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('Mono requester;\nUsernamePasswordMetadata credentials = new UsernamePasswordMetadata("user", "password");\n\npublic Mono findRadar(String code) {\n\treturn this.requester.flatMap(req ->\n\t\treq.route("find.radar.{code}", code)\n\t\t\t.metadata(credentials, authenticationMimeType)\n\t\t\t.retrieveMono(AirportLocation.class)\n\t);\n}\n')])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('import org.springframework.messaging.rsocket.retrieveMono\n\n// ...\n\nvar requester: Mono? = null\nvar credentials = UsernamePasswordMetadata("user", "password")\n\nopen fun findRadar(code: String): Mono {\n return requester!!.flatMap { req ->\n req.route("find.radar.{code}", code)\n .metadata(credentials, authenticationMimeType)\n .retrieveMono()\n }\n}\n')])])]),a("h3",{attrs:{id:"jwt"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#jwt"}},[e._v("#")]),e._v(" JWT")]),e._v(" "),a("p",[e._v("Spring Security has support for "),a("a",{attrs:{href:"https://github.com/rsocket/rsocket/blob/5920ed374d008abb712cb1fd7c9d91778b2f4a68/Extensions/Security/Bearer.md",target:"_blank",rel:"noopener noreferrer"}},[e._v("Bearer Token Authentication Metadata Extension"),a("OutboundLink")],1),e._v(".\nThe support comes in the form of authenticating a JWT (determining the JWT is valid) and then using the JWT to make authorization decisions.")]),e._v(" "),a("p",[e._v("The RSocket receiver can decode the credentials using "),a("code",[e._v("BearerPayloadExchangeConverter")]),e._v(" which is automatically setup using the "),a("code",[e._v("jwt")]),e._v(" portion of the DSL.\nAn example configuration can be found below:")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("@Bean\nPayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {\n\trsocket\n\t\t.authorizePayload(authorize ->\n\t\t\tauthorize\n\t\t\t\t.anyRequest().authenticated()\n\t\t\t\t.anyExchange().permitAll()\n\t\t)\n\t\t.jwt(Customizer.withDefaults());\n\treturn rsocket.build();\n}\n")])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("@Bean\nfun rsocketInterceptor(rsocket: RSocketSecurity): PayloadSocketAcceptorInterceptor {\n rsocket\n .authorizePayload { authorize -> authorize\n .anyRequest().authenticated()\n .anyExchange().permitAll()\n }\n .jwt(withDefaults())\n return rsocket.build()\n}\n")])])]),a("p",[e._v("The configuration above relies on the existence of a "),a("code",[e._v("ReactiveJwtDecoder")]),e._v(" "),a("code",[e._v("@Bean")]),e._v(" being present.\nAn example of creating one from the issuer can be found below:")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('@Bean\nReactiveJwtDecoder jwtDecoder() {\n\treturn ReactiveJwtDecoders\n\t\t.fromIssuerLocation("https://example.com/auth/realms/demo");\n}\n')])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('@Bean\nfun jwtDecoder(): ReactiveJwtDecoder {\n return ReactiveJwtDecoders\n .fromIssuerLocation("https://example.com/auth/realms/demo")\n}\n')])])]),a("p",[e._v("The RSocket sender does not need to do anything special to send the token because the value is just a simple String.\nFor example, the token can be sent at setup time:")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("MimeType authenticationMimeType =\n\tMimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());\nBearerTokenMetadata token = ...;\nMono requester = RSocketRequester.builder()\n\t.setupMetadata(token, authenticationMimeType)\n\t.connectTcp(host, port);\n")])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("val authenticationMimeType: MimeType =\n MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.string)\nval token: BearerTokenMetadata = ...\n\nval requester = RSocketRequester.builder()\n .setupMetadata(token, authenticationMimeType)\n .connectTcp(host, port)\n")])])]),a("p",[e._v("Alternatively or additionally, the token can be sent in a request.")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('MimeType authenticationMimeType =\n\tMimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());\nMono requester;\nBearerTokenMetadata token = ...;\n\npublic Mono findRadar(String code) {\n\treturn this.requester.flatMap(req ->\n\t\treq.route("find.radar.{code}", code)\n\t .metadata(token, authenticationMimeType)\n\t\t\t.retrieveMono(AirportLocation.class)\n\t);\n}\n')])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('val authenticationMimeType: MimeType =\n MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.string)\nvar requester: Mono? = null\nval token: BearerTokenMetadata = ...\n\nopen fun findRadar(code: String): Mono {\n return this.requester!!.flatMap { req ->\n req.route("find.radar.{code}", code)\n .metadata(token, authenticationMimeType)\n .retrieveMono()\n }\n}\n')])])]),a("h2",{attrs:{id:"rsocket-authorization"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#rsocket-authorization"}},[e._v("#")]),e._v(" RSocket Authorization")]),e._v(" "),a("p",[e._v("RSocket authorization is performed with "),a("code",[e._v("AuthorizationPayloadInterceptor")]),e._v(" which acts as a controller to invoke a "),a("code",[e._v("ReactiveAuthorizationManager")]),e._v(" instance.\nThe DSL can be used to setup authorization rules based upon the "),a("code",[e._v("PayloadExchange")]),e._v(".\nAn example configuration can be found below:")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('rsocket\n\t.authorizePayload(authz ->\n\t\tauthz\n\t\t\t.setup().hasRole("SETUP") (1)\n\t\t\t.route("fetch.profile.me").authenticated() (2)\n\t\t\t.matcher(payloadExchange -> isMatch(payloadExchange)) (3)\n\t\t\t\t.hasRole("CUSTOM")\n\t\t\t.route("fetch.profile.{username}") (4)\n\t\t\t\t.access((authentication, context) -> checkFriends(authentication, context))\n\t\t\t.anyRequest().authenticated() (5)\n\t\t\t.anyExchange().permitAll() (6)\n\t);\n')])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('rsocket\n .authorizePayload { authz ->\n authz\n .setup().hasRole("SETUP") (1)\n .route("fetch.profile.me").authenticated() (2)\n .matcher { payloadExchange -> isMatch(payloadExchange) } (3)\n .hasRole("CUSTOM")\n .route("fetch.profile.{username}") (4)\n .access { authentication, context -> checkFriends(authentication, context) }\n .anyRequest().authenticated() (5)\n .anyExchange().permitAll()\n } (6)\n')])])]),a("table",[a("thead",[a("tr",[a("th",[a("strong",[e._v("1")])]),e._v(" "),a("th",[e._v("Setting up a connection requires the authority "),a("code",[e._v("ROLE_SETUP")])])])]),e._v(" "),a("tbody",[a("tr",[a("td",[a("strong",[e._v("2")])]),e._v(" "),a("td",[e._v("If the route is "),a("code",[e._v("fetch.profile.me")]),e._v(" authorization only requires the user be authenticated")])]),e._v(" "),a("tr",[a("td",[a("strong",[e._v("3")])]),e._v(" "),a("td",[e._v("In this rule we setup a custom matcher where authorization requires the user to have the authority "),a("code",[e._v("ROLE_CUSTOM")])])]),e._v(" "),a("tr",[a("td",[a("strong",[e._v("4")])]),e._v(" "),a("td",[e._v("This rule leverages custom authorization."),a("br"),e._v("The matcher expresses a variable with the name "),a("code",[e._v("username")]),e._v(" that is made available in the "),a("code",[e._v("context")]),e._v("."),a("br"),e._v("A custom authorization rule is exposed in the "),a("code",[e._v("checkFriends")]),e._v(" method.")])]),e._v(" "),a("tr",[a("td",[a("strong",[e._v("5")])]),e._v(" "),a("td",[e._v("This rule ensures that request that does not already have a rule will require the user to be authenticated."),a("br"),e._v("A request is where the metadata is included."),a("br"),e._v("It would not include additional payloads.")])]),e._v(" "),a("tr",[a("td",[a("strong",[e._v("6")])]),e._v(" "),a("td",[e._v("This rule ensures that any exchange that does not already have a rule is allowed for anyone."),a("br"),e._v("In this example, it means that payloads that have no metadata have no authorization rules.")])])])]),e._v(" "),a("p",[e._v("It is important to understand that authorization rules are performed in order.\nOnly the first authorization rule that matches will be invoked.")]),e._v(" "),a("p",[a("RouterLink",{attrs:{to:"/en/spring-security/cors.html"}},[e._v("CORS")]),a("RouterLink",{attrs:{to:"/en/test/index.html"}},[e._v("Testing")])],1)])}),[],!1,null,null,null);t.default=r.exports}}]);