Custom access control to REST endpoints in a Spring Boot application - java

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

Related

Spring boot #PreAuthorize (is it possible to modify the input to the method in #PreAuthorize then pass into the method)

I have a method annotated with #PreAuthorize(...) with some logic that goes away and queries an API for some information about what the user can view. However, I have this endpoint that I need to add this #PreAuthorize annotation into which receives in a more "complex" object which I want to transform slightly (the object contains an array that is some cases I want to add/remove data from).
#PostMapping("/search")
#PreAuthorize("#Service.isAuth(#searchParam)")
public ResponseEntity<Response> search(SearchParams searchParam) {
return service.getSearchResult(searchParam);
}
Is there a way I can modify searchParam inside the #PreAuthorize annotation then have it passed into the method body, I know that this is probably is not the correct way to do this and maybe isn't something that #PreAuthorize wasn't designed for but is there any way of doing this even with a different type of annotation. Obviously worst case I can move the logic into the method body but I would prefer to use an annotation-based solution like #PreAuthorize offers if possible. Thanks for any help even links to other relevant things would be useful I've not found much on google related to this.
I think the best solution is to make a handler/interceptor and then annotate it with #PreAuthorize. So I think you are in the right track but you need to make sure that you modify your code to implement the HandlerMapping interface to create the interceptor and then override the prehandle method. After you need to annotate it with #PreAuthorize programatically. The last thing will be to use a wrapper to modify the HttpWrapper, it cannot be done manually. Here links to the relevant resources in order:
Creating a Handler/Interceptor: https://www.baeldung.com/spring-mvc-handlerinterceptor
Using PreAuthorise in the interceptor: How can I require that all request handlers in my Spring application have #PreAuthorize
To modify the HttpServlet request you will need a wrapper: How to modify HttpServletRequest body in java?
Have a try, hopefully that works.
Snippet of code taken from second link uses a programatic PreAuthorize rather than annotation:
public class PreAuthorizeChecker implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod hm = (HandlerMethod) handler;
PreAuthorize annotation = AnnotationUtils.findAnnotation(hm.getMethod(), PreAuthorize.class);
//TODO use the technique shown on the third link to wrap and modify the HttpServletRequest
if (annotation == null) {
// prevent access to method wihout security restrictions
throw new RuntimeException("Rights are not defined for this handler");
}
}
return true;
}
.....

Java Validation on method and return a message based on the input params

I have a scenario, in which I need to throw an exception when a method returns a null object and also customise the response based on the parameters sent to the method.
I have an interface which has some methods, now i want to ensure that whoever implements that method does a basic check (defined by annotation or something like that) like null check is assigned. So that after the overridden method is call that check is invoked
For example:
#NotNull
User getUser(Integer id)
if response is Null then
"User not available for id:" + id
I've tried using javax validation #NotNull but it doesn't provide the parameters sent to method, is there any way to achieve that using javax validation.
Another thing, I tried was using AOP #Around control annotation and was successful to get the response and the parameters, wanted to ensure is there any other way to achieve that, as pointed out annotation on interface not possible on AOP LINK.
Another option is using decorator pattern but involves lots of boiler plate coding
#InterceptorBinding
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.TYPE})
public #interface MethodInterceptorBinding { }
#Interceptor
#MethodInterceptorBinding
public class MethodInterceptor {
#AroundInvoke
public Object validateMethodInvocation(InvocationContext ctx)
throws Exception {
}
}
Tried CDI but the MethodInterceptor is not getting invoked in the spring boot application, do i need to register these interceptors, if yes then how?

Spring MVC: What's the right way to register custom Validator in REST controller

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?

How to declare custom field/paramether annotation similar to Auth in Dropwizard

I have secured resources with #Auth annotation on User entity.
But I want to control the flow of data, so that requests without header will be also valid (by default, Dropwizard Auth returns 401 Unauthorized).
I think that the best for it would be my custom annotation, which will be applied to User parameter, and, if from SecurityContext userPrincipal is null, then I can return some default User or just pass the null further.
How to implement such an annotation? Or maybe You have other suggestions?
EDIT
I think that I found the code that may suit my needs :
User user = (User) context.getUserPrincipal();
But how can I make paramether annotation from it, so that this would be executed to every annotated object?

Is there a way to access HttpRequest type inside a #Controller method

I have tried to find the answer to this, but I cannot seem to find what I am looking for. So I apologize if this question already exists.
PROBLEM:
I want to be able to access the request type of a request inside of a generic method within my Controller.
DESCRIPTION:
Using Spring ROO and Spring MVC, I have developed a small web service that will respond with certain tidbits from a database when queried. In one of my controller classes, I have some methods that handle some variety of GET, PUT, POST, etc., for the URIs that are mapped within the #RequestMapping parameter.
For example:
#RequestMapping(method = RequestMethod.Get, value = "/foo/bar")
#ResponseBody
public ResponseEntity<String> getFooBar() {
// stuff
}
If a request is made to the web service that it is not currently mapped, a 405 error is returned (which is correct), but I want to return more information along with a 405 response. Maybe respond with something like:
"I know you tried to execute a [some method], but this path only handles [list of proper methods]."
So I wrote a short method that only has the RequestMapping:
#RequestMapping(value = "/foo/bar")
I have found that the method with this mapping will catch all unhandled request types. But I am having trouble accessing the information of the request, specifically the type, from within the method.
QUESTION:
A. How can I access the request type from within the method? OR
B. Is this the right approach? What would be the right approach?
EDIT
ANSWER:
I added a HttpServletRequestobject to the method parameters. I was able to access the method type from that.
I tried using HttpRequest, but it didn't seem to like that much.
Thanks all!
You can add a method parameter of HttpServletRequest, but I think you'd be better off continuing to reply with 405. A client should then make an HTTP OPTIONS call (see How to handle HTTP OPTIONS with Spring MVC?) and you can return the list of allowed methods there.
A. you can access request if you mentioned it as parameter in controller method
public ... getFooBar(HttpRequest request) {
...
}
B. you do not need to add any other description as the 405 status is descriptive.
In answer to "A", just add "HttpRequest req" as an additional argument to your controller methods. Spring will automatically inject a reference to the request, and you can play with headers to your heart's content.
In answer to "B" - "What would be the right approach", how about this?
In order to return that 405, Spring has raised a MethodArgumentNotValidException. You can provide custom handling for this like so:
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public MyMethodArgumentMessage handleMathodArgumentNotValidException(
MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
MyMethodArgumentMessage myMessage =
new MyMethodArgumentMessage(result.getFieldErrors());
return myMessage;
}
You should take a look at the #ExceptionHandler annotation. This lets you add methods such as the following to your controller. You can define your own exceptions and appropriate custom handlers for them. I use it to return well-structured XML and JSON from REST services. Although for it to work, you need to throw specific exceptions from your controller methods.
A good walk-through of using this was provided by Petri Kainulkainen in his blog:
http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-adding-validation-to-a-rest-api/

Categories

Resources