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.
Related
we have a spring boot app with a java package that has spring controllers with endpoints for admin-like functionality. right now they all start with the same request mapping.
so one way i could do authorization of every endpoint in the package is by WebSecurityConfigurerAdapter implementation...
http.authorizeRequests()
.antMatchers("/admin/**").access("hasRole('ADMIN')")
but i was thinking it would be nice to use AOP somehow to target the package of the admin controllers to PreAuthorize all controller methods in the package. just in case someone on the project creates a new controller in the proejct with a different request mapping it would automatically be projected. also, if we decided to PreAuthorize at the #Service level instead of the controller level then this way could be used as well.
so, is it possible to PreAuthorize at the package level with AOP or some other way?
Spring provides default AOP interceptor for #Secured and #PreAuthorized annotations, but it works only on class or method level, and AFAIK not intended to be expanded to package level.
To put it simply, Spring intercepts calls to certain methods or all class methods with these annotations and check whether SecurityContextHolder holds Authority object and whether its collection of GrantedAuthority matches any of the annotation's value field values. So, you can do the same thing using AOP, for example like this:
#Aspect
#Component
public class AdminServiceAOPAuthorization {
private static final List<String> ALLOWED_ROLES = List.of("ADMIN", "SUPER_ADMIN");
#Pointcut("within(com.example.service.admin.*)") // <- any method in any class of the package
public void adminServiceLayer() {}
#Before("adminServiceLayer()")
public void authorize(JoinPoint jp) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
// throw some exception depending on the logic
}
boolean authorized = authentication.getAuthorities().stream()
.anyMatch(ga -> ALLOWED_ROLES.contains(ga.getAuthority()));
if (!authorized) {
throw new AccessDeniedException("Access denied");
// log or whatever
}
}
}
For better performance I'd advise to use it at the service layer to let Spring create proxy using implemented interface (if you use them, of course) instead of proxying controller class.
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.
My project actual using spring + gradle system,
in some controller file, I added some global variable like this:
#Controller
#RequestMapping(value = "/admin/question")
public class QuestionAdminController {
List<Question> questions;
String message;
Company company;
this is my WebSecurityConfigurerAdapter class:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable(); // temporary disabled for testing file upload
http.headers().frameOptions().disable(); // disabled for h2 console
// Roles permissions
http.authorizeRequests()
// Directories access permissions
.antMatchers("/user/**").access("hasRole('USER')")
.antMatchers("/company/**").access("hasRole('COMPANY')")
.antMatchers("/admin/**").access("hasRole('ADMIN')")
.antMatchers("/db/**").access("hasRole('DBA')")
// All user can access to new routes at the root
.antMatchers("/**").permitAll()
// Other requests need the login
.anyRequest().authenticated()
// Configure the login page
.and().formLogin().loginPage("/login").successHandler(customSuccessHandler).permitAll()
// Default logout from Spring Security
.and().logout().permitAll()
.and()
.sessionManagement()
.maximumSessions(1).expiredUrl("/login?expired");
}
Now when I logging with another user, these global variables keep still the same value.
So I want to know: How can I initiate these global variable after log out?
A controller is not the best place to initialize and store conversational state or user specific data. A Controller by default has a singleton scope hence its shared by all threads servicing user requests.
You need to extract the user specific data into a separate class which has a session scope in the application context. The bean is initialized every time a new user session is initiated. An example using Java config
public class UserData{
List<Questions> questions;
String message;
// etc
}
//in your #Configuration class
#Bean
#Scope("session")
#ScopedProxy
public UserData userData(){
//initialize and return user data
}
In your controller inject UserData as follows
#Controller
#RequestMapping(value = "/admin/question")
public class QuestionAdminController {
#Autowired
private UserData userData;
}
NB.Your UserData bean needs to be a scoped proxy because the controller into which its injected has a longer scope.Hence the proxy has the responsibility of looking up the bean from the session or creating it if it does not exist
If you declare UserData a session scoped, then the #PostConstruct & #PreDestroy annotated methods will be executed when the session is created or destroyed. There you can manipulate global pareameters.
Might not be the best design however, but I need more details about what you want to achieve.
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.
Spring Security has the assumption of Authentication is a Principal.
public interface Authentication extends Principal, Serializable {}
HttpServletRequest has the method of getUserPrincipal which is responsible for accessing principal object.
Let's consider this case:
public interface RealPrincipal extends Principal {
public Integer getId();
}
Common Module A has Real Principal interface and implementation.
Module A uses Common Module A, Servlet Api and does not depend on Spring Security:
Module B uses Common Module A, Servlet Api and configures Spring Security. This module responsible for security and UserDetails implementation.
Web A uses Module A and Module B.
In order to use request methods, I am ending up with such an implementation:
public ModelAndView someRequestHandler(Principal principal) {
User activeUser = (User) ((Authentication) principal).getPrincipal();
...
}
This is forcing me to have dependency of Spring Security for the Module A and other modules. I believe that a proper servlet api abstraction should not depend on spring security. request.getUserPrincipal should return real principal.
Please explain why org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestWrapper returns
Authentication instead of Real Principal.
Edit: I have added Common Module A to my scenario and updated that Module B is responsible for security.
As Luke stated, Spring Security uses the Authentication for the Principal because it implements Principal. It does not use the Authentication#getPrincipal() because it is not guaranteed to be a Principal (it is an Object). In fact, in most situations Spring Security's Authentication#getPrincipal() returns a User (does not implement Principal), a custom UserDetails provided by users of the framework, or a String.
If you want Spring Security to handle this, you will likely need to implement this logic using an HttpServletRequestWrapper as Luke suggested. For example, you could do the following:
public RealPrincipalFilter extends OncePerRequestFilter {
public void doFiter(HttpServletRequest request, HttpServletResponse response, FilterChain) {
chain.doFilter(new RealPrincipalRequestWrapper(request), response);
}
private static final class RealPrincipalRequestWrapper
extends HttpServletRequestWrapper {
public Principal getUserPrincipal() {
Authentication auth = (Authentication) super.getPrincipal();
return auth == null ? null : (RealPrincipal) auth.getPrincipal()
}
}
}
#Configuration
#EnableWebSecurity
public WebSecurityConfig extends WebSecurityConfigurerAdapter {
public configure(HttpSecurity http) {
http
// ... other config ...
.addFilterAfter(new RealPrincipalFilter(), SecurityContextHolderAwareRequestFilter.class);
}
...
}
Alternatively, take a look at my answer on your other question for options to integrate with Spring MVC - Injecting Custom Principal to Controllers by Spring Security
The short answer is that Authentication is a Principal so that it can be used in APIs (such as the servlet API method you mention) which require one.
What does this mean in practice? Not a lot. Java's Principal interface has only one method getName, so if you want to do more than render the user's name, you need to know something more about the implementation.
You should probably think about what you mean when you use the phrases "real principal" and "proper servlet api abstraction". How would you expect to implement your someRequestHandler method if the principal was a "real" one, for example?