I'm using spring security and I've 2 methods in my MovieService-
#PreAuthorize("hasRole('ROLE_DIRECTOR')")
public Map<String, Movie> getAllMovies() {
.......
.......
}
And another method is -
public Movie getMovieByMovieCode(String movieCode) {
Map<String, Movie> movies = getAllMovies();
Movie movie = movies.get(movieCode);
return movie;
}
As shown in code, I'm calling getAllMovies() method from inside the getMovieByMovieCode() method. So, if a user who does NOT have ROLE_DIRECTOR role, it tries to access getMovieByMovieCode() method and from inside this method, getAllMovies() method is also accessed.
But the expected behavior is that this user should not be able to access getAllMovies() method as this user does not have role ROLE_DIRECTOR.
How can I achieve this behavior? Do I need to use <dispatcher> inside filter mapping?
Spring Security authorization using annotations is implemented using Spring AOP proxies. That, among other things, means that whenever you are calling methods on the current object, you are referencing the object itself, not the proxy. That is why authentication is not done. The simple workaround would be to annotate the getMovieByMovieCode with the #PreAuthorize annotation too.
Here is some helpfull reading on AOP proxies: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html#aop-understanding-aop-proxies
Related
Using Spring Security 5.2.X (latest currently), I need to prevent the access to a third party role to all methods but one in a SOAP service. In other words, I need that particular role to have access only to one particular method among all of the availables in the service.
Normally, a usual method level securization consists of annotation with #Secure("MY_ROLE"). This makes the method only to be accessible for that role.
Is it possible to tell Spring Security Core to do the opposite. I.e, configure it in a way that certain user only has access to the secured method.
Of course, the workaround could be:
// Method accessible by anyone but third party role
#Secured({"GOOD_ROLE1", "GOOD_ROLE2", "GOOD_ROLE3"})
public void methodAccessibleByAnyoneButThirdPartyRole(){
}
// Method accessible by anyone, including third party role
#Secured({"GOOD_ROLE1", "GOOD_ROLE2", "GOOD_ROLE3", "THIRD_PARTY_ROLE"})
public void methodAccessibleByAnyone(){
}
Other workaround would consist of just creating a new service only with the method to be restricted.
But is there any annotation option to get the same straightaway? Something like #PreAuthorize("!hasRole('THIRD_PARTY_ROLE')") or similar?
According to Spring Security doc you can use any Spring Expression Language (SpEL) in #PreAuthorize
Any Spring-EL functionality is available within the expression, so you
can also access properties on the arguments. For example, if you
wanted a particular method to only allow access to a user whose
username matched that of the contact, you could write
#PreAuthorize("#contact.name == authentication.name")
public void doSomething(Contact contact);
So you can use:
#PreAuthorize("!hasRole('THIRD_PARTY_ROLE')")
Also as more readable variant:
#PreAuthorize("not hasRole('THIRD_PARTY_ROLE')")
First of all, I know about #PreAuthorize annotations and about Expression based access control.
For the sake of learning (as well as for many reasons), what I would like to have is this:
Users are authenticated and their roles are provided by an LDAP directory and populated into the Principal object when they authenticate. This works, as in "it is currently in place in a project".
An annotation (chosen to be #AccessControl) implements the paradigm that access control is entirely tied to roles. The annotation can be set on a class/type (the REST controller), in which case it applies to any method on which there's not also another such annotation, or on a method (a REST endpoint). The deepest annotation always wins, whether it is restricting or relaxing the authorization constraint.
The access control logic, which is a bit more complex than what I could get from the expression based access control, would be enforced by another piece of code. It is also a bit more maintainable but I guess this is only in my eyes.
As an example, a controller would have, except for an #AccessControl annotation over a method, endpoints that can only be accessed by users with ADMIN in their list of roles:
#RestController
#RequestMapping("/admin")
#AccessControl({ Roles.ADMIN })
public class AdminController {
...
}
My current indecision, after reading a lot these past days is more about whether to write a custom request filter or rather an AOP advice.
With a custom request filter, I find myself unable (for the moment) to determine which method of which controller the request is going to be mapped to. The annotations are out of my reach.
With an AOP advice, I don't know (yet) how to reply to the client with a 403 Forbidden status.
My questions stem directly from these two points:
How can I get the controller method that will be called for a client request?
How can I return an HTTP status code from an AOP advice and effectively end the processing of the request when the client is not authorized?
It turned out to be much simpler than I initially thought and I completed it in less than a day, using the AOP option.
This is the code of the AccessControl annotation, comments removed:
#Documented
#Inherited
#Retention(RUNTIME)
#Target({ TYPE, METHOD })
public #interface AccessControl {
public String[] value() default {};
}
It can be placed either on a controller (see my original post/question) or on a controller method:
#RestController
#RequestMapping("/admin")
#AccessControl({ Roles.ADMIN })
public class AdminController {
// This endpoint has open access: no authorization check will happen.
#AccessControl
#RequestMapping(value = "{id}", method = RequestMethod.GET)
public DummyDto getNoCheck(#PathVariable Integer id) {
return service.get(id);
}
// This endpoint specifically allows access to the "USER" role, which is lower
// than ADMIN in my hierarchy of roles.
#AccessControl(Roles.USER)
#RequestMapping(value = "{id}", method = RequestMethod.GET)
public DummyDto getCheckUser(#PathVariable Integer id) {
return service.get(id);
}
// The authorization check defaults to checking the "ADMIN" role, because there's
// no #AccessControl annotation here.
#RequestMapping(value = "{id}", method = RequestMethod.GET)
public DummyDto getCheckRoleAdmin(#PathVariable Integer id) {
return service.get(id);
}
}
In order to perform the actual verification, two questions must be answered:
first, which methods are to be processed?
second, what is checked?
Question 1: which methods are to be processed?
To me, the answer was something like "all REST endpoints in my code". Since my code lies in a specific root package, and since I'm using the RequestMapping annotation in Spring, the concrete answer comes in the form of a Pointcut specification:
#Pointcut("execution(#org.springframework.web.bind.annotation.RequestMapping * *(..)) && within(my.package..*)")
Question 2: what exactly is checked at runtime?
I will not put the entire code here but basically, the answer consists in comparing the user's roles with the roles required by the method (or its controller if the method itself bears no access control specification).
#Around("accessControlled()")
public Object process(ProceedingJoinPoint pjp) throws Throwable {
...
// Get the roles specified in the access control rule that applies (from the method annotation, or from the controller annotation).
// Get the user roles from the UserDetails previously saved when the user went through the authentication process.
// Check authorizations: does the user have one role that is required? If no, throw an exception. If yes, don't do anything.
// No exception has been thrown: let the method proceed and return its results.
}
What was bothering me in my initial thinking was the exception. Since I already had an exception mapper class that bears the #ControllerAdvice annotation, I just reused that class to map my specific AccessControlException to a 403 Forbidden status code.
For retrieving the user's roles, I used SecurityContextHolder.getContext().getAuthentication() to recover the authentication token, then authentication.getPrincipal() to retrieve the custom user details object, which has a roles field that I normally set up during the authentication process.
The code above is not to be used as-is (for instance, path mapping collisions will happen), but this is just to convey the general idea.
I want to provide an approach you can use if you want to follow the AOP advice root:
Concerning this point if using AOP:
How can I return an HTTP status code from an AOP advice and
effectively end the processing of the request when the client is not
authorized? solution:
In your aspect class, using at Around Advice kindly do the following:
#Around("execution(* net.my.package.AdminController.*(..)) && args(.., principal)")
public ResponseEntity<?> processRequest(final ProceedingJoinPoint joinPoint, final Principal principal) {
final String controllerMethodName = joinPoint.getSignature().getName();
LOGGER.info("Controller Method name : {}", controllerMethodName);
final boolean isAuthSuccessful = authenticationService.authenticate(principal);//Pass auth details here
if(!isAuthSuccessful) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Request declined"); //End request if auth failed
} else {
try {
return (ResponseEntity<?>)joinPoint.proceed(); //Continue with request
} catch (Throwable e) {
LOGGER.error("Error In Aspect :", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("failed request");
}
}
}
Well, the above code has comments addressing the challenges you are facing. But for this code to work ensure to do the following:
Make Sure all your controller methods you want to intercept do return ResponseEntity
You can change the #Around aspect to use a Point cut with value of #annotation for your #AccessControl annotation and you are free to chain the conditions in the #Around aspect
Make sure you find a way to pass data to this aspect so that you have a way to validate user credentials
I'm trying to make sense of how validation works in Spring. So far I've learned that there are two ways to perform validation of user input data:
JSR-303 validation based on javax.validation.constraints annotations. This validation is best suitable for simple object fields validation. But you can also implement your custom type level annotations to perform more complicated validation based on checking values of multiple fields.
Spring Validation based on org.springframework.validation.Validator interface. Seems to be better suited for more complicated validation.
If I want to use both these approaches, what is the right way to register my custom validator in controller?
This is what I'm trying to do.
My custom validator.
public class PasswordPairValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return PasswordPair.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
PasswordPair password = (PasswordPair) target;
if (!password.getPassword().equals(password.getRepeatPassword())) {
errors.reject("passwordField", "passwords don't match");
}
}
}
My controller.
#RestController
#RequestMapping("/api/v1/users")
public class UserController {
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(new PasswordPairValidator());
}
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<UserInfo> createUser(
#RequestBody #Valid UserInfo userInfo) {
userInfo.setId(123);
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}").buildAndExpand(userInfo.getId()).toUri();
return ResponseEntity.created(location).body(userInfo);
}
#RequestMapping(value = "/change_password", method = RequestMethod.POST)
public ResponseEntity<UserInfo> changePassword(
#RequestBody #Valid PasswordPair password) {
UserInfo user = new UserInfo("test#gmail.com", "testuser");
user.setId(123);
return ResponseEntity.ok().body(user);
}
}
When I call createUser endpoint the code fails with the following error:
ERROR c.e.testapp.controller.GlobalExceptionHandler - Internal Server Error
java.lang.IllegalStateException: Invalid target for Validator [com.example.testapp.validation.PasswordPairValidator#49acd001]: com.example.testapp.domain.UserInfo#cae4750
The problem apparently is that Spring tries to apply PasswordPairValidator to UserInfo object, which was not my intention.
Why Spring doesn't use validator's supports() method to check to which objects validator can be applied?
In a different stackoverflow question I found out that I need to specify value for #InitBinder annotation to make it work and the value should be "passwordPair". But what is this value as it's not the class name ("PasswordPair") or method parameters value ("password")?
The second question is if I want to add several validators do I need to define multiple #InitBinder("value") methods or is there a less cumbersome way to do it?
And the final question, maybe it's better to use annotation based validation for everything, to validate separate fields and implement type level custom annotations with ConstraintValidator to perform more complicated validation? It's a bit confusing what are the pros and cons of these approaches.
You have to provided an argument to your #InitBinder annotation.
Please refer this question
Above question also answers your other question on registering multiple validators.
I believe the reason this happens is because the #InitBinder method will be called every time a request is being processed and thus for all the methods you have that correspond to HTTP verbs.
The only way I know that you can limit the times the method annotated with #InitBinder gets called is by using the value argument that the annotation takes. I admit that I am also a bit confused on what that value is or how it is interpreted.
Spring boot uses supports to check if a validator can be used every time initBinder() gets called but will throw an exception when it doesn't fit. This happens when initBinder() get called when a Request is processed. So even if you have multiple validators from which one is valid for the request body it will fail
If someone could help with how we can correctly apply validators in Spring boot I would also appreciate it. In C# I know that you can register beans as in middleware and based on the class you register the validator with, the correct validator gets called. (I am not well versed in C# but this is what I remember). Isn't something like this also possible in Java?
Typically the spring controller method has such view:
#RequestMapping("/url")
public String getUrl([params]){
// do some stuff..
}
As params spring can accept get/post/delete .. params from request or some standart params like model, locale etc.
My question is next: can spring accept my parametr when method is invoked. like:
#RequestMapping("/profile")
public String getUserProfile(UserEntity user, [params ..]){
// do some stuff..
}
Here UserEntity user is NOT a request body, but the entity from my DB.
Scenario is next: before executing method getUserProfile, spring execute some other method, that somehow get user from DB and pass it as method param.
Is there some way to repeat this scenario in real life?
This is possible if you use Spring Data. See the documentation: http://docs.spring.io/spring-data/commons/docs/current/reference/html/#core.web
Basically, what you have to do, is to add the id as parameter, and Spring Data will then use that ID to get the entity from the database and pass it to the method:
#RequestMapping("/{id}")
public String showUserForm(#PathVariable("id") User user) {}
To enable that feature, you have to put the annotation #EnableSpringDataWebSupport onto a #Configuration class
Check here.
We can use MVC interceptors. You need to modify the Interceptor implementation. Check the request URL pattern, if it matches then call a method that returns the UserProfile object. Hope this helps.
Consider an Struts 2 + Spring 4 project.
For each login the User object is put in session. As a very simple action it will look
public class LoginProcess implements ServletRequestAware {
#Inject
private AuthenticationServices authenticationServices;
public String execute() {
//The login method makes a new User and fills its setters
User newUser = authenticationServices.login(....);
getServletRequest().getSession().setAttribute("USER_SESSION", user);
}
}
As we manually make a new User object so it is not managed spring bean, and we can't use spring features in User class: #Inject , #Value ,...
I tried to change user as:
#Named
#Scope(value="session")
public class User { ...
#Inject
private AccountServices accountServices;
}
and inject the User in instead of calling new User, but I get the error:
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
at org.springframework.web.context.request.SessionScope.get(SessionScope.java:91)
Well although it describes the error, but I can not find how can I fix it, and I am not sure if this is the correct way at all. It seems that I can only use spring session scope been when I am using spring mvc
Any comments ?!
Why I need this ?! (Simplified situation)
The user object has a getAccounts() methods which get all user accounts. Getting user accounts is an expensive operation, and it is possible that a user does not require its accounts during its login.
So, instead of get user accounts as soon as user logs in, we let the get method get user accounts if it does not have it:
public class User() {
private Accounts accounts;
#Inject
private AccountServices accountServices;
Accounts getAccounts() {
if (accounts == null) {
accounts = accountServices.getUserAccountsFromDB(...)
}
return accounts;
}
Don't create a new instance of User by yourself, instead get a bean from Spring context.
For example you can achieve it by implementing ApplicationContextAware interface and calling one of getBean methods.
User user = applicationContext.getBean(User.class);
// populate user and put it into session
In that way it is a Spring managed bean an all required properties should be injected.
BUT consider changing your User to a simple POJO and moving all business logic (such as fetching users accounts) to some more appropriate place, in that way your model layer will be cleaner and easily testable.