How to configure Spring Boot Security so that a user is only allowed to update their own profile - spring

I have implemented the basic Spring Boot Security stuff in order to secure my web services. I know that you can grant access to some services only to some user Roles, but is it also possible to grant access to a specified user (user can be dynamic)?
Let's say we have a social app, where every user has their own profile. With the following rest-service, they should be the only one able to edit the profile:
#RestController
public class UserController {
#RequestMapping(method = RequestMethod.PUT, path = "/user/{userId}", ...)
public UserDetails updateUserDetails(#PathVariable("userId") String userId) {
// code for updating the description for the specified user
}}
}
How can i ensure with spring security, that only the user itself can update his personal profile? Any other user should be rejected. Is there an elegant way, how you can configure this behaviour?
I have tried to find a method for that inside my WebSecurityConfig, but with no success.
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// configure authorization for urls
.authorizeRequests()
// grant access to all users for root path and /home
//.antMatchers("/", "/home").permitAll()
// here i would like to grant access in the way, that only the user is allowed to perform this request by calling url with his userId
.antMatchers(HttpMethod.PUT,"/user/<userId>").and().httpBasic();
}
What is a good approach to implement this behaviour?

I think that the best way to implement something like this would be to inject the Principal (Object containing the user that is logged in for this request) into the controller and then check if the user id or username is matching.
#RestController
public class UserController {
#RequestMapping(method = RequestMethod.PUT, path = "/user/{userId}", ...)
public UserDetails updateUserDetails(#PathVariable("userId") String userId, Principal principal) {
CustomUserDetails userDetails = (CustomUserDetails) principal;
if (userDetails.getUserId().equals(userId)) {
// Update the user
}
}}
}
Note that you will need a custom UserDetails interface if you want to add the user id, because it only provided the username by default. Check this question if you want to know how.

Use #PreAuthorize annotation:
#PreAuthorize("#userId == principal.userId")
#RequestMapping(method = RequestMethod.PUT, path = "/user/{userId}", ...)
public UserDetails updateUserDetails(#PathVariable("userId") String userId) {
// code for updating the description for the specified user
}
This assumes that the class that implements UserDetails interface has a userId property.

Related

How to pass attribute to Spring Controller in a stateless application?

I am currently implementing a SAML SSO solution in my application where in my SAMLUserDetailsService, I am loading my user
#Service
public class SAMLUserDetailsServiceImpl implements SAMLUserDetailsService {
#Override
public Object loadUserBySAML(SAMLCredential credential) throws UsernameNotFoundException {
return new User(credential.getNameID().getValue());
}
}
I am then using a SavedRequestAwareAuthenticationSuccessHandler to redirect user to a landing controller upon successful authentication.
#Bean
public AuthenticationSuccessHandler successRedirectHandler() {
SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler =
new SavedRequestAwareAuthenticationSuccessHandler();
successRedirectHandler.setDefaultTargetUrl("/landing");
return successRedirectHandler;
}
Controller:
#RequestMapping("/landing")
public ResponseEntity landing(User user) {
return ResponseEntity.ok(user.getLoginName());
}
Is there a way to pass the User object to my controller. I noticed that this is usually done using a HandlerMethodArgumentResolver but since my application is stateless and does not use sessions, is there a way to achieve this using another way please?
You don't need injection for this. Use following instead:
SecurityContextHolder.getContext().getAuthentication().getName()
or
SecurityContextHolder.getContext().getAuthentication().getPrincipal()
In the latter case check the type of what getPrincipal() returned. It can be String, it can be UserDetails. If latter, cast it to UserDetails and call getUsername().

Spring Security using value of variable in class to authenticate

I am using Spring Security in my application. I am authenticating APIs based on the role (ADMIN, USER).
There is one API endpoint which I would like to restrict access using the value of a variable passed as parameter to it.
I have
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().exceptionHandling().authenticationEntryPoint(this.unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/api/**").authenticated()
.anyRequest().permitAll();
httpSecurity.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
}
I have a call
#PostMapping("/something")
public ResponseEntity<BotResponse> handleRequest(#Valid #RequestBody SomeClass someClass) {
// if someClass.getSomeValue() is not present in the User permissions, then it should give an unauthorized response.
return value(someClass);
}
The User in Spring Security is :
public Class User {
String userId;
String userName;
String authorities;
List<String> someList;
//Getters and setters for variables
}
And the SomeClass used is :
public Class SomeClass {
String someValue;
String userName;
...
// Getters and Setters
}
How do I not allow users based on if the value of someClass.getSomeValue is present in User's someList?
As per your question, one approach would be to get the UserDetails stored in your Spring Security Authentication Context and then check the concerned data in this context object against the value passed as the parameter. I'm assuming that you have all the required values stored in the Security Context.
This check can be done in the endpoint code itself(if you have a small number of such APIs). If there are multiple APIs that need the same logic, you will have to implement either a filter that filters only these API(config can be written in web.xml) or a pointcut(through AOP).
Perhaps you could do such kind of authorization with spring's global method security.
To use Method Level Authorization you need to add the following annotation to your Security Configuration class.
#EnableGlobalMethodSecurity(prePostEnabled = true)
Then apply #PreAuthorize using Spring Expression Language, to your end point. Something like..
#PostMapping("/something")
#PreAuthorize("#someService.checkUserAccess(principal, #someClass)")
public ResponseEntity<BotResponse> handleRequest(#Valid #RequestBody SomeClass someClass) {
// if someClass.getSomeValue() is not present in the User permissions, then it should give an unauthorized response.
return value(someClass);
}
#someService is a Bean which you would autowired in the Controller and define checkUserAccess() method in this been. Something like ..
public boolean checkUserAccess(Pricipal principal, SomeClass someClass) {
// here you can fetch your full user object from db or session (depending on your application architecture)
// apply what ever logic you want to apply, return true if user has access and false if no.
}
Note / Suggestion- You may add this checkUserAccess() method to your existing user service if your application design allows it, and autowire user service in the controller.

Cache user specific application data with Spring Security

In our microservice each authenticated Spring Security user has an associated application-specific data structure.
when thinking on how can we easily cache this data together with the user, we thought it would be good if it could have been done similar to this:
add the cached data to the in-memory-authentication when creating the users:
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth.inMemoryAuthentication().withUser("user").password("123").roles("ROLE");
auth.inMemoryAuthentication().withUser("user").cache(appDate);
...
}
pull the data in #RestController methods:
#RequestMapping("/foo")
public void foo() {
User user = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Object details = SecurityContextHolder.getContext().getAuthentication().getDetails();
Object cachedAppDate= SecurityContextHolder.getContext().getAuthentication().getCachedData();
}
Obviously the method in bold are a wish-list and do not exist.
Any advice on how to do this easily with the existing Spring Security framework?
thanks!
If you can get your cached data using only user name (no password required) you can use implement org.springframework.security.core.userdetails.UserDetailsService, it has only one method - UserDetails loadUserByUsername(String username), and return required data as custom UserDetail object. After implement interface, just pass it as UserDetailService for AuthenticationManagerBuilder.
If you need password to get that cached data, things got a bit complicated.
You should create your own AuthenticationProvider and put cached data in Principal or UserDetails. For example code for set additional data in Principal:
public class MyProvider implements org.springframework.security.authentication.AuthenticationProvider {
#Override
public boolean supports(Class<? extends Object> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
... // check login/password
Object cachedAppDate = "i'm cached data!";
MyUser user = new MyUser(token, cachedData);
UsernamePasswordAuthenticationToken output = new UsernamePasswordAuthenticationToken(user, authentication.getCredentials(), user.getAuthorities());
output.setDetails(authentication.getDetails());
return output;
}
}
public static class MyUser extends org.springframework.security.core.userdetails.User {
private final Object cachedData;
public User(UsernamePasswordAuthenticationToken token, Object cachedData) {
super(token.getName(), "", token.getAuthorities());
this.cachedData = cachedData;
}
public Object getCachedData() {
return this.cachedData;
}
}
and access cached data as ((MyUser)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getCachedData();

Spring - Call a Service method in JSTL

I'm using Spring Security to handle user authentication for my Spring MVC web app. I'm able to get the username from the Authentication object, but my username is the email address, and I want to be able to show the user's actual name in my header.
So I have my custom User class:
class Users{
String name;
String email;
String password;
// getters and setters
}
I thought about using an aop scoped proxy to set the User in the session, as explained in this blog: http://richardchesterwood.blogspot.co.uk/2011/03/using-sessions-in-spring-mvc-including.html . The problem I faced using this approach is that the AuthenticationSuccessHandler is actually a Service and should be stateless. So Spring doesn't autowire a Users object for me in the Service.
So I created a Service method that would get the username (or email) from the Authentication object and return my Users object. This I can use in my Controllers.
#Service
#Transactional
public class UserServiceImpl implements UserService {
#Override
public Users getCurrentUser() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User userD = (User)auth.getPrincipal();
Users currentUser = getUserByEmail(userD.getUsername());
return currentUser;
}
}
So is there a way that I can call this Service method from JSTL to get the user's full name, which I can display in my header?
Am also open to suggestions for a better way to implement this.
EDIT:
In my earlier approach using the AuthenticationSuccessHandler, my code goes like this:
#Service("userDetailsService")
#Transactional
public class UserAuthenticationServiceImpl implements AuthenticationSuccessHandler {
#Autowired
Users currentUser;
#Override
public void onAuthenticationSuccess(HttpServletRequest hsr, HttpServletResponse hsr1, Authentication a) throws IOException, ServletException {
User user = (User) a.getPrincipal();
Users user1 = userDao.getUserByEmail(user.getUsername());
currentUser.setName(user1.getName());
currentUser.setUserRoles(user1.getUserRoles());
//currentUser = user1;
}
}
And in my spring-servlet.xml file, I have this:
<bean id="currentUser" class="com.foo.bean.Users" scope="session">
<!-- this next element effects the proxying of the surrounding bean -->
<aop:scoped-proxy/>
</bean>
The problem I'm facing here is that Spring isn't autowiring my currentUser object because the Service isn't in the session scope.
If the only thing you need is the full name just use an AuthenticationSuccessHandler to retrieve the user and add the name to the session (or the full user if you need more then that).
#Override
public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse res, Authentication auth) throws IOException, ServletException {
User user = (User) auth.getPrincipal();
Users user1 = userDao.getUserByEmail(user.getUsername());
WebUtils.setSessionAttribute(req, "currentUser" user1);
}
Then in your JSP the only thing you need is ${currentUser.username}.
Although I wouldn't suggest stuffing the full user in the session I would suggest just adding the information needed.
WebUtils.setSessionAttribute(req, "currentUsername" user1.getUsername());
Then in your JSP ${currentUsername} saves you a lot of serialization overhead of the session.

How to perform RunAs using method security with Spring MVC 3.2 and Spring Security 3.1

I have a web application with Spring MVC 3.2 and Spring Security 3.1
I'm using roles base security and have implemented UserDetailsService and UserDetails to provide GrantedAuthority.
I've enabled global method security with jsr250-annotations
Everything upto here is working as expected with signed in user method access restricted to the declared roles.
I have a further requirement to run certain methods called during application initialisation as a special user with a 'system role' ideally along the lines of JavaEE RunAs.
I'm not sure how to do this in Spring Security.
Should I be trying to create a PreAuthenticatedAuthenticationToken with some made up values and a 'system role' authority.
I could then do something likeSecurityContextHolder.getContext().setAuthentication(token);
when initialising the application.
Alternatively should I be trying to use the RunAsManager. It sounds like what I need but I have not found any simple examples of how I actually could use it.
I'm fairly new to Spring Security and I'm unsure of the best way to proceed.
When my application starts
I run a post construct method in my spring bean to create a special user in memory with a system role.
This user object implements the org.springframework.security.core.userdetails.UserDetails interface.
I then use the user to create a security token org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken
The token is then set in the Security Context.
#Service
#Transactional(readOnly = true)
public class ApplicationConfiguration{
#Inject
MyService myService;
#PostConstruct
#Transactional(readOnly = false)
public void init(){
// ######## Application Starting #######"
// Create a user that meets the contract of the Spring UserDetails interface
UserAccountImpl sysAcc = new UserAccountImpl("system", "system", "system");
UserRole role = new UserRole(Role.SYSTEM_ROLE);
role.addUserPermission(Permission.SYSTEM);
sysAcc.addUserRole(role);
UserDetailsAdapter userDetails = new UserDetailsAdapter(sysAcc);
// Create a token and set the security context
PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken( userDetails, userDetails.getPassword(), userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(token);
// Now call service method with roles allowed
myService.initialiseSystem();
}
}
....
public interface MyService {
#RolesAllowed(SYSTEM)
public void initialiseSystem();
}
Do you really need to attach a role to the said app initialization? Why not just extract the code that needs to be run during initialization like so:
public interface Service {
#Secured("hasRole('USER')")
void service();
}
public class DefaultService implements Service {
#Override
public void service() {
doService();
}
public void doService() {
// Implementation here
}
}
...
public class AppInitializer {
#Autowired
private DefaultService service;
public void init() {
service.doService();
}
}
I believe that in this case a good solution for you would be to use the Spring Security OAuth because allow you have a greater integration to custom rules for access via tokens.
http://projects.spring.io/spring-security-oauth/

Resources