Spring security role-based authorisation best practices - java

So I basically have a controller method with a PreAuthorize annotation. By default the method will return all projects. The method signature also includes an optional query string (blank query means retrieve all records).
The issue is that if the logged in user is only supposed to view/manage his/her own records, the query string needs to include a filter in it such as "clientId:2".
Now to do that, I was thinking of using the Principal object to retrieve the logged in user and check if he/she is a client as well, then I update the query by adding the required filter to it.
I am just not sure if this is the best approach for this type of issues.
#PreAuthorize("hasAuthority('MANAGE_ALL') OR hasAuthority('VIEW_ALL') OR hasAuthority('MANAGE_OWN')")
#RequestMapping(value = "/projects", method = RequestMethod.GET)
public ResponseEntity<RestResponse> list(Principal principal, #RequestParam(value = "query", required = false, defaultValue = "") String query) {
//If a client is logged in, he/she will have the MANAGE_OWN authority so will need to update the query string to include clientId:<logged-in-client-id>

I would rather move the #PreAuthorize to an application service.
class SomeApplicationService {
UserService userService;
SecurityService securityService;
#PreAuthorize("hasAuthority('MANAGE_ALL') OR hasAuthority('VIEW_ALL') OR hasAuthority('MANAGE_OWN')")
public List<Project> getProjects(String clientId) {
User currentUser = userService.getLoggedInUser();
if(securityService.canManageAllProjects(currentUser))
//get all projects or projects of clientId
else if(securityService.canManageOwnProjects(currentUser))
//get own projects, ignore clientId
}
}

Related

Spring OAuth2 extract Principal from access token string

I have access token received in controller and I need to extract Principal from string access token. Without using Authentication in method argument since in this object will be different user. Simple decoding of token should help. Anyone know how to do that from just access token string?
Example
#RequestMapping(value = "create", method = RequestMethod.POST)
public ResponseEntity create(Authentication authentication,#RequestParam("access_token") String accessToken) {
//extract Principal from accessToken variable
}
After some time I manage to get Principal from access token string.
#Autowired
private TokenStore tokenStore;
#RequestMapping(value = "create", method = RequestMethod.POST)
public ResponseEntity create(Authentication authentication,#RequestParam("access_token") String accessToken) {
tokenStore.readAuthentication(accessToken).getPrincipal();
}
I don't know why you're sending another user's token in the request, which i find it dangerous cause access token contain sensible information ( credentials ). i advise you to change the way you identify the second user by creating something like action or identification token ( the schema you define will contain the id of the user and the information you want to send ).
in case you have another phylosophhy that you didn't mention and assuming the access token is a Jwt, you must first validate it, using the algorithm and the private key used to hash it.if it's a valid token, you can access its content.
#RequestMapping(value = "create", method = RequestMethod.POST)
public ResponseEntity create(Authentication authentication,#RequestParam("access_token") JwtAuthenticationToken accessToken) {
// validate your accessToken
// to access the token details
accessToken.getTokenAttributes().get(A_KEY_IN_YOUR_TOKEN)
}
check this class

Spring RequestMapping with custom isLoggedIn parameter

I've been searching internet for several hours now, and cannot find a way to add my own variables into request mapping.
We are using a custom user authentication system. And I want to serve 2 different controllers for the same path depending on if the user is authenticated or not. More specifically, I want to create controllers that only mapped to if the user is not authenticated.
How can I achieve something like this: (I want to define isLoggedIn myself).
public class PageController {
#RequestMapping(value = "/page", isLoggedIn = false)
#ResponseBody
String getPage(){
return "Page content";
}
}
I want request to hit this controller if user is not logged in, and fallback to catch all if user is logged in. I am open to solutions using Interceptors, Custom Annotations, or extending RequestMapping or anything else.
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#RequestMapping(
method = {RequestMethod.GET}
)
public #interface CustomGetRequestMapping {
#AliasFor(value = "value", annotation = RequestMapping.class)
String path();
boolean isLoggedIn() default false;
}
I guess this is not enough because you have to write interceptor and catch the logged in user.

Email or username validation

I want to check the email from database through controller and check if the email already exists or not!!! (email = column in db), please suggest me some ideas i'm new here
#RequestMapping(value = "/signup", method = RequestMethod.POST)
public String saveUser(#ModelAttribute User user, Model model) {
if (!user.getName().isEmpty() || !user.getEmail().isEmpty() || !user.getPassword().isEmpty()) {
user.setPassword(DigestUtils.md5DigestAsHex(user.getPassword().getBytes()));
udao.addUser(user);
return "login";
}
else {
model.addAttribute("error", "fill the form completely!!!");
return "signup";
}
}
Assuming you have the udao as your data access, you just need to create a method that gets the user object given an email id like
#Query(value="select * from users where email = :emailId and isactive=1", nativeQuery=true)
User getUserByEmailId(#Param("emailId")String emailId);
OR
#Query(value="select count(*) from users where email = :emailId and isactive=1", nativeQuery=true)
Integer getUserByEmailId(#Param("emailId")String emailId);
In the option2, you just get the count of the users having the email, a value of 0 indicates that the email is Unique and otherwise.
Once this DAO is completed, you can verify by calling this method and if your response is null, it means that there is no user in the db, else user is present, you can show the end user that the user already exists with the provided email id.
Couple of points for improving the code,
Please do not write your DAO accessing logic in the controller, it is best advised to use a service like UserDetailsService so that all the data access and business rules are applied in the service facilitating SOC, UnitTestability and lot more.
Also, consider using a highly secure algorithm for handling passwords like PBKDF2 which is very secure.
HTH
Assuming that you're using Spring Data JPA you can query the DB in order to check if there is an user with that email, by Spring Data JPA Docs (check the Example 60. Query method declaration in UserRepository):
public interface UserRepository extends Repository<User, Long> {
User findByEmail(String emailAddress);
}
So, in your controller you can do something like:
#Autowired
UserRepository userRepository;
#RequestMapping(value = "/signup", method = RequestMethod.POST)
public String saveUser(#ModelAttribute User user, Model model) {
if (!user.getName().isEmpty() || !user.getEmail().isEmpty() || !user.getPassword().isEmpty()) {
User userFromDb = userRepository.findByEmail(user.getEmail());
// Please note that I'm not sure if it will return null or empty User object, you need to check that
if (userFromDb == null) {
// Do the logic if no email exists in the DB
} else {
// Do the logic if an email exists in the DB
}
}
}

Getting the current logged in user name when using Spring Security

Its a coupon system app using Spring security, spring MVC,
now.
when the app starts, I need to somehow initialize the current logged in user into the controller.
Issue is:
If I try to get the current user via SecurityContextHolder it is impossible because it seems like spring is initializing the controllers before the security so I cannot get it in the controller.
Is there anything I'm missing? a different approach of getting the current logged in user after he logs in?
What you need is called #AuthenticationPrincipal.
You can inject it in controller method like this:
#GetMapping("/")
public void get(#AuthinticationPrincipal User user){ ... }
Here is documentation
Alternatively, you can create your own annotation and custom argument resolver, and inject whatever you want.
Solution 1: Principal principal
#RequestMapping(value = {"/", ""})
public String start(Principal principal, Model model) {
String currentUser = principal.getName();
return currentUser;
}
Solution 2: Authentication authentication
#RequestMapping(value = {"/", ""})
public String currentUserName(Authentication authentication) {
return authentication.getName();
}
Solution 3: SecurityContextHolder
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
More Here

PUT request in Spring MVC

I'm trying to write a simple PUT request method in Spring MVC. I got the following:
#RequestMapping(value = "/users/{id}", method = RequestMethod.PUT)
public #ResponseBody User updateUser(#PathVariable("id") long id,
String name,
String email) {
User user = repository.findOne(id);
user.setName(name);
user.setEmail(email);
System.out.println(user.toString());
repository.save(user);
return user;
}
Which is obviously wrong, because it returns the following:
User{id=1, name='null', email='null'}
I also tried with #RequestBody annotation, but that also did not help. Any ideas what I'm doing wrong here would be greatly appreciated.
You can receive name and email whith the #RequestBody annotation:
#RequestMapping(value = "/users/{id}", method = RequestMethod.PUT)
public #ResponseBody User updateUser(#PathVariable("id") long id,
#RequestBody User user) {}
This is a better practice when it comes to REST applications, as your URL becomes more clean and rest-style.
You can even put a #Valid annotation on the User and validate its properties.
On your postman client, you send the User as a JSON, on the body of your request, not on the URL. Don't forget that your User class should have the same fields of your sent JSON object.
See here:
You did not tell spring how to bind the name and email parameters from the request. For example, by adding a #RequestParam:
public #ResponseBody User updateUser(#PathVariable("id") long id,
#RequestParam String name,
#RequestParam String email) { ... }
name and email parameters will be populated from the query strings in the request. For instance, if you fire a request to /users/1?name=Josh&email=jb#ex.com, you will get this response:
User{id=1, name='Josh', email='jb#ex.com'}
In order to gain more insight about defining handler methods, check out the spring documentation.

Categories

Resources