Add RSocket Authentication Extension Support
Fixes gh-7935
This commit is contained in:
+102
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.rsocket.authentication;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.rsocket.metadata.WellKnownMimeType;
|
||||
import io.rsocket.metadata.security.AuthMetadataFlyweight;
|
||||
import io.rsocket.metadata.security.WellKnownAuthType;
|
||||
import org.springframework.core.codec.ByteArrayDecoder;
|
||||
import org.springframework.messaging.rsocket.DefaultMetadataExtractor;
|
||||
import org.springframework.messaging.rsocket.MetadataExtractor;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
|
||||
import org.springframework.security.rsocket.api.PayloadExchange;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Converts from the {@link PayloadExchange} for
|
||||
* <a href="https://github.com/rsocket/rsocket/blob/5920ed374d008abb712cb1fd7c9d91778b2f4a68/Extensions/Security/Authentication.md">Authentication Extension</a>.
|
||||
* For
|
||||
* <a href="https://github.com/rsocket/rsocket/blob/5920ed374d008abb712cb1fd7c9d91778b2f4a68/Extensions/Security/Simple.md">Simple</a>
|
||||
* a {@link UsernamePasswordAuthenticationToken} is returned. For
|
||||
* <a href="https://github.com/rsocket/rsocket/blob/5920ed374d008abb712cb1fd7c9d91778b2f4a68/Extensions/Security/Bearer.md">Bearer</a>
|
||||
* a {@link BearerTokenAuthenticationToken} is returned.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 5.3
|
||||
*/
|
||||
public class AuthenticationPayloadExchangeConverter implements PayloadExchangeAuthenticationConverter {
|
||||
private static final MimeType COMPOSITE_METADATA_MIME_TYPE = MimeTypeUtils.parseMimeType(
|
||||
WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA.getString());
|
||||
|
||||
private static final MimeType AUTHENTICATION_MIME_TYPE = MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());
|
||||
|
||||
private MetadataExtractor metadataExtractor = createDefaultExtractor();
|
||||
|
||||
@Override
|
||||
public Mono<Authentication> convert(PayloadExchange exchange) {
|
||||
return Mono.fromCallable(() -> this.metadataExtractor
|
||||
.extract(exchange.getPayload(), this.COMPOSITE_METADATA_MIME_TYPE))
|
||||
.flatMap(metadata -> Mono.justOrEmpty(authentication(metadata)));
|
||||
}
|
||||
|
||||
private Authentication authentication(Map<String, Object> metadata) {
|
||||
byte[] authenticationMetadata = (byte[]) metadata.get("authentication");
|
||||
if (authenticationMetadata == null) {
|
||||
return null;
|
||||
}
|
||||
ByteBuf rawAuthentication = ByteBufAllocator.DEFAULT.buffer().writeBytes(authenticationMetadata);
|
||||
if (!AuthMetadataFlyweight.isWellKnownAuthType(rawAuthentication)) {
|
||||
return null;
|
||||
}
|
||||
WellKnownAuthType wellKnownAuthType = AuthMetadataFlyweight.decodeWellKnownAuthType(rawAuthentication);
|
||||
if (WellKnownAuthType.SIMPLE.equals(wellKnownAuthType)) {
|
||||
return simple(rawAuthentication);
|
||||
} else if (WellKnownAuthType.BEARER.equals(wellKnownAuthType)) {
|
||||
return bearer(rawAuthentication);
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown Mime Type " + wellKnownAuthType);
|
||||
}
|
||||
|
||||
private Authentication simple(ByteBuf rawAuthentication) {
|
||||
ByteBuf rawUsername = AuthMetadataFlyweight.decodeUsername(rawAuthentication);
|
||||
String username = rawUsername.toString(StandardCharsets.UTF_8);
|
||||
ByteBuf rawPassword = AuthMetadataFlyweight.decodePassword(rawAuthentication);
|
||||
String password = rawPassword.toString(StandardCharsets.UTF_8);
|
||||
return new UsernamePasswordAuthenticationToken(username, password);
|
||||
}
|
||||
|
||||
private Authentication bearer(ByteBuf rawAuthentication) {
|
||||
char[] rawToken = AuthMetadataFlyweight.decodeBearerTokenAsCharArray(rawAuthentication);
|
||||
String token = new String(rawToken);
|
||||
return new BearerTokenAuthenticationToken(token);
|
||||
}
|
||||
|
||||
private static MetadataExtractor createDefaultExtractor() {
|
||||
DefaultMetadataExtractor result = new DefaultMetadataExtractor(new ByteArrayDecoder());
|
||||
result.metadataToExtract(AUTHENTICATION_MIME_TYPE, byte[].class, "authentication");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+2
@@ -31,7 +31,9 @@ import java.util.Map;
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 5.2
|
||||
* @deprecated Basic Authentication did not evolve into a standard. Use Simple Authentication instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class BasicAuthenticationDecoder extends AbstractDecoder<UsernamePasswordMetadata> {
|
||||
public BasicAuthenticationDecoder() {
|
||||
super(UsernamePasswordMetadata.BASIC_AUTHENTICATION_MIME_TYPE);
|
||||
|
||||
+2
@@ -34,7 +34,9 @@ import java.util.Map;
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 5.2
|
||||
* @deprecated Basic Authentication did not evolve into a standard. use {@link SimpleAuthenticationEncoder}
|
||||
*/
|
||||
@Deprecated
|
||||
public class BasicAuthenticationEncoder extends
|
||||
AbstractEncoder<UsernamePasswordMetadata> {
|
||||
|
||||
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.rsocket.metadata;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.rsocket.metadata.security.AuthMetadataFlyweight;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.codec.AbstractEncoder;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.NettyDataBufferFactory;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Encodes <a href="https://github.com/rsocket/rsocket/blob/5920ed374d008abb712cb1fd7c9d91778b2f4a68/Extensions/Security/Bearer.md">Bearer Authentication</a>.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 5.3
|
||||
*/
|
||||
public class BearerTokenAuthenticationEncoder extends
|
||||
AbstractEncoder<BearerTokenMetadata> {
|
||||
|
||||
private static final MimeType AUTHENTICATION_MIME_TYPE = MimeTypeUtils.parseMimeType("message/x.rsocket.authentication.v0");
|
||||
|
||||
private NettyDataBufferFactory defaultBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
|
||||
|
||||
public BearerTokenAuthenticationEncoder() {
|
||||
super(AUTHENTICATION_MIME_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<DataBuffer> encode(
|
||||
Publisher<? extends BearerTokenMetadata> inputStream,
|
||||
DataBufferFactory bufferFactory, ResolvableType elementType,
|
||||
MimeType mimeType, Map<String, Object> hints) {
|
||||
return Flux.from(inputStream).map(credentials ->
|
||||
encodeValue(credentials, bufferFactory, elementType, mimeType, hints));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer encodeValue(BearerTokenMetadata credentials,
|
||||
DataBufferFactory bufferFactory, ResolvableType valueType, MimeType mimeType,
|
||||
Map<String, Object> hints) {
|
||||
String token = credentials.getToken();
|
||||
NettyDataBufferFactory factory = nettyFactory(bufferFactory);
|
||||
ByteBufAllocator allocator = factory.getByteBufAllocator();
|
||||
ByteBuf simpleAuthentication = AuthMetadataFlyweight
|
||||
.encodeBearerMetadata(allocator, token.toCharArray());
|
||||
return factory.wrap(simpleAuthentication);
|
||||
}
|
||||
|
||||
private NettyDataBufferFactory nettyFactory(DataBufferFactory bufferFactory) {
|
||||
if (bufferFactory instanceof NettyDataBufferFactory) {
|
||||
return (NettyDataBufferFactory) bufferFactory;
|
||||
}
|
||||
return this.defaultBufferFactory;
|
||||
}
|
||||
}
|
||||
+2
@@ -32,7 +32,9 @@ public class BearerTokenMetadata {
|
||||
* Represents a bearer token which is encoded as a String.
|
||||
*
|
||||
* See <a href="https://github.com/rsocket/rsocket/issues/272">rsocket/rsocket#272</a>
|
||||
* @deprecated Basic did not evolve into the standard. Instead use Simple Authentication MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString())
|
||||
*/
|
||||
@Deprecated
|
||||
public static final MimeType BEARER_AUTHENTICATION_MIME_TYPE = new MediaType("message", "x.rsocket.authentication.bearer.v0");
|
||||
|
||||
private final String token;
|
||||
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.rsocket.metadata;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.rsocket.metadata.security.AuthMetadataFlyweight;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.codec.AbstractEncoder;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.NettyDataBufferFactory;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Encodes
|
||||
* <a href="https://github.com/rsocket/rsocket/blob/5920ed374d008abb712cb1fd7c9d91778b2f4a68/Extensions/Security/Simple.md">Simple</a>
|
||||
* Authentication.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 5.3
|
||||
*/
|
||||
public class SimpleAuthenticationEncoder extends
|
||||
AbstractEncoder<UsernamePasswordMetadata> {
|
||||
|
||||
private static final MimeType AUTHENTICATION_MIME_TYPE = MimeTypeUtils.parseMimeType("message/x.rsocket.authentication.v0");
|
||||
|
||||
private NettyDataBufferFactory defaultBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
|
||||
|
||||
public SimpleAuthenticationEncoder() {
|
||||
super(AUTHENTICATION_MIME_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<DataBuffer> encode(
|
||||
Publisher<? extends UsernamePasswordMetadata> inputStream,
|
||||
DataBufferFactory bufferFactory, ResolvableType elementType,
|
||||
MimeType mimeType, Map<String, Object> hints) {
|
||||
return Flux.from(inputStream).map(credentials ->
|
||||
encodeValue(credentials, bufferFactory, elementType, mimeType, hints));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer encodeValue(UsernamePasswordMetadata credentials,
|
||||
DataBufferFactory bufferFactory, ResolvableType valueType, MimeType mimeType,
|
||||
Map<String, Object> hints) {
|
||||
String username = credentials.getUsername();
|
||||
String password = credentials.getPassword();
|
||||
NettyDataBufferFactory factory = nettyFactory(bufferFactory);
|
||||
ByteBufAllocator allocator = factory.getByteBufAllocator();
|
||||
ByteBuf simpleAuthentication = AuthMetadataFlyweight
|
||||
.encodeSimpleMetadata(allocator, username.toCharArray(), password.toCharArray());
|
||||
return factory.wrap(simpleAuthentication);
|
||||
}
|
||||
|
||||
private NettyDataBufferFactory nettyFactory(DataBufferFactory bufferFactory) {
|
||||
if (bufferFactory instanceof NettyDataBufferFactory) {
|
||||
return (NettyDataBufferFactory) bufferFactory;
|
||||
}
|
||||
return this.defaultBufferFactory;
|
||||
}
|
||||
}
|
||||
+2
@@ -33,7 +33,9 @@ public final class UsernamePasswordMetadata {
|
||||
* {@code ${username-bytes-length}${username-bytes}${password-bytes}}.
|
||||
*
|
||||
* See <a href="https://github.com/rsocket/rsocket/issues/272">rsocket/rsocket#272</a>
|
||||
* @deprecated Basic did not evolve into the standard. Instead use Simple Authentication MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString())
|
||||
*/
|
||||
@Deprecated
|
||||
public static final MimeType BASIC_AUTHENTICATION_MIME_TYPE = new MediaType("message", "x.rsocket.authentication.basic.v0");
|
||||
|
||||
private final String username;
|
||||
|
||||
Reference in New Issue
Block a user