In my Spring Boot application I have a REST controller with a following method:
#PreAuthorize("hasAnyRole('PERMISSION_UPDATE_OWN_COMMENT', 'PERMISSION_UPDATE_ANY_COMMENT')")
#RequestMapping(value = "/update", method = RequestMethod.POST)
public CommentResponse updateComment(#AuthenticationPrincipal User user, #Valid #RequestBody UpdateCommentRequest commentRequest) {
Comment comment = commentService.updateComment(commentRequest.getCommentId(), commentRequest.getTitle(), commentRequest.getContent(), user);
return new CommentResponse(comment);
}
Only users with PERMISSION_UPDATE_OWN_COMMENT or PERMISSION_UPDATE_ANY_COMMENT are allowed to use this endpoint.
Inside of this method I need to create two different flows - one for users with PERMISSION_UPDATE_OWN_COMMENT and another one for users with PERMISSION_UPDATE_ANY_COMMENTpermissions.
So my question is - what is best practice for Spring security in order to implement these different flows of logic inside of the single method ?
Should I validate inside of the updateComment method that the user has one or another permission and based on this condition implement my logic ?
The easiest way is to do the logic inside updateComment function inside controller. Because, you can easily get the instance of SecurityContextHolderAwareRequestWrapper from the action param to find the role.
But the best practice is to put your logic inside service. This will make your life easier to reuse the logic in another place like RESTFul APIs.
So you may use the below code or something similar to check the role inside the Service.
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
boolean authorized = authorities.contains(new SimpleGrantedAuthority("PERMISSION_UPDATE_OWN_COMMENT"));
(edited with further information)
Complete function which can be used to check the roles
protected boolean roleExist(String role) {
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
for (GrantedAuthority auth : authentication.getAuthorities()) {
if (role.equals(auth.getAuthority()))
return true;
}
return false;
}
Related
I need to have authorization at the method level so that the users with proper permissions only can access it. The method will contain a token as a parameter. I need to make an API call passing the token and get the user email id. Once I have the email id, I need to fetch the user's roles & permissions from the database. Then I invoke the method if the user have appropriate roles else return a 403 error.
Is there a way to get this done in spring boot? I will have multiple methods behind authorization and would like to have some kind of annotation at method level.
Thanks.
#PreAuthorize annotation is what you want
Please read the following link for spring method level authorization
baeldung method authorization
you will also need to undestand SPEL(Spring Expression Language) as this is what the PreAuthorize method gets as parameter , link can be found here
please note that spring uses the SecurityContext to get the user data(Role etc..), meaning that the user already passed the login(authentication) stage and has SecurityContext loaded for said user
Example:
//other annotations
#PreAuthorize("hasRole('ROLE_VIEWER')") // hasRole('ROLE_VIEWER') -> this is SPEL
public ResponseEntity<String> methodName() {
//method
}
You can use #PreAuthorize with more flex as:-
#PreAuthorize("#securityService.hasPermission({'PERMISSION_1'})")
and service:-
#Component("securityService")
public class SecurityService {
public boolean hasPermission(PermissionEnum... permissions) {
Collection<? extends GrantedAuthority> authorities = SecurityContextHolder.getContext().getAuthentication()
.getAuthorities();
for (PermissionEnum permission : permissions) {
if (authorities.contains(new SimpleGrantedAuthority(permission.toString))) {
return true;
}
}
return false;
}
}
You can make it as you want.
For more
https://dreamix.eu/blog/java/implementing-custom-authorization-function-for-springs-pre-and-post-annotations
https://try2explore.com/questions/10125443
I have a case where certain endpoints on a controller should return 401 if the username that the user authenticated with is the same username as the user being operated on.
I have been thinking of the best way to do this. Currently, I have an authentication facade (https://www.baeldung.com/get-user-in-spring-security) where I check in the body of each controller method if the user should have access to operate on the item it is asking to.
IE. A user should only be able to delete their account:
User u = service.findOne(id);
if (u != null) {
// user can only delete their own account
User authenticated = authenticationFacade.getAuthUser();
RestPreconditions.checkRequestState(authenticated.getId() == u.getId());
}
Another case is where a user needs to operate on something of a different data type that they have access to work on.
IE
Post p = service.findOne(id);
if (p != null) {
// user can only delete their own posts
User authenticated = authenticationFacade.getAuthUser();
RestPreconditions.checkRequestState(authenticated.getId() == p.getUser().getId());
}
I am here to ask if this is the best way. As demonstrated above, some of the checks require operating through different objects and making database calls to get the data to determine if the user should have access.
I considered a role-based implementation and was wondering if anyone could provide some insight into how I would do that and if it is cleaner than the method above.
The reason I ask is that I also want to allow people with the ROLE_ADMIN role to be able to do all operations but I would need to transform the current checks to or || with the current checks and that seems messy. But simply preauthorizing just role admin would still fail with the facade without the or
Check about #PreAuthorize / #PostAuthorize which allows you to use SpEL to secure a method call in a declarative way.
One of the very nice thing is that you can even uses SpEL to refer a spring bean method , which means you can do something like below.
First, defining a bean to encapsulate all the security related checking.Suppose all entities implements some sort of interface (e.g BaseEntity) which can get the owner of that entity :
#Service
public class SecurityService{
public boolean isAllowAccessedByCurrentUser(BaseEntity entity) {
User authenticated = authenticationFacade.getAuthUser();
return authenticated.getId() == entity.getOwnerId();
}
}
To use it to apply the security checking :
#Service
public class UserService {
//"returnObject" is the built-in variable referring to the return object
#PostAuthorize ("#securityService.isAllowAccessedByCurrentUser(returnObject)")
public User findOne(Integer id){
}
}
#Service
public class PostService {
//"returnObject" is the built-in variable refer to the return object of the method
#PostAuthorize ("#securityService.isAllowAccessedByCurrentUser(returnObject)")
public Post findOne(Integer id){
}
}
I have scenario where I have users, groups and many different elements(models) which are owned by group and have to be accessible only by users from that group with active membership. I have many models belonging to groups.
So basically somehow I need to check that user and target element have the same group_id
How it can be done with Spring Security?
I was looking for similar scenario but I haven't found even if I am,pretty sure it is quite common usage.
Spring Security provide a complete authorization framework :
http://docs.spring.io/spring-security/site/docs/current/reference/html/el-access.html.
It use annotation like #PreAuthorize to check if the authenticated user is allowed or not to call the method .
Here with the spring method hasRole :
#PreAuthorize("hasRole('USER')")
public void create(Contact contact);
If your authorization logic cannot be handled only with default spring methods, you can create your custom implementation :
#PreAuthorize("#patientAuth.readObject(authorization,#patientId)")
public Patient getPatient(Long patientId) {
Patient patient = patientDao.findOne(patientId);
if(patient==null) throw new NotFoundException("Patient not found");
return patient;
}
The method getPatient can only be called if the method patientAuth.readObject return true :
I'm new to Spring security, and working on a grails app that connect to external authentication and session management service for authentication/authorization. So what I did is create customized authentication filter
class TokenAuthenticationFilter extends AbstractPreAuthenticatedProcessingFilter {
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
//in here i call an external service, passing in the a cookie from the request,
//and get username and role information from the service
//not sure what to do with user roles
return username
}
}
Then I looked at super class AbstractPreAuthenticatedProcessingFilter code, in it's doFilter() method, it create an PreAuthenticatedAuthenticationToken (new PreAuthenticatedAuthenticationToken(principal, credentials);) for authentication, but it only takes username and credential, not the role information. Then I tried to inject a different details source J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource into the filter, because looks to me (i might be wrong) this detail source will pass the role information to the authentication object by calling setDetails(). but J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource is reading role information from http request object.
protected Collection<String> getUserRoles(HttpServletRequest request) {
ArrayList<String> j2eeUserRolesList = new ArrayList<String>();
for (String role : j2eeMappableRoles) {
if (request.isUserInRole(role)) {
j2eeUserRolesList.add(role);
}
}
return j2eeUserRolesList;
}
I got confused about the life cycle of authentication. I thought the http request object is getting role information through authentication object in security context, which hasn't been created at this point. I need the role information in order to create the authentication object. Isn't this running in cycle? or am I misunderstanding anything?
I know I can go with another approach to make my app work, just making my own filter to create the authentication object (which takes the role parameter) instead of letting super class (AbstractPreAuthenticatedProcessingFilter) to create the authentication object, but I'm just curious why the first approach is not working. What is J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource trying to do? It calls request.isUserInRole(role), but by who and when is the user role set to http request?
Hopefully I express myself clear enough for someone to understand.
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);
}
}