Spring REST Api -- access User details in repository - java

The setup of the RESPApi project is:
SpringBoot
Spring's OAuth2
In the project we have many clients, so SQL queries almost always have "... and clientId = ?" in the where clause.
We store clientId in the SecurityContext with other user details (we extend Spring's User class).
The question is: how to get the User object in the #Repository?
Possible solutions we can think of:
In every repository implementation add
SecurityContextHolder.getContext().getAuthentication()
cast the result to our custom UserDetails implementation and use it.
Cons: somehow I feel there's a better solution.
Add #AuthenticationPrincipal annotated parameters to the controllers and then pass the parameters to the service layer and then to the repository layer.
Cons: passing the paremeter though 2 layers only to obtain clientId doesn't seem reasonable.
I thought about #Autowired paramter MyUser user in the #Repository class. The first try was to create #Configuration annotated class in which there will be a method
#Bean
public MyUser getUser() {
SecurityContext context = SecurityContextHolder.getContext();
if (context != null) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
return (MyUser) authentication.getPrincipal();
}
}
return null;
}
But the bean is null and I cannot use it.
For now we've ended up with solution nr 1 but I feel there must be a better way.
Any ideas how to solve this problem?

If you're using Spring Data (or have the time to switch to using it), you can use the SecurityEvaluationContextExtension and use principal directly in your queries:
https://stackoverflow.com/a/29692158/1777072
If not, you could hide the static access if it offends (or if you want more control over it changing in future):
#Component
public class AuthenticationHelper {
public Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
}
Then inject that class into your Repository.
Or your Service. That's probably a better fit than the Repository.
I like to keep Repositories dumb (ultimately using Spring Data to avoid writing them entirely).
And I like to think of Services as being separated out of the web layer, running on separate boxes (even if they aren't). In that situation, you would never pass the Authentication details over HTTP from Controller to Service. The service would obtain authentication details for itself, rather than just trusting what the web layer sent it.
So I think the Service should get the details itself, rather than the Controller passing them through.

Your bean is null because by default beans are singleton and they are created when the application starts, and as you can imagine, you are not going to have a SecurityContext at that point.
Try declaring your bean with request scope, in this way:
#Bean
#Scope(value=WebApplicationContext.SCOPE_REQUEST, proxyMode=ScopedProxyMode.TARGET_CLASS)
public MyUser getUser() {
.....
}

Related

Different response based on logged user role

In my RestController I have POST method which returns different object based on user role:
#PostMapping("getlocal")
#PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')")
public ResponseEntity<LocalDto> getLocal(#RequestBody LocalRequest localRequest){
return status(OK).body(localService.findLocalBylocalId(localRequest));
}
Service method:
public LocalDto findLocalBylocalId(LocalRequest localRequest) {
Role role = userRoleRepository.findByUsername(localRequest.getUsername());
if(role == Role.ROLE_ADMIN) //return localDto infromation object for ADMIN
else if(role == Role.ROLE_USER) //return localDto information for USER
}
LocalRequest contains username of current logged in user.
The problem is when user will authenticate and hit this endpoint he can pass to RequestBody admin's username. In this case he will get access to admin resources even if he is logged as USER.
How to avoid this situation and modify the code?
Should I create two different endpoints - one secured only for USER, and one secured only for ADMIN?
Should I pass Principal object instead passing username in POST method as parameter? Can I pass Princpal object if I am using jwt mechanism?
You can access the currently authenticated username by specifying Principal as an argument. For example:
#PostMapping("getlocal")
#PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')")
public ResponseEntity<LocalDto> getLocal(Principal principal){
return status(OK).body(localService.findLocalBylocalId(principal.getName()));
}
This works because Spring MVC will resolve the Principal argument from the HttpServletRequest.getUserPrincipal() method and Spring Security overrides the method to align with the currently logged in user. You would then need to update your LocalDto to accept a username instead of a Localrequest.
Alternatively, you can also resolve the entire Spring Security Authentication in the same way since the HttpServletRequest.getUserPrincipal() will be an Authentication.
#PostMapping("getlocal")
#PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')")
public ResponseEntity<LocalDto> getLocal(Authentication authentication){
return status(OK).body(localService.findLocalBylocalId(principal.getName()));
}
This gives you access to the roles without needing to look them up again. The disadvantage of this approach is that you are now relying on Spring Security's API directly.
You can also consider using the #AuthenticationPrincipal annotation to decouple yourself from Spring Security. This approach is the best if you need access to more than just the username because you can still be decoupled from Spring Security, but it also involves more work (i.e for username/password authentication you need a custom UserDetailsService, custom UserDetails). Because the amount of work/variables here, it is difficult to provide more guidance than a link to the documentation without further details.

Spring Security and Web MVC: How to save data that belong to a user?

What is the best pattern to follow when saving an object that belongs to a authenticated user using Spring Security and Web MVC?
All the tutorials that I've come across show how to authenticate a user logging in and that's as far as they go, they don't explain how to work with multiple users and their data. For example: A logged in user is saving a new blog post. Here is what the controller looks like:
#PostMapping("/blog/save")
public String blogSavePost(#AuthenticationPrincipal CurrentUser currentUser, BlogForm blogForm) {
blogService.save(currentUser, blogForm);
return "redirect:/blog";
}
Should the currentUser be passed to the blogService where the service then sets it on the entity Blog (which is then passed to BlogRepository for saving)? It seems a bit tedious to have to pass this on every save or edit..
What about when editing an object - since the id of the object being saved is passed to the controller (either as a parameter of part of the form object), it's open to being changed by a user. How should the service layer verify that the data being saved belongs to the user? I figure that there is an easy way to handle this with Spring Security, I just haven't come across what it is..
Question 1
Entirely up to you. I would just set the association in the Controller before passing it to the service:
Question 2.
You can prevent binding of certain fields by defining a binder in youir controller:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setDisallowedFields("userId");
}
You can also use method level security to prevent users from editing entities other than those that belong to them. See here for an example:
Spring Security in conjuction with Spring repositories to secure and authenticate various parts of a website
Alternatively you could use a Web Security expression that would secure the endpoint for save/update/delete requests.
http://docs.spring.io/spring-security/site/docs/current/reference/html/el-access.html#el-access-web

Spring controllers - thread safety and storing resources

I have a question about spring mvc and thread safety.
We are developing web application that will be stored on tomcat. If I understand right, Tomcat creates thread per request and it has some thread pool. Now, dispatcher servlet is shared between requests and probably is thread safe.
But when I create controller like this:
#Controller
#RequestMapping("/manage")
public class QuestionManagementController {
he has Singleton scope so the same controller is used by every request that comes from every user.
I am wondering how this problem is usually solved:
1: are controllers created with Session scope? (but I think that there also could be problems if one user quickly do some things that may lead to race conditions in the controller).
2: controllers are scoped as request
3: creating stateless controllers that don't share any variables at class level, or have them in read only mode
or maybe there is some better "best practice" that solves this kind of problem.
I am asking this question, because now we have them as Singleton scoped, and there is a problem, that in most methods we are querying for user in the database , and we can't store this information in class level variable because of the scope. We could try to use some thread safe collection but later there could be other resources needing synchronized access.
A lot of parameters can be added to the controller methods like request, session, principal etc to avoid your problem.
Usually there's a 3-layers architecture:
#Controller (they delegates to services)
#Service (they do the work using DAOs or Repositories)
#Repository (or DAOs, they do DB access)
So depending on what you are querying for in the DB, I would advise having a service, injected by Spring with a cache if hitting the DB is costly and everytime you need the stuff from the DB call the service (i.e. nothing stored in the controller class level)
A short example, let's say we are behind spring-security and everything need a fully logged-in user. We have an userData table where the key is the user login, we have an url /data to get a page showing my user data:
#Controller
#RequestMapping("/data")
public class UserDataController
{
#Autowired
private UserService userService;
#RequestMapping(value = "", method = RequestMethod.GET)
public ModelAndView data(final Principal principal) {
Assert.notNull(principal); // throw if assertion fails
Assert.hasText(principal.getName());
final UserData userData = this.userService.findByUserName(principal.getName());
Assert.notNull(userData, "userData not found");
return new ModelAndView("userData", "userData", userData);
}
}
#Service("userService")
public class userService
{
private static final String USERDATA_CACHE = "com.acme.foo.UserData";
#Autowired
private UserDataRepository userDataRepository;
#Cacheable(USERDATA_CACHE)
public UserData findByUserName(final String userName) {
return this.userDataRepository.findByUserName(userName);
}
}
#Repository
public class UserDataRepository
{
// or use spring-data-jpa
public UserData findByUserName(final String userName) {
// query table userData and create an UserData from results.
}
}
Here I use principal and spring ensure that this is current user one.
Refs:
#Cachable
see also Initialize Singletons in Spring Framework 3 MVC
Note sure if this answer fully to your concerns

SpringMVC 3 Spring Security Creating User Login

I'm trying to use this tutorial to create a spring mvc user login page.
The tutorial is great but one thing is not clear, on the second page he talks about the UserRepository interface. The interface methods return User. What I'm confused about is of the User he's referring to is the User object part of the spring framework? The reason I ask if because I want to have an email address field which is not there in the User object which is part of the Spring security framework.
Also, in his implementation of the UserLoginService he has a method:
#Override
public User getLoggedUser() {
User loggedUser = null;
AuthenticationUserDetails userDetails = getLoggedUserDetails();
if (userDetails != null) {
loggedUser = userRepository.findById(userDetails.getId());
}
return loggedUser;
}
the trouble is that the AuthenticationUserDetails does not have a getId() method, which makes me think he intends us to extend Spring's User to create our own Account entity or something.
I want to use Hibernate to create my Account and Role entities and so far every tutorial I've found seems to be before Spring MVC 3 or just giving bits and pieces.
Can anyone provide clarification on this or refer me to a good tutorial for User Login with Spring Security and SpringMVC?
The User is a Spring Security UserDetails object. If you want to extend the object to add more fields, implement the UserDetailsService interface and extend the UserDetails object with your own fields. Then configure Spring Security to use your service, as follows:
<security:authentication-manager>
<security:authentication-provider user-service-ref="myDetailsService" />
</security:authentication-manager>

How do you configure a Session-Scoped bean to be able to access information from the request and from other services?

In order for this session-scoped bean to work, it needs access to the Request object to allow it to determine the privileges of the logged-in user.
It also needs to be able to access the userService - another bean.
What does it need in order to gain access to these resources?
#Configuration
public class ExceptionResolverBuilder
{
#Bean #Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public ExceptionResolver getExceptionResolver()
{
ExceptionResolver er = new ExceptionResolver();
User user = userService.getLoggedInUser(request);
if(user.isAdmin())
{
sendEmail("Caught exception:" + exeption.getMessage());
}
else
{
writeLog("Caught exception:" + exeption.getMessage());
}
return er;
}
}
Rather annoyingly, session-scoped beans don't get easy access to the request that initiated the session.
However, in your case you shouldn't need to. Assuming that your ExceptionResolver is an implementation of HandlerExceptionResolver, then there's no reason to put your logging logic into ExceptionResolverBuilder.getExceptionResolver(), since the resolver will get passed the current HttpServletRequest in the resolveException method.
Also consider using the #ExceptionResolver annotation, which makes life even easier, and also gets access to the current request.
In order for this session-scoped bean to work, it needs access to the Request object to allow it to determine the privileges of the logged-in user.
AFAIK, you can only get the current request if it has been passed as a parameter, or via a thread local.
If you are using SpringSecurity, you get the current request's authentication information by calling SecurityContext.getContext(). (This typically uses a thread local.) However, I'm not sure this will work because your method might be called at a point when the security context hasn't been set.
It also needs to be able to access the userService - another bean.
You need to arrange that this is provided by dependency injection.
For a bean that is defined as a #Component you can autowire the HttpServletRequest like so:
#Component
#Scope("session")
public class Foo {
#Autowired private HTTPServletRequest request;
//
}
But since you are using #Bean you can't do this.
You can get the current request as follows:
ServletRequestAttributes sra = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest req = sra.getRequest();
This uses thread-local under the covers.
If you are using Spring MVC that's all you need. If you are not using Spring MVC then you will need to register a RequestContextListener or RequestContextFilter in your web.xml.

Categories