Spring security notes 04 modifying user permissions

Posted by iJoseph on Mon, 31 Jan 2022 17:07:15 +0100

We often encounter such scenarios as dynamic permission adjustment. For example, Zhang San is a department head and does not have the permission to delete employee data, but his superiors trust Zhang San and want to entrust this function to Zhang San.

Let's first log in with Zhang San (xsc001):

Let's look at Zhang San's role permissions printed in the background:

The current role is SALE_ADMIN, the permissions are add and modify

At this time, we need to give him the permission to add and delete employees. The problem here is that whether he directly modifies the database or uses the code to add these permissions and roles to him, if Zhang San is already logged in, You need to log out of the system and log in again before loading the latest permissions (because Zhang San has logged in, his login information is loaded in the spring security cache, and the permissions are loaded into the cache when logging in. The newly added permissions database is modified in the middle, but the currently logged in user is not loaded)

Obviously, the above is a very uneven permission assignment. What we need is that when users are dynamically added permissions, they can be used immediately!!!

Solution: (Reference) Solution of Alibaba's former senior R & D Engineer)

Let's first use Zhang San's account to access the deletion interface:

We saw that it was intercepted by our permission system, because the del method has no permission

Our solution is:
The interceptor is used to intercept the request processing. The user marked as needing to be processed refreshes the permission information of the current user.

The detailed idea is that we set a user list in the interceptor. When we set permissions for a user in the code, we add the user information to the user list. For all users in the user list (if any), we will reload the permissions of the users inside (query the latest permission list from the database), Then refresh the token of the current user
First of all, let's take a look at the Interceptor:

package com.mysecurity.liuyf.conf.security.filter;

import com.mysecurity.liuyf.pojo.MyUserDetails;

import com.mysecurity.liuyf.pojo.User;
import com.mysecurity.liuyf.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Component
public class RoleCheckInterceptor  implements HandlerInterceptor {
    @Autowired
    private UserService userService;
    //Save username
    public static Set<String> usersToUpdateRoles = new HashSet<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("RoleCheckInterceptor");
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if(auth != null) {
            // Anonymous user needs to be excluded
            if(auth.getPrincipal() instanceof MyUserDetails) {
                MyUserDetails userDetails = (MyUserDetails)auth.getPrincipal();
                String username = userDetails.getUsername();
                if(usersToUpdateRoles.size()>0 && usersToUpdateRoles.contains(username)) {
                    updateRoles(auth,userDetails);
                    usersToUpdateRoles.remove(username);
                }
            }
        }
        return true;
    }

    private void updateRoles(Authentication auth, MyUserDetails user) {
        User u=userService.findUserByName(user.getUsername());
        List<GrantedAuthority> list = new ArrayList<>();
        List<String> roles=userService.findUserRoleByUserId(u.getId());
        for (String role:roles){
            list.add(new SimpleGrantedAuthority("ROLE_"+role));
        }
        //Load the permission table
        List<String> permission=userService.findUserPermissionByUserId(u.getId());
        for (String per:permission){
            list.add(new SimpleGrantedAuthority(per));
        }

        Authentication newAuth = new UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(),list);
        SecurityContextHolder.getContext().setAuthentication(newAuth);
        System.out.println("RoleCheckInterceptor.updateRoles Refresh permission information");
        System.out.println(newAuth);
    }
}

1: dynamically query user permissions in method updateRoles, and then call

SecurityContextHolder.getContext().setAuthentication(newAuth);

Bank refresh
2: In the usersToUpdateRoles list, save the user who has modified the user permission (remember to remove the duplicate user name)

Finally, remember to put this connector into the configuration file to take effect:

package com.mysecurity.liuyf.conf;

import com.mysecurity.liuyf.Interceptor.TokenInterceptor;
import com.mysecurity.liuyf.conf.security.filter.RoleCheckInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    /**
     * Interceptors are used to dynamically refresh user permissions
     */
    @Resource
    private RoleCheckInterceptor roleCheckInterceptor ;
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(roleCheckInterceptor).addPathPatterns("/**");
    }
}

Finally, we simulate a method to modify user permissions:

/**
     *Test assign a new role
     * @param
     * @return
     */
    @GetMapping("/upAuthForUpRole")
    public void upAuthForUpRole(HttpServletResponse response) throws IOException {
        //Parameter 1: receive user ID
        //Parameter 2: receive permissions to be added / removed
        //Operation 1: database operation, which stores the data to be modified into the data

        //Store the user name in the list in the previous step, which is used to refresh the user permissions
        RoleCheckInterceptor.usersToUpdateRoles.add("xs001");
        //Redirect to another method
        response.sendRedirect("/user/upAuthForUpRoleOk");
    }


	 /**
     *Authorization succeeded
     * @param
     * @return
     */
    @GetMapping("/upAuthForUpRoleOk")
    public JsonResult upAuthForUpRoleOk() {
        return new JsonResult(ResultCode.SUCCESS,ResultCode.SUCCESS.msg());
    }

Here are pseudocodes written. The database operation can be supplemented by itself. The most important thing here is to list the users in the list

Reason for redirection: because the accessdenied handler is configured here, it will take precedence over our authorization interceptor. It will direct to another method directly here. Instead of waiting for the user's new authorization to be intercepted, it will directly execute the operation in the interceptor when authorizing the method

Final experiment:
We directly modify the user role in the database (because the pseudo code is written in the front). At this time, the user who logged in before did not quit, that is, he still did not have the permission to delete

Change Zhang San's role id to 1 (super tube)
At this time, the upAuthForUpRole method is called to refresh the authorization

Successfully redirected to another method
The prompt information in the authorization method is also successfully printed in the background:

Then you do not need to log out and access the del method:

We see that there is no need to log out and restart the server, and the dynamic authorization has been completed

Topics: Spring Boot Spring MVC spring-security