I have been searching on how to remove the ROLE based authorization and replace it with fine grain authorization. What I meant by fine grain is
All method has a #PreAuthorize("isAuthorize('GETCLIENT')") or directly #IsAuthorize("GETCLIENT").
If the user has GETCLIENT in Authorization List, then the method can be executed. Otherwise, the system give error message or just deny access.
Any clue or information regarding how to do that is very much appreciated.
Thank you.
Like I said in the comment one quick and easy way to do this is to add your new custom authorities in the AuthoritiesConstants.java class. You have examples of how to do this here and here.
public final class AuthoritiesConstants {
public static final String ADMIN = "ROLE_ADMIN";
public static final String USER = "ROLE_USER";
public static final String ANONYMOUS = "ROLE_ANONYMOUS";
public static final String GETCLIENT = "ROLE_GETCLIENT"; // custom
private AuthoritiesConstants() {
}
}
Remember to insert the new role into your jhi_authority database table. You can assign new authorities to a user via the user management interface admin/user-management, it's possible the user needs to relog for the change to take effect.
Then in the method you want to secure just add:
#GetMapping("/clients/{id}")
#PreAuthorize("hasRole(\"" + AuthoritiesConstants.GETCLIENT + "\")")
public ResponseEntity<ClientDTO> getClient(#PathVariable Long id) {
log.debug("REST request to get Client : {}", id);
Optional<ClientDTO> clientDTO = clientService.findOne(id);
return ResponseUtil.wrapOrNotFound(clientDTO);
}
I said #Secured before but in reality you should use #PreAuthorize since it is more powerful and lets you work with Spring Expression Language (SpEL).
The go to resource to understand how JHipster security works is here, but in reality it just follows the standard Spring Security guidelines (as with many other things) so the official documentation about Spring Security should apply too.
Also, if you find this is too simple or that it is breaking the default conventions I found this guide about custom privileges to be particularly great. It's a bit more work, but should work better since you separate authorities (roles) from privileges.
Related
as the title suggests, I have configured security in my Spring WebFlux application by using #EnableWebFluxSecurity and #EnableReactiveMethodSecurity.
I am using RouterFunction to handle the request routing. The following code is for the router:
#Component
public class UserServiceRequestRouter {
#Autowired
private UserServiceRequestHandler requestHandler;
#Bean
public RouterFunction<ServerResponse> route() {
//#formatter:off
return RouterFunctions
.route(GET("/user/{userId}"), requestHandler::getUserDetails);
//#formatter:on
}
}
And the request handler is:
#Component
public class UserServiceRequestHandler {
#Autowired
private UserService userService;
#PreAuthorize("#userServiceRequestAuthorizer.authorizeGetUserDetails(authentication, #request)")
public Mono<ServerResponse> getUserDetails(ServerRequest request) {
//#formatter:off
return userService.getUserDetails(request.pathVariable("userId"))
.convert()
.with(toMono())
.flatMap(
(UserDetails userDetails) -> ServerResponse.ok()
.contentType(APPLICATION_NDJSON)
.body(Mono.just(userDetails), UserDetails.class)
);
//#formatter:on
}
}
Note: The #Autowired UserService is to fetch data from the database in a reactive way.
Next, I have defined a #Component as:
#Component
#SuppressWarnings("unused")
#Qualifier("userServiceRequestAuthorizer")
public class UserServiceRequestAuthorizer {
public boolean authorizeGetUserDetails(JwtAuthenticationToken authentication, ServerRequest request) {
// #formatter:off
if (authentication == null) {
return false;
}
Collection<String> roles = authentication.getAuthorities()
.stream()
.map(Objects::toString)
.collect(Collectors.toSet());
if (roles.contains("Admin")) {
return true;
}
Jwt principal = (Jwt) authentication.getPrincipal();
String subject = principal.getSubject();
String userId = request.pathVariable("userId");
return Objects.equals(subject, userId);
// #formatter:on
}
}
It is notable here that I am using Spring OAuth2 Authorization Server, which is why the parameter authentication is of type JwtAuthenticationToken.
The application is working as per the expectation. But I am wondering if I am doing it the right way, meaning is this the best practice of doing method level Authorization in a reactive way?
The followings are my stack:
JDK 17
org.springframework.boot:3.0.0-M4
org.springframework.security:6.0.0-M6
Any advice you could give would be much appreciated.
Update
As mentioned by M. Deinum in the comment why shouldn't I use hasAuthority("Admin") or principal.subject == #userId, the reason is that the authorization code I provided is merely for demonstration purposes. It can get complicated and even if that complicacy might be managed by SpEL, I would rather not for the sake of simplicity.
Also the question is not about using inline SpEL, it's more about its reactiveness. I don't know if the SpEL mentioned in the #PreAuthorize is reactive! If it is reactive by nature then I can assume any expression mentioned in the #PreAuthorize would be evaluated reactively.
As far as I know, SpEL expressions evaluation is synchronous.
Unless your UserServiceRequestAuthorizer does more than checking access-token claims against static strings or request params and payload, I don't know why this would be an issue: it should be very, very fast.
Of course, if you want to check it against data from DB or a web-service this would be an other story, but I'd say that your design is broken and that this data access should be made once when issuing access-token (and set private claims) rather than once per security evaluation (which can happen several times in a single request).
Side notes
It is notable here that I am using Spring OAuth2 Authorization Server, which is why the parameter authentication is of type JwtAuthenticationToken.
I do not agree with that. It would be the same with any authorization-server (Keycloak, Auth0, Microsoft IdentityServer, ...). You have a JwtAuthenticationToken because you configured a resource-server with a JWT decoder and kept the default JwtAuthenticationConverter. You could configure any AbstractAuthenticationToken instead, as I do in this tutorial.
It can get complicated and even if that complicacy might be managed by SpEL, I would rather not for the sake of simplicity.
I join #M.Deinum point of view, writing your security rules in a service, like you do, makes it far less readable than inlining expressions: hard to guess what is checked while reading the expression => one has to quit current source file, open security service one and read the code.
If you refer to the tutorial already linked above, it is possible to enhance security DSL and write stuff like: #PreAuthorize("is(#username) or isNice() or onBehalfOf(#username).can('greet')") to stick to your sample, this would give #PreAuthorize("is(#userId) or isAdmin()).
Spring Authorization Server OAuth 2.1.
How can i programatically simulate the authorization_code grant?
Since all grants except for authorization_code and client_credentials have been dropped this has become quite a headache.
The scenario calls for a #Scheduled job to login as a specific user where the client credentials are encoded properties within the server performing the login.
The user roles are important when executing downstream resources and is considered a regular user of the registered Client.
Using the Password grant was perfect for this scenario in OAuth 2.0.
Before i start hacking our Spring Auth server and implement a Password grant for registered resources or maybe overloading the client_credentials for user_credentialed payloads.
Quite a pain if you ask me, so please enlighten me? Are there any patterns for implementing this that i have not yet discovered?
While I'm curious what specific use case you have that needs to perform tasks as a particular user (as opposed to a single confidential client), it should still be possible with customization.
maybe overloading the client_credentials for user_credentialed payloads
This approach makes the most sense to me as a way to adapt supported flows in OAuth 2.1 to emulate a deprecated flow like the resource owner password grant. You can use a variation of this github gist, extending it with your user's authorities if needed. One possible solution might look like the following:
#Component
public final class DaoRegisteredClientRepository implements RegisteredClientRepository {
private final RegisteredClient registeredClient;
private final UserDetailsService userDetailsService;
public DaoRegisteredClientRepository(RegisteredClient registeredClient, UserDetailsService userDetailsService) {
this.registeredClient = registeredClient;
this.userDetailsService = userDetailsService;
}
#Override
public void save(RegisteredClient registeredClient) {
throw new UnsupportedOperationException();
}
#Override
public RegisteredClient findById(String id) {
return this.registeredClient.getId().equals(id) ? this.registeredClient : null;
}
#Override
public RegisteredClient findByClientId(String clientId) {
UserDetails userDetails;
try {
userDetails = this.userDetailsService.loadUserByUsername(clientId);
} catch (UsernameNotFoundException ignored) {
return null;
}
return RegisteredClient.from(this.registeredClient)
.clientId(userDetails.getUsername())
.clientSecret(userDetails.getPassword())
.clientSettings(ClientSettings.builder().setting("user.authorities", userDetails.getAuthorities()).build())
.build();
}
}
This uses a single client registration, but makes use of a UserDetailsService to resolve a subject representing your user's username and a secret which is actually the user's password. You would then need to provide an #Bean of type OAuth2TokenCustomizer<JwtEncodingContext> to access the user.authorities setting and add those authorities to the resulting access token (JWT) using whatever claim your resource server expects them in.
Alternatively, you could just override the scopes parameter of the returned RegisteredClient if desired.
I had the similar problem and ended up creating a password grant emulation servlet filter. Please refer to my example:
https://github.com/andreygrigoriev/spring_authorization_server_password_grant
Scenario:
A community webapp where people can form communities about certain topics with a Spring REST backend.
Today I was wondering how one would implement a setting akin "Who can see your email adress".
When a User enters a community and a REST call to e.g. /api/community/1/users is being made, how would it be possible to stop the CrudRepository from serializing a field such as email of User B if the user A making the API call to the backend is not a friend / does not fulfill certain criteria of user B's settings, such as only showing emails to approved contacts. The resulting JSON should contain a list of users with some having a email field and some not.
While searching I was not able to find anything that matches my question. Following are some things I have discovered but don't feel like they are of much help.
Annotating Controller methods / Repository methods with #PreAuthorize, passing in the Principal.
Why I think this might not help: This seems to be a good solution if I want to block someone based on their ID from viewing a ressource completely. Such as Users only being able to see their own data but not others because the Principal ID does not match the requested ressource's id.
Using JsonFilter as described here: https://www.baeldung.com/jackson-serialize-field-custom-criteria
With this approach I don't see a way of checking WHO is making a request for e.g. my email.
This approach seems to fit well for a scenario such as having a boolean flag set to show email or not, for all cases and any requesters.
Creating a new domain object such as "Friend" extending "User", which is only there for overwriting the #JsonIgnore property of User. While a normal User would not have their Email field serialized due to #JsonIgnore, a friend would set #JsonIgnore(false) over email.
I dont like this approach because I feel like it must somehow be possible to implement this functionality without creating new classes only to overwrite Jackson annotations.
Sorry if there isn't any code to show. So far I have only been creating simple entities and mostly theorycrafting how it would be possible to accomplish the above when I saw that the repository exposes everything. I'm usually more home at the Frontend side of things but I want to learn backend with Spring as well, for private as well as professional reasons. I hope the question isn't too basic.
Thank you in advance.
You can use #JsonView from Jackson for it.
First, create a DTO with the fields you want to return and annotate them with #JsonView:
public class UserDto {
#JsonView(NoFriend.class)
private String name;
#JsonView(Friend.class);
private String email;
public static class NoFriend {}
public static class Friend extends NoFriend {}
}
The NoFriend and Friend inner classes are just markers to define what fields should be returned in what case.
Now in your controller, instead of returning a UserDto, you wrap the UserDto in a MappingJacksonValue:
public class UserController {
#GetMapping("/api/community/1/users")
public List<MappingJacksonValue> getUsers(#AuthenticationPrincipal Principal principal) {
List<User> users = service.getUsers();
return users.stream()
.map( user -> {
MappingJacksonValue value = new MappingJacksonValue(UserDto.fromUser(user));
value.setSerializationView(getView(principal, user));
})
.collectors(toList());
}
private Class getView(Principal princapl, User user) {
// return UserDto.Friend.class or UserDto.NoFriend.class, depending the relation of the authentication principal with the user
}
Probably, not the simplest way to implement it. But maybe it will help you to decompose a problem and find an appropriate solution.
I assume that you just want to clear fields on API level, but still gonna fill it in your Objects.
Let's define a model with some security metadata on it:
class UserDTO {
Long id;
String name;
#AllowOnly("hasRole('FRIEND')") // SPeL/any your custom defined language, or simpler:
//#AllowOnly(Role.FRIEND)
String email;
}
Then define a controller class
#RestController
class SomeController {
#GetMapping("/api/community/{id}/users")
public List<UserDTO> getUsers() {
return List.of(
new UserDTO(1, "Bob", "email-you#gonna.see"),
new UserDTO(2, "Jack", "email-you-NOT#gonna.see"))
}
}
So what i propose is to create an aspect, which is gonna clear fields based on your permission model.
#AfterReturning("within(#org.springframework.web.bind.annotation.RestController *)
&& execution(* *(..))", returning="objectToClear")
public void applyFieldPermissions(Object objectToClear) {
// Here i would parse annotations on object fields
// and if found #AllowOnly, check your role to a user.
// and clean up field, if necessary
}
Logic of the aspect is totally dependent on your cases, but for this simple example, need only to implement some method to check your role for specific object
boolean hasRoleOn(UserDto dto, Role role, Authentication currentUser)
I've used Spring Security multiple times on several projects, including 3 legged OAuth2 authentication on Zuul API Gateway, etc. All works brilliant and official documentation is very neat and simple.
But there is one point that I still don't get from docs. Imagine you have a spring based Resource Server with several ID Providers, and also you have your own user database and form login.
Thus, users can be authenticated either via form login or via one of IDPs (let's say Google or Facebook).
The question is: how to match Authentication from any of your IDPs to Authentication object that is enhanced by/mapped to your local user?
I.e.: Alice has an account in your system (in your database). She goes into her "profile" and declares that she also has a Google or Facebook account. OK, done, you save this info somewhere in your system.
Now, when Alice login into your system via social network, what spring API do you use to understand that Alice entered via Google is exact same Alice that is already registered in your DB? In what API do you enhance her Authentication with authorities based on your DB?
Thanks in advance, guys
The way this is typically done is by creating a composite that contains both the OidcUser object and your domain object. For example:
#Component
public class MyOAuth2UserService implements OAuth2UserService<OidcUserRequest, OidcUser> {
private final OidcUserService delegate = new OidcUserService();
#Override
public OidcUser loadUser(OidcUserRequest oidcUserRequest) {
// the information that comes back from google, et al
OidcUser oidcUser = this.delegate.loadUser(oidcUserRequest);
// the information from your DB
MyUser user = this.myRepository.findUserByXYZ(oidcUser.getXYZ());
return new MyOidcUser(user, oidcUser);
}
private static class MyOidcUser extends MyUser implements OidcUser {
private final OidcUser delegate;
public MyOidcUser(MyUser user, OidcUser oidcUser) {
super(user);
this.delegate = oidcUser;
}
// ... implement delegate methods
}
}
Note that XYZ is some attribute that allows you to know that the user from Google is the user from your system. Maybe that's the email address, for example.
The benefit to this extra bit of work is that Spring Security will place this MyOidcUser object into Authentcation#getPrincipal. So now, if you need to get your domain bits, you do (MyUser) authentication.getPrincipal(), but if you need the OIDC bits, you do (OidcUser) authentication.getPrincipal(). Depending on your use cases, you may be able to do something as simple as:
#GetMapping("/endpoint1")
public String endpoint1(#AuthenticationPrincipal MyUser myUser) {
// ...
}
#GetMapping("/endpoint2")
public String endpoint2(#AuthenticationPrincipal OidcUser oidcUser) {
URL issuer = oidcUser.getIdToken().getIssuer();
// ...
}
My current Spring3 REST JSON api is authenticated with the default InMemory properties file/basic-authentication authentication manager. That has worked fine thus far, but I need to further validate that an incoming request is allowed to be made for that user. The Role concept seems to work fine as a gateway for entry to a particular controller's url, but it doesn't go far enough to validate that the user is permitted to ask for the data being requested.
In my app, each B2B partner that will be making requests to the API is assigned an applicationId. That partner user account is only allowed to make requests for that applicationId. The applicationId is passed as an attribute of the RequestBody POJO for all the POST API messages. I would like to decline requests that are made for improper applicationIds.
How can I validate that the authenticated user is making a permitted request?
I've started down the path of creating a custom AuthenticationProvider, but I don't know how to get access to the applicationId within the RequestBody bean that hadn't been marshalled into the java bean yet.
Perhaps a custom AuthenticationProvider isn’t the right solution, and a request validator of some sort is needed. If so, how would the validator on the appId attribute get access to the Principal (authenticated user object)
With any solution, I would like it be invisible to the controller, so that requests that do make it to the controller are permitted ones. Also, ideally, the solution should not depend on an engineer to remember some annotation to make the logic work.
Thanks in advance,
JasonV
EDIT 1: By implementing an InitBinder in the controller, and using the #Valid annotation on the RequestBody I was able to validate a request. However, this is not the Droids (er I mean solution) I'm looking for. I need to find a more generic way to handle it without all those Binders and annotations; too much to remember and spread around the application over dozens of request controllers, and it will be forgotten in the future.
The usual way to implement this is using #PreAuthorize.
#PreAuthorize("hasRole('USER') and authentication.principal.approvedAppId == #dto.applicationId")
#RequestMapping...
public ... someMethod(#RequestBody Dto dto, ...)
If you're worried about the repetition of the SpEL, define a new annotation like #PreAuthorizeUser and set the #PreAuthorize as a meta-annotation on it.
I was able to utilize an aspect to solve the problem generically.
I would still like to see if it is possible to do the following:
Get a marshalled RequestBody from the request object in the context of an AuthenticationProvider.
Here is the aspect code for future help to others.
#Pointcut("within(#org.springframework.stereotype.Controller *)")
public void controllerBean() {
}
#Pointcut(
"execution(org.springframework.http.ResponseEntity *(.., #org.springframework.web.bind.annotation.RequestBody (*),..))")
public void methodPointcut() {
}
#Around("controllerBean() && methodPointcut()")
public Object beforeMethodInControllerClass(ProceedingJoinPoint jp) throws Throwable {
Object[] args = jp.getArgs();
long requestAppId = Long.parseLong(BeanUtils.getProperty(args[0], "applicationId"));
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User principal = (User) auth.getPrincipal();
String username = principal.getUsername();
long[] approvedAppIds = getApprovedAppIdsForUsername(username);
for (long approvedAppId : approvedAppIds) {
if (approvedAppId == requestAppId) {
isAllowedAccess = true;
break;
}
}
if (isAllowedAccess) {
return jp.proceed(args);
} else {
LOGGER.warn("There was an attempt by a user to access an appId they are not approved to access: username="+username+", attempted appId="+requestAppId);
return new ResponseEntity(HttpStatus.FORBIDDEN);
}
}