Check for User Role in Autogenerated Endpoints of JpaRepository - spring

let's say I have a JpaRepository managing an Entity and providing auto-generated CRUD-endpoints for creation and reading of said Entity:
#CrossOrigin
#RepositoryRestResource(path = "testentity", itemResourceRel = "testentity", collectionResourceRel = "testentity")
public interface TestEntityRepository extends JpaRepository<TestEntity, Long> {
}
Since this provides pageable and sortable results I would really like to use the provided endpoints, but I want to restrict access to these depending on User-Roles. Users are stored in the "session" field of the respective requests, sent by the client:
request.getSession().getAttribute("user")
Each user has a set of roles which specify their privileges. (just a List of Strings).
Is there an easy way to keep the auto-generated endpoints provided by JpaRepository and restrict access to them depending on the content of the user.roles field?
Note that Users are generated after previously authenticating via a third-party application. This means that the roles of each User are not persisted within a readable data-source, but have to be requested from said application each time a user logs in.
The login process therefor looks something like this:
User sends username and password
Application sends username and password to third party application
Third party application sends back If User exists and their roles
Application creates User Object with received roles
User is used in session and discarded when session expires (This means that each user only ever exists for the duration of each session)
User Class:
#Entity
#Table(name = "user")
#Data
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ElementCollection
private List<String> roles;
private String token;
private DateTime expiration;
}
For Clarification: If there are 2 roles ("user","admin") I would want the POST Endpoints to only be accessible for Users with the "admin"-role Where as the GET methods should be accessible by any User (Both "admin" and "user").

Spring-Security offers advanced options for restricting method access via annotations based on information from the security-context, e.g.:
#PreAuthorize("hasRole('admin'))
These can be used on the repository interface (for all methods), or on a per-method basis.
Official example: https://github.com/spring-projects/spring-data-examples/tree/main/rest/security
(Depending on your security configuration, you might need to set the Authentication of the SecurityContext in a custom way.)

Related

Spring - RESTful provide different entity representations

In advance, I'm not speaking of Content Negotiation. Let's assume I've a simple JPA entity, by the way it is convertible with a related DTO it doesn't matter.
#Entity
public class User {
...
private String email;
private String password;
...
}
I've a RESTful controller with two different routes, a secured one and a public one.
#RestController
public class UserController {
...
#GetMapping("/public")
private User publicRoute() {
return service.getLatestUser();
}
#Secured("...")
#GetMapping("/private")
private User privateRoute() {
return service.getLatestUser();
}
}
For both routes the same entity is returned, but in the first case a public representation, let's say for a user profile, without sensitive stuff like E-Mail and Password should be returned. However in the second case a private representation, let's say for the owner itself, is required.
Is there any elegant way for doing this? I tried it on JSON level with #JsonIgnore but it doesn't worked for me. Also I tried to use Response-Objects, but it results in a lot of boilerplate code! Any suggestions?
See Also:
Recommended by Ananthapadmanabhan there already exists some questions/resources about this topic:
Spring REST webservice serializing to multiple JSON formats
How do I serialize using two different getters based on JsonView in RestController?
You could have different DTO objects being returned from the two endpoints instead of returning the same Entity class, that way you can have control over which attributes should be there in the response.
Read here about the advantages of using a DTO .
Another approach that you could make is to have custom serializers and deserializers for your endpoint.
You could read here for more details.
And here
Ignore dto fields while sending back to controller.
you can write you own method if your object is not final
private User ignoreEmailAndPass(User user){User usr=new User();usr.setName();//send only required fields.}
from Question:
In the database table you can have two roles
Say like User and Owner
3.In the service,check if it is user or owner and get the required details then have the
two DTOs,for each of their information that you want to send,set the info and return.
Or have a Common DTO, conataining all the information and when want to send user info just ignore the other info{Subset} else all.
Tell me what do you think of this solution?

Springboot allow access to endpoint if userId matches

I am following up from this question:
How to configure Spring Boot Security so that a user is only allowed to update their own profile
Imagine I had an end-point /user/edit/{id}, I want this to be accessible if the user either tries to edit themslves (eg: a user with ID 1 accessing /user/edit/1 but not being able to access user/edit/2) or, if they are an admin, to be able to edit any user.
Is there any way I can achieve this in the security configuration?
.antMatchers("/user/edit/**").hasRole("ADMIN")
Would restrict this to admin users, I want either admin or the id matching the user's id.
The only thing I can think of is inside the controller having something like
#GetMapping("/edit/{id}")
public void edit(#PathVariable("id") int id, Principal principal) {
User u = (User) userDetailsService.loadUserByUsername(principal.getName());
if(u.getId() == id || u.getRoles().contains("ADMIN")) {
//accept uer
}
}
But I was under the impression we shouldn't encode access logic in our controller?
It is possible to use Spring Security's Method Security Expressions to do this. Example copied from the docs:
#PreAuthorize("#c.name == authentication.name")
public void doSomething(#P("c") Contact contact);
Read the sections preceding, as there is some configuration needed. Also note that if an expression is used repeatedly you can define your own security annotations.
I was under the impression we shouldn't encode access logic in our
controller?
"Should" is maybe too strong a word, IMHO. Security expressions are powerful, and in theory would allow you to keep all security checks separate from the controller logic. Easier to spot when a check is wrong, or missing. Easier to compare with the Swagger annotations too, if you are using those to document your endpoints.
But it can get trickier when you have to do something like filter rows returned so that the user only sees some of the results. Spring Security can do that using #PostFilter. But sometimes it isn't optimal. For example, if you know that certain rows aren't going to be returned you may be able to run a faster query, rather than filter out rows after the fact.
My first Spring Security project had queries like that, so ever since I have tended to use controller logic instead of security annotations. But that's not a good reason to never use annotations! So by all means use security expressions when you can, but if you have trouble with them or other considerations arise, integrating security with your controller logic isn't so bad IMHO.
To control role access in your controller you can use annotations like #Secured or #PreAuthorize.
To use the #Secured, put in you security config class:
#EnableGlobalMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig {
// ...
}
And now you can use it in your controller:
#Secured("ROLE_ADMIN")
#PostMapping
public Account post(Account account, double amount){
// ...
}
To use the #PreAuthorize, put in you security config class:
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
// ...
}
And now you can use it in your controller:
#PreAuthorize("hasAuthority('ROLE_ADMIN')")
#PostMapping
public Account post(Account account, double amount){
// ...
}
For more information you can check here the spring docs.

How to synchronize two session scoped beans in JSF?

I have this case where I have two session-scoped beans. One is used for Login-functionality. So this bean remembers what the current user is, which it sets after a user has successfully logged in.
There is another session scoped bean that allows the user to configure some stuff, which is also supposed to be be kept along the session. The user can go back to the configuration-site whenever he wants and see his old (from the same session) data. Important to know is that the user does not need to be logged in to use this site. Imagine like a cart on a webshop, with many shops you can put stuff in the cart before even logging in and it will remain throughout your session.
Here is where it gets tricky: On this configuration-site, the user can access some special functionality, such as permanently saving his configuration-stuff, but only if he is logged in. If not, he simply won't have the option. Again, very similar to a webshop, if you actually want to order the cart of your session you usually have to log in at that point.
The problem is that if the user first goes onto the configuration-site, then this session bean will be created first. The session bean retrieves the user by a binding-annotation (CurrentUser) which is #Provided by the Login-Bean via it's getter for the current user.
However, at creation time of the configuration-site bean, there is no current user.
Now, if the user then decides to go and login, the configuration-site bean will still think that there is no currentUser, since that field was initialized when the bean was initialized and there is no logic that will update it.
How can I handle this situation? Do I have to start manually putting and retrieving stuff from the Session-Objects? So far everything was handled automatically by JSF / Application Server simply because of the #SessionScoped annotations.
Edit: Here goes some code to explain the situation further:
The Login-Bean:
#SessionScoped
#Named
public class LoginUserManager implements Serializable {
private UserBean currentUser;
// Logic that does the login and set the currentUser if successfull
// ...
// "Produces" currentUsers for other beans, that want to inject it simply
// via the #CurrentUser annotation, see below
#Produces
#CurrentUser
public UserBean getCurrentUser() {
return currentUser;
}
}
Then there is the configuration-manager
#SessionScoped
#Named
public class ConfigurationManager implements Serializable {
// Session based configuration data here
// And, the current user (if any)
#CurrentUser
private UserBean currentUser;
}
The CurrentUser annotation should be a simple "binding annotation" if I understood correctly. It's taken from a snippet I saw on the internet, to be honest. I found it elegant, thought it's smooth to read and functionally identical to injecting the LoginUserManager directly and then calling it's getCurrentUser() getter.
#Retention(RUNTIME)
#Target({TYPE, METHOD, FIELD})
#BindingType
public #interface CurrentUser {
}

Spring Data REST: Infer owner from authenticated request

I have an #Entity that has a field owner:
#Entity
#EntityListeners({TalkListener.class})
public class Talk {
private #ManyToOne(targetEntity = User.class, optional = false) User owner;
// ...
}
Note: I have a listener in place that implementes #PrePersist. From what I have read it is discouraged to lookup request parameters from there (maybe it is also impossible, I didn't go on researching in this direction). There is the section about events in the docs, but it also seems purely entity-related and not taking the context of the request into account.
I would like to infer the owner from the authenticated user that POSTs the request. What is the easiest way to do so? If it is necessary to override the POST a snippet or linked example would be much appreciated.
Update 1:
#NeilMcGuigan suggested using Spring Data JPA's auditing features, which include a #CreatedBy annotation that would clearly solve the original question (and I would accept it as an answer).
Update 2:
A good tutorial about auditing with Spring Data JPA as well as an alternative of getting the principal within entity lifecycle events can be found here.
For educational purpose, what would be another way if I needed to access some value from the request to populate a value in my entity (say my current use case wasn't #CreatedBy but something different)?

Spring Ldap - multipe base names

I am trying to use spring LDAP /ODM to receive some attributes from LDAP. Is there a way to configure multiple base names in
<ldap:context-source
url="${ldap.url}"
base="${ldap.base}" // here ..is there a prop that will take an array of base names
username="${userdn}"
password="${password}" />
<ldap:ldap-template id="ldapTemplate" />
or in
#Entry(objectClasses = { "person"} base={..CAN I GIVE MULTIPLE BASENames here..})
public class LdapUser {
#Id
private Name dn;
//..
}
The app I am developing has users defined under one OU and internal TESTERs defined in another ou in our AD. So I am trying to see if I can use the same LDAP entry class for looking up everyone.
The ContextSource base is intended to specify the base of all operations on the ContextSource, and is typically set to the domain controller DN.
You can use ODM without specifying a base on the #Entry (or using a base DN higher up in the tree), but in that case you will typically use the #DnAttribute annotation in order to have the framework automatically build DNs for you (mainly needed when persisting entries back to LDAP).
If we assume your users are in the following structure:
dc=example,dc=com,ou=USERS
dc=example,dc=com,ou=TESTERS
Now, if you specify base dc=example,dc=com on the ContextSource you can have ODM handle this automatically as described briefly below:
#Entry(objectclasses={"person"})
public class Person {
#Id
private Name dn;
#DnAttribute(name="ou", index=0)
#Transient // Indicates that this is not an attribute on the entry
private String userType;
#Attribute(name="cn")
private String name;
// More attributes here
}
The above will handle automatic mapping of LDAP entries to and from the Person class. Now, if you want to find all persons, do:
List<Person> allPersons = ldapTemplate.findAll(Person.class);
If you want to find all testers you would do:
List<Person> testers = ldapTemplate.find(
query().base("ou=TESTERS"),
Person.class);
I am not very familiar with Spring LDAP but (IIRC) LDAP itself can only search from a single node (base). So, looking at the documentation, you might have to do a search from the organization (o=xx) with an LDAPQueryBuilder, adding conditions for the ous. See the javadocs.
No expert here, mind you.
With XML config at least, you can wire an LdapTemplate instance. One suggestion might be to make a new implementation called something like DelegatingLdapTemplate that gets injected with two regular templates (one per basename) and then delegates to them appropriately (or just calls one, then the other if the first one return 0 results), and use this in place of a normal template instance. This of course makes sense only if your use case really warrants this behavior (e.g. if you never know where to search for the user and have to check both locations). Otherwise, just make two separate beans.

Resources