How to resolve authentication parameter with Spring messaging RSocket #ConnectMapping - spring

#ConnectMapping("setup")
public void setup(#AuthenticationPrincipal Principal principal) {
}
#MessageMapping("hello")
public void hello(#AuthenticationPrincipal Principal principal) {
}
The two mappings are on the server side.
When the RSocket client setup a connection with 'message/x.rsocket.authentication.v0' metadata,
and then send request to hello mapping.
The first principal is null.
The second principal is the expected authentication data.
How to resolve the principal in #ConnectMapping?

Work properly with spring-security-rsocket version 5.3.4.RELEASE

You need to add message handler with custom resolver which will include AuthenticationPrincipalArgumentResolver from package org.springframework.security.messaging.handler.invocation.reactive
may need to add dependency
org.springframework.security:spring-security-messaging
to your maven/gradle
my example for kotlin:
#Bean
fun messageHandler(strategies: RSocketStrategies?): RSocketMessageHandler? {
val handler = RSocketMessageHandler()
handler.argumentResolverConfigurer.addCustomResolver(AuthenticationPrincipalArgumentResolver())
handler.rSocketStrategies = strategies
return handler
}

Related

Duplicate config code problem Spring Boot microservices

Is there a way to create a spring configuration class and use it to all my microservices?
For example, I have to copy the following configuration class and paste it through all my microservices which means that when I want to make a small change I have to go through all the microservices editing the same class.
I have investigated and the most I could find is to create a module with all the classes in common and import it in my microservices by the pom, what happens with this is that when I want to get the SecurityContextHolder.getContext() this is null for a context issue and I do not know very well how to give solution to this or what other alternatives there are.
#Configuration
public class FeignGlobalConfiguration {
#Bean
public ErrorDecoder errorDecoder() {
return new RetrieveMessageErrorDecoder();
}
#Bean
public RequestInterceptor requestInterceptor(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return requestTemplate -> {
requestTemplate.header(JwtClaims.USERNAME, authentication.getPrincipal().toString());
requestTemplate.header(JwtClaims.CLIENT, authentication.getDetails().toString());
requestTemplate.header(JwtClaims.TOKEN, authentication.getCredentials().toString());
requestTemplate.header(JwtClaims.ROLES, authentication.getAuthorities().toString());
};
}
}
The problem is your bean definition.
The line Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); is called when the bean is constructed so only once. After that the reference is used (which is probably null at the time of construction.
To solve move tha tline inside the lambda so it gets evaluated each time a request is processed.
#Bean
public RequestInterceptor requestInterceptor(){
return requestTemplate -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
requestTemplate.header(JwtClaims.USERNAME, authentication.getPrincipal().toString());
requestTemplate.header(JwtClaims.CLIENT, authentication.getDetails().toString());
requestTemplate.header(JwtClaims.TOKEN, authentication.getCredentials().toString());
requestTemplate.header(JwtClaims.ROLES, authentication.getAuthorities().toString());
};
}

Spring RSocket over WebSocket - Access user information from HTTP session

In my web application, users login using a username/password combination and get a session cookie. When initiating a WebSocket connection, I can easily access the user information in the WebSocketHandler, for example:
#Component
public class MyWebSocketHandler implements WebSocketHandler {
#Override
public Mono<Void> handle(WebSocketSession session) {
// two ways to access security context information, either like this:
Mono<Principal> principal = session.getHandshakeInfo().getPrincipal();
// or like this
Mono<SecurityContext> context = ReactiveSecurityContextHolder.getContext();
//...
return Mono.empty();
}
}
Both reuse the HTTP session from the WebSocket handshake, I don't have to send additional authentication over the WebSocket itself. With STOMP the same thing applies: I can just reuse the information of the HTTP session.
How do I achieve the same thing using RSocket? For example, how would I get information about the user inside a MessageMapping method like this?:
#Controller
public class RSocketController {
#MessageMapping("test-stream")
public Flux<String> streamTest(RSocketRequester requester) {
// this mono completes empty, no security context available :(
Mono<SecurityContext> context = ReactiveSecurityContextHolder.getContext();
return Flux.empty();
}
}
I found many resources how to setup authentication with RSocket, but they all rely on an additional authentication after the WebSocket connection is established, but I specifically want to reuse the web session and don't want to send additional tokens over the websocket.
Have you tried the following? I found it in the documentation: 2.2 Secure Your RSocket Methods (might have to scroll down a bit) https://spring.io/blog/2020/06/17/getting-started-with-rsocket-spring-security
#PreAuthorize("hasRole('USER')") // (1)
#MessageMapping("fire-and-forget")
public Mono<Void> fireAndForget(final Message request, #AuthenticationPrincipal UserDetails user) { // (2)
log.info("Received fire-and-forget request: {}", request);
log.info("Fire-And-Forget initiated by '{}' in the role '{}'", user.getUsername(), user.getAuthorities());
return Mono.empty();
}
You can get the user information using #AuthenticationPrincipal Mono<UserDetails> userDetails.
In case someone use JWT authentication as me you need to add #AuthenticationPrincipal Mono<Jwt> jwt to your method arguments.
But for this to work, you need to configure the RSocketMessageHandler bean, that resolvs the argument.
#Bean
public RSocketMessageHandler rSocketMessageHandler(RSocketStrategies strategies) {
RSocketMessageHandler handler = new RSocketMessageHandler();
handler.getArgumentResolverConfigurer()
.addCustomResolver(new AuthenticationPrincipalArgumentResolver());
handler.setRSocketStrategies(strategies);
return handler;
}
Important you have to use org.springframework.security.messaging.handler.invocation.reactive.AuthenticationPrincipalArgumentResolver() class as the resolver, and for that you need spring-security-messaging dependency.

Spring Boot Webflux Security - reading Principal in service class when writing tests

I am quite new to the Spring ecosystem in general and Webflux. There are 2 things that I am trying to figure out and cannot find any specifics about.
My Setup:
I am writing a Spring Boot 2 REST API using WebFlux (not using controllers but rather handler functions). The authentication server is a separate service which issues JWT tokens and those get attached to each request as Authentication headers. Here is a simple example of a request method:
public Mono<ServerResponse> all(ServerRequest serverRequest) {
return principal(serverRequest).flatMap(principal ->
ReactiveResponses.listResponse(this.projectService.all(principal)));
}
Which i use to react to a GET request for a list of all "Projects" that a user has access to.
I afterwards have a service which retrieves the list of projects for this user and i render a json response.
The Problems:
Now in order to filter the projects based on the current user id i need to read it from the request principal. One issue here is that i have plenty service methods which need the current user information and passing it through to the service seems like an overkill. One solution is to read the principal inside the service from:
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Question 1:
Is this a good practice in general when writing functional code (If i do this instead of propagating the principal)? is this a good approach despite the complexity of reading and sending the principal from the request to the service in each method?
Question 2:
Should i instead use the SecurityContextHolder Thread Local to fetch the principal, and if i do that how do i write tests for my service?
If i use the Security Context how do i test my service implementations which are expecting a principal that is of type JWTAuthenticationToken
and i always get null when trying to do something like described here: Unit testing with Spring Security
In the service tests, In tests what i've managed to do so far is to propagate the principal to the service methods and use mockito to mock the principal. This is quite straightforward.
In the Endpoint Tests i am using #WithMockUser to populate the principal when doing requests and i mock out the service layer. This has the downside of the principal type being different.
Here is how my test class for the service layer looks:
#DataMongoTest
#Import({ProjectServiceImpl.class})
class ProjectServiceImplTest extends BaseServiceTest {
#Autowired
ProjectServiceImpl projectService;
#Autowired
ProjectRepository projectRepository;
#Mock
Principal principal;
#Mock
Principal principal2;
#BeforeEach
void setUp() {
initMocks(this);
when(principal.getName()).thenReturn("uuid");
when(principal2.getName()).thenReturn("uuid2");
}
// Cleaned for brevity
#Test
public void all_returnsOnlyOwnedProjects() {
Flux<Project> saved = projectRepository.saveAll(
Flux.just(
new Project(null, "First", "uuid"),
new Project(null, "Second", "uuid2"),
new Project(null, "Third", "uuid3")
)
);
Flux<Project> all = projectService.all(principal2);
Flux<Project> composite = saved.thenMany(all);
StepVerifier
.create(composite)
.consumeNextWith(project -> {
assertThat(project.getOwnerUserId()).isEqualTo("uuid2");
})
.verifyComplete();
}
}
Based on the other answer, i managed to solve this problem in the following way.
I added the following methods to read the id from claims where it normally resides within the JWT token.
public static Mono<String> currentUserId() {
return jwt().map(jwt -> jwt.getClaimAsString(USER_ID_CLAIM_NAME));
}
public static Mono<Jwt> jwt() {
return ReactiveSecurityContextHolder.getContext()
.map(context -> context.getAuthentication().getPrincipal())
.cast(Jwt.class);
}
Then i use this within my services wherever needed, and i am not forwarding it through the handler to the service.
The tricky part was always testing. I am able to resolve this using the custom SecurityContextFactory. I created an annotation which i can attach the same way as #WithMockUser, but with some of the claim details i need instead.
#Retention(RetentionPolicy.RUNTIME)
#WithSecurityContext(factory = WithMockTokenSecurityContextFactory.class)
public #interface WithMockToken {
String sub() default "uuid";
String email() default "test#test.com";
String name() default "Test User";
}
Then the Factory:
String token = "....ANY_JWT_TOKEN_GOES_HERE";
#Override
public SecurityContext createSecurityContext(WithMockToken tokenAnnotation) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
HashMap<String, Object> headers = new HashMap<>();
headers.put("kid", "SOME_ID");
headers.put("typ", "JWT");
headers.put("alg", "RS256");
HashMap<String, Object> claims = new HashMap<>();
claims.put("sub", tokenAnnotation.sub());
claims.put("aud", new ArrayList<>() {{
add("SOME_ID_HERE");
}});
claims.put("updated_at", "2019-06-24T12:16:17.384Z");
claims.put("nickname", tokenAnnotation.email().substring(0, tokenAnnotation.email().indexOf("#")));
claims.put("name", tokenAnnotation.name());
claims.put("exp", new Date());
claims.put("iat", new Date());
claims.put("email", tokenAnnotation.email());
Jwt jwt = new Jwt(token, Instant.now(), Instant.now().plus(1, ChronoUnit.HOURS), headers,
claims);
JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(jwt, AuthorityUtils.NO_AUTHORITIES); // Authorities are needed to pass authentication in the Integration tests
context.setAuthentication(jwtAuthenticationToken);
return context;
}
Then a simple test will look like this:
#Test
#WithMockToken(sub = "uuid2")
public void delete_whenNotOwner() {
Mono<Void> deleted = this.projectService.create(projectDTO)
.flatMap(saved -> this.projectService.delete(saved.getId()));
StepVerifier
.create(deleted)
.verifyError(ProjectDeleteNotAllowedException.class);
}
As you are using Webflux you should be using the ReactiveSecurityContextHolder to retrieve the principal like so : Object principal = ReactiveSecurityContextHolder.getContext().getAuthentication().getPrincipal();
The use of the non-reactive one will return null as you are seeing.
There is more info related to the topic in this answer - https://stackoverflow.com/a/51350355/197342

Spring Cloud : Using routing type filter in Zuul

I have 2 micro-services (Service A and Service B) built using Spring Boot, which gets routed through a Zuul Proxy also built as a Spring Boot app and I have checked that the Zuul proxy works just fine. However, what I am trying to do is to write a custom routing type ZuulFilter which should first route to Service A when a request comes in for Service B. Here is what I need assistance for:
I would like to know an example of how a routing filter looks like as I do not see anything after searching the internet. What I get are some examples of pre-filter and Netflix's documentation doesn't help much as well on that aspect.
Whether writing a custom route filter would mess up the original routing behavior of Zuul
I would construct a Feign client in the Zuul filter and make the call to service A using it. Feign will populate a ribbon load balancer to make the call in just the same way that Zuul does when proxying.
I had the same issue and this is what I came up with.
public class ServicesLegacyRouteFilter extends ZuulFilter {
private ServiceB serviceB;
public ServiceLegacyRouteFilter(ServiceB serviceB) {
this.serviceB = serviceB;
}
#Override
public String filterType() {
return ROUTE_TYPE;
}
#Override
public int filterOrder() {
return 10;
}
#Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
if ("serviceA".equals(ctx.get("serviceId"))) {
//call Service B here and use return type to set
//the final destination service
String destination = serviceB.routeWhere();
ctx.set("serviceId", destination);
return true;
}
return false;
}
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
// Or call ServiceB here to make your determination on
// the final destination.
String destination = serviceB.routeWhere();
ctx.set("serviceId", destination);
return null;
}
}
My actual production use case was more complicated on the routing of course, but this is the basics of how I was able to change routes based on what was coming in and how to take advantage of Zuul to get it out to the correct service.

RestEasy client spring integration: can not auto follow redirects

Problem: I can not get RestEasy to automatically follow redirects
I'm using the RestEasy client framework 2.3.4 to consume RESTful JSON services. I'm using the rest easy client spring integration. If I wasn't using spring RestClientProxyFactoryBean to create my services I would set the auto redirect flag on the client request factory
I have tried setting the follow redirect on my HTTP client and following the debug I can see this value is overridden to false by Rest Easy.
Looking at the source code I need to get access to the client invoker that the spring proxy factory creates but it doesn't expose this.
This is like a very common task, surely I am missing something? Cheers.
You should be able to set a custom client executor on the proxybean factory but that also didn't work e.g
#Override
public ClientRequest createRequest(String uriTemplate) {
ClientRequest clientRequest = new ClientRequest(uriTemplate, this);
clientRequest.followRedirects(true);
return clientRequest;
}
#Override
public ClientRequest createRequest(UriBuilder uriBuilder) {
ClientRequest clientRequest = super.createRequest(uriBuilder);
clientRequest.followRedirects(true);
return clientRequest;
}
}
proxyFactoryBean.setClientExecutor(new FollowRedirectsClientExecutor());
In end extending and overriding the Http client (in this case HTTP Component) was needed to make this work e.g.
public HttpUriRequest followRedirects(HttpUriRequest request) {
if (logger.isDebugEnabled()) {
logger.debug("Setting allow redirects");
}
HttpParams p = request.getParams();
HttpClientParams.setRedirecting(p, true);
request.setParams(p);
return request;
}
}
...
#Override
public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler) throw
s IOException,
ClientProtocolException { ClientProtocolException {
request = followRedirects(request);
...

Resources