Extracting Principal data using ReactiveSecurityContextHolder - java

I have a Spring webflux microservice, with working OAuth authentication in place.
I can access the Principal using the #AuthenticationPrincipal annotation in the controller method parameters:
#GetMapping("/user-info")
public #ResponseBody User getUserInfo(#AuthenticationPrincipal Principal principal) {
return service.getMe(principal);
}
This works fine.
But what I want, is to access the Principal directly, using ReactiveSecurityContextHolder, using something like:
#Override
public Principal getLoggedUser() {
Mono<Principal> authentication=
ReactiveSecurityContextHolder.getContext().filter(context ->
Objects.nonNull(context.getAuthentication()))
.map(context -> context.getAuthentication().getPrincipal())
.cast(Principal.class);
return authentication.share().block();
}
I need to block, because I need some Principal data to access a non reactive database, loading the corresponding User.
For some reason, the above snippet always returns null. I tried with
return authentication.toFuture().get();
and got the same result.

Related

How to make common authorization call in SecurityConfig class to authorize every request in SpringBoot

I am new to Spring Boot Security. I am performing validation of licenseKey in every end-point in REST call. It is working fine.
I want to do it in a common way like SecurityConfig extends WebSecurityConfigurerAdapter {} class so that I should not pass extra parameter in methods for validation. It means there should be common validation if the licenseKey exists then the REST calls should be authorized to go otherwise, it should throw error. Currently I am passing HttpServletRequest which contains licenseKey, the methods are working fine. But our requirement is to perform only in one place in SecurityConfig so that all the requests can be validated.
#GetMapping(path="some/path")
public ResponseEntity<> viewDetails(HttpServletRequest httpRequest, MappingVO mappingVO) {
String licenseUser = userDetailsService.getLicenseUser(httpRequest).getUser().getEmailAddress();
....
....
}
#DeleteMapping(path="some/path")
public ResponseEntity<> deletePart(HttpServletRequest httpRequest, Part part) {
String licenseUser = userDetailsService.getLicenseUser(httpRequest).getUser().getEmailAddress();
....
....
}
In class CustomUserDetails, it has been written like this.
public CustomUserDetails getLicenseUser(HttpServletRequest httpRequest) {
String userId = httpRequest.getHeader("licenseKey");
CustomUserDetails ud = (CustomUserDetails) loadUserByUsername(userId);
return ud;
}
You should add a custom filter in the filter chain in your security config that executes before each request.
Just create a Custom Filter implementing OncePerRequestFilter.java and do the license Key validation inside of that filter. The implementation logic inside your Custom filter will run once before each request that is made on your spring boot server.
Refer
https://www.baeldung.com/spring-onceperrequestfilter
https://www.javadevjournal.com/spring-boot/spring-boot-add-filter/
If you are using Spring Security and want to filter requests to verify that it has valid token passed to it (if you're not already doing this), refer to the official documentation of your respective version of spring security to see where should you add your filter inside the filterChain.
Check Filter Ordering in:
https://docs.spring.io/spring-security/site/docs/3.1.4.RELEASE/reference/security-filter-chain.html
The token validation filter should ideally be exeucted before UsernamePasswordAuthenticationFilter.

Spring Boot + Keycloak: optional auth endpoint

I'm trying to configure a Spring Boot application with Keycloak to have an endpoint that is both accessible for authenticated and unauthenticated users. For authenticated users, I want to return some extra information. Here is a simple example of what I'm trying to achieve:
#RestController
public class HelloController {
#GetMapping("/")
public String index(Principal principal) {
KeycloakPrincipal keycloakPrincipal = (KeycloakPrincipal) principal;
if (keycloakPrincipal != null) {
return "Hello " + keycloakPrincipal.getKeycloakSecurityContext().getToken().getPreferredUsername();
} else {
return "Hello";
}
}
}
application.properties:
keycloak.securityConstraints[0].authRoles[0] = *
keycloak.securityConstraints[0].securityCollections[0].name = Hello
keycloak.securityConstraints[0].securityCollections[0].patterns[0] = /*
So far, I only got it to work for one of both cases. If I protect the endpoint using the security constraint above, the endpoint is only accessible to authenticated users. If I remove the security constraint, the endpoint is accessible for everyone, but then the principal will always be null.
Is it possible to achieve the intended behavior?
Have you tried something like Principal principal = SecurityContextHolder.getContext().getAuthentication();?
I believe the Principal as method parameter is only populated on secured endpoints but am unsure if it would exist in the SecurityContext. If not, you need to add a Filter to add it yourself.
I was able to solve the problem by calling the authenticate() method on the HttpServletRequest object. This will trigger the authentication process and will populate the user principal whenever possible. From the docs:
Triggers the same authentication process as would be triggered if the
request is for a resource that is protected by a security constraint.
To avoid triggering an authentication challenge, I pass in a dummy response object to the authenticate() call.

Auth0 and SpringBoot securing an API with null for AuthenticationPrincipal OidcUser

I want to secure a backend API written in Java/SpringBoot and using Auth0. I am following the Auth0 example.
Everything works fine and as expected, however, when I apply this to my application I want to detect the user who made the API call. In various examples I've come across it appears to be possible with Spring Security by injecting the OidcUser.
Some relevant links I've found are:
Inject custom OidcUser wrapper with #AuthenticationPrincipal
Spring Security and OIDC connect
In my application, my controller looks like this:
#CrossOrigin(origins = "*", maxAge = 3600)
#RestController
#RequestMapping("/api/project")
public class ProjectController {
#PostMapping("/add")
#PreAuthorize("hasAuthority('write:project')")
public ResponseEntity<DummySubmitResponseDto> createNewProject(#AuthenticationPrincipal OidcUser oidcUser,
#RequestBody DummyDto dummyDto) {
DummySubmitResponseDto projectSubmitResponseDto = new DummySubmitResponseDto("TheResponse");
return new ResponseEntity<>(projectSubmitResponseDto, HttpStatus.CREATED);
}
}
I'm using a React SPA to get an access-token. I save it manually and use Curl or POSTMAN to call the API. However, oidcUser is always null.
I'm not sure if I'm following the correct approach. Separate to this, I don't understand how the Backend (which is a resource server) is able to get the user information from the access token.

Authenticate spring websocket via MessageMapping

Problem
I have set up a stomp websocket on spring, and have endpoints defined via the #MessageMapping annotation.
I had read that #PreAuthorize could be used to authorize on a per mapping basis but this doesn't appear to work. When using the #PreAuthorize, the request is not denied when the user is not in a specific role.
Code
#PreAuthorize("hasRole('ROLE_ADMIN')")
#MessageMapping(value="/addComment/{ID}")
public void addComment(#DestinationVariable Integer ID, String content, Principal principal)
throws Exception {
//Do stuff with ID,content etc
}
I currently have it set up like so
#Configuration
public class WebSocketSecurityConfig extends
AbstractSecurityWebSocketMessageBrokerConfigurer {
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.simpDestMatchers("/put/addComment/**").hasRole("ADMIN");
}
}
Although would prefer to annotate on each mapping since it is clearer for me.
Question(s)
Can preauthorize be used with mappings?
If so is there a reason that it is not working in the above example?
If not, is there a way to do this per mapping, instead of in the configurer?
Extra
Using Spring 4
Any more information needed let me know

Spring Security + MVC : same #RequestMapping, different #Secured

Let say we have an API endpoint configured using Spring MVC and Spring Security. We would like to be able to handle pairs of #RequestMapping and #Secured annotations where the only #Secured annotation values differ from pair to pair. This way, we would be able to return a different response body depending on security rules for the same request.
This may allow our code to be more maintainable by avoiding to check for security rules directly into the method body.
With a not working example, here is what we would like to do :
#Controller
#RequestMapping("/api")
public class Controller {
#Secured ({"ROLE_A"})
#RequestMapping(value="{uid}", method=RequestMethod.GET)
#ResponseBody
public Response getSomething(#PathVariable("uid") String uid) {
// Returns something for users having ROLE_A
}
#Secured ({"ROLE_B"})
#RequestMapping(value="{uid}", method=RequestMethod.GET)
#ResponseBody
public Response getSomethingDifferent(#PathVariable("uid") String uid) {
// Returns something different for users having ROLE_B
}
}
How can we achieve this ?
And if this can be done: How the priority should be managed for a user who has both ROLE_A and ROLE_B ?
Assuming you are using Spring 3.1 (or up) together with the RequestMappingHandlerMapping (and RequestMappingHandlerAdapter) you can extend the request mapping mechanism. You can do this by creating your own implementation of the RequestCondition interface and extend the RequestMappingHandlerMapping to construct this based on the #Secured annotation on your method.
You would need to override the 'getCustomMethodCondition' method on the RequestMappingHandlerMapping and based on the Method and the existence of the #Secured annotation construct your custom implementation of the RequestCondition. All that information is then taken into account when matching incoming requests to methods.
Related answers (although not specific for #Secured annotations but the mechanism is the same) is also to be found here or here
I don't think you can do this in spring-mvc, since both routes have exactly the same #RequestMapping (#Secured) is not taken into account by the route engine of spring-mvc. The easiest solution would be to do this:
#Secured ({"ROLE_A", "ROLE_B"})
#RequestMapping(value="{uid}", method=RequestMethod.GET)
#ResponseBody
public Response getSomething(#PathVariable("uid") String uid, Principal p) {
// Principal p gets injected by spring
// and you need to cast it to check access roles.
if (/* p.hasRole("ROLE_A") */) {
return "responseForA";
} else if (/* p.hasRole("ROLE_B") */) {
return "responseForB";
} else {
// This is not really needed since #Secured guarantees that you don't get other role.
return 403;
}
}
However, I would change your design, since the response is different per role, why not have 2 separate request mappings with slightly different URLs? If at some point you have users with role A and B at the same time, you can't let the user choose what response to get (think, for example, of the public and private profiles of LinkedIn)

Categories

Resources