How to use method-based and class-based authorization for a controller class at the same time? - spring

I want to include Spring Security Basic Authentication in my project. I want to do both method-based and class-based authorization for a controller class. So the teacher will be able to access all end points in my controller class. But only some of them will be accessible to students. How do I provide this?
I added #PreAuthorize("hasRole('TEACHER')") above my class and added #PreAuthorize("hasRole('STUDENT')") above some of the methods. But I couldn't do what I wanted. What do you think I should do?

As #dur has suggested, there isn't a way to 'extend' the authorized roles for methods of a Controller (not any that I know of, at least). That being said, there is a nice way to implement the functionality you want. Let's say you have a controller named StudiesController:
#RestController
#PreAuthorize("hasRole('TEACHER')") //annotating the controller class with PreAuthorize will provide "default" required acccess for all of the controller's methods
public class StudiesController {
#PreAuthorize("hasAnyRole('STUDENT', 'TEACHER')") //both students AND teachers have to to be able to access the study schedule. hence, we override the "default" required access to also allow students to access this method
#GetMapping("/schedule")
public Map<String, String> getSchedule() {
//some code
}
#GetMapping("/grades/{studentId}")
public Map<String, Integer> getGradesOfStudent(#PathVariable Integer studentId) { //only teachers have to be able to access a student's grades, so we do not override the "default" access level
//some code
}
}
Basically, every method will only be accessible by Teachers by default. Any method you wish to make accessible to some other role, you annotate with #PreAuthorize and use the hasAnyRole method to list the allowed roles. This does lead to you having to write 'TEACHER' for such methods, though.
Also, do not forget to add #EnableMethodSecurity to your SecurityConfig class to enable the #PreAuthorize annotations

Related

Spring inject component into non-spring managed interface/abstract class and its subclasses

TLDR: I need an interface/abstract class and all classes implementing it to have access to a Spring managed bean. Can Spring inject a bean into an interface/abstract-class and its subclasses simply via #Autowired ?
I am working on an API built with Spring Webflux + Cloud Gateway that depending on the cookie JWT authorized party, identifies the User's policy group and assign an Attribute ENUM "InterfaceID" to the ServerWebExchange via exchange.getAttribute().put("InterfaceID",InterfaceID.A) after the JWT is validated, and currently uses "InterfaceID" to represent the different groups of users/different interface the user entered from.
JWTValidationFilter.java [Current]
switch(JWTValidator.validate(jwt).get("AZP")){
//if user is from company A or its partners
case "a":
case "aa":
exchange.getAttribute().put(InterfaceID.COMPANY_A_ACCESS);
break;
case "b":
exchange.getAttribute().put(InterfaceID.NORMAL_ACCESS);
...
}
For certain API endpoints (say /api/getSessionDocument), different "InterfaceID" fetches data from different DB/apis, as well as have different permission checking on top of that.
RequestController.java [Current]
#Autowired
APICallerUtil apiCallerUtil;
switch(exchange.getAttribute.get(InterfaceID)){
case "NORMAL_ACCESS":
apiCallerUtil.getDataFromApiA();
break;
case "COMPANY_A_ACCESS":
// call api B but check for permission from api D first
...
}
The endpoint's controller now has another switch statement, and to many code analyzers this have been a code smell. I have been trying to refactor this entire bit of code to use polymorphism to handle the different "getSessionDocument" flows, but i run into issues regarding the injection of util classes that calls specific APIs.
APICallerUtil.java class, exisiting class from the project, would prefer not to refactor this.
#Component
public class APICallerUtil{
#Value("${some uri to some API}") //different by environment and therefore cant be static final
private String uri1;
#Value("${some auth to some API}") //confidential
private String uri1AuthHeader;
//...
public JSONObject getDataFromApiA(String somekey){ //cant be static since uri1 is not static
//Some code that uses uri1 and apache httpclient
return data;
}
...
}
IBaseAccess.java
interface IBaseAccess{
default Mono<JSONObject> getSesssionDocument(ServerWebExchange e){return Mono.error("not implemented");}
}
RequestController.java [new]
#Autowired
APICallerUtil apiCallerUtil;
return exchange.getAttribute.get(InterfaceID).getSessionDocument(exchange);
NormalAccess.java
public class NormalAccess implements IBaseAccess{
//can i autowire APICallerUtil here?
//use constructor to pass the Util class reference here?
Mono<JSONObject> getSesssionDocument(ServerWebExchange e){
//need to call ApiA here
//need to call ApiC here
}
}
NormalAccess needs to call APICaller.getDataFromApiA(), but it needs a reference to the Spring managed instance of APICaller. What would be the "correct" way to pass the reference/autowire API caller into NormalAccess, or even better IBaseAccess (so that the implementing classes can use the Util bean)?
JWTValidationFilter.java [new]
switch(JWTValidator.validate(jwt).get("AZP")){
//if user is from company A or its partners
case "a":
case "aa":
exchange.getAttribute().put("InterfaceID",new CompanyAAccess(/*pass the util class here?*/));
break;
case "b":
exchange.getAttribute().put("InterfaceID",new NormalAccess(/*pass the util class here?*/));
...
}
I have tried several methods, but either I lack the knowledge on the specific Spring feature, or that method is deeemed a bad design choice by some, including:
Making the methods and fields in APICallerUtil static, via suggestions from Spring: How to inject a value to static field? and Assigning private static final field member using spring injection , then the Access classes can call the static methods.
Creating a contructor for IBaseAccess that consumes the APICallerUtil reference and store it inside. The JWTfilter would hold an autowired APICallerUtil and pass it in when the attribute is assigned.
Create a static class that provides the application context and Access classes use applicationContext.getBean("APICallerUtil"); to obtain the bean.
Use the #Configurable annotation? I could not find much documentation on how this works for interfaces/abstract-class.
I understand that there might not exist an absolute answer for this question, but regardless I'd like suggestion/feedback on which of these approaches are viable/good. Especailly concerning whether the APIUtil class should be static or not.

Spring REST: Show certain data of a User only if requester is a friend

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)

Is it possible to inject objects into templates in a transversal way?

I have multiple templates, but in all I need to know if the user is authenticated or not to know whether to show the access button or exit.
Currently in each controller I must send the object of the user entity to the data model for the template, but this means initializing the user service on all controllers.
Is there any way to intercept the controller and automatically inject the user entity into the model without affecting the normal use of the model in each controller?
I currently know that it is possible to use the aspect interceptor to control the use of controllers from their classes and their functions, but I don't know how to intercept the "model" argument and fill it with the entity.
You could use #ControllerAdvice with #ModelAttribute. Just create a class like this:
#ControllerAdvice
public class ModelAttributesForEveryController {
private UserService userService;
#Autowired
public ModelAttributesForEveryController(UserService userService) {
this. userService = userService;
}
#ModelAttribute("userEntity")
public String userEntity() {
// Here goes your logic to get the User entity
return theUserEntity;
}
#ModelAttribute("isLoggedIn")
public String isLoggedIn() {
// Here goes your logic to determine if logged in or not
return isLogged;
}
And then you can use that model attribute named userEntity in you template like any other attribute
For example using the isLoggedIn model attribute (a very dummy example):
<button th:if="${isLoggedIn}"> Exit</button>
<button th:if="${!isLoggedIn}"> Sign In</button>
If you just need to know the authenticated state, you can add the thymeleaf-extras-springsecurity5 dependency to your project, then you can use sec:authorize="!isAuthenticated()" to check logged in or not.

How to implement access protected by username in spring

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){
}
}

Protecting method calls in Spring multiactionController using methodNameResolver

I am using Spring 3 and implemented MVC using simpleUrlMapping. I am having CustomerController class. In CustomerController I am having three methods:
View customer
Add customer
Delete customer
The above actions are getting called using method name resolver.
My requirement over here depending upon the logged in user and privilege I want to protect the corresponding method calls.
Delete customer method should be called by the privilege user and not by all the user.
I am using Spring Security as well. Is there any way to protect the delete customer method with Spring security?
options:
#RequestMapping
public void deleteCustomer(HttpServletRequest request) {
if(request.isUserInRole("ROLE_ADMIN"){
// do deletion
};
}
or use #EnableGlobalMethodSecurity
#PreAuthorize("hasRole('ROLE_ADMIN')")
#RequestMapping
public void deleteCustomer(HttpServletRequest request) {

Resources