Authority management in front end and back end separation

In the traditional front and back-end development, permission management is mainly carried out through filters or interceptors (the permission management framework itself also realizes the function through filters). If users do not have a role or a permission, they cannot access a page.

However, in the separation of front and back ends, the page Jump is all left to the front end, and the back end only provides data. At this time, permission management can no longer follow the previous idea.

First of all, it should be clear that the front-end is displayed to users. The purpose of displaying or hiding all menus is not to achieve permission management, but to give users a good experience. You can't rely on the front-end hidden controls to achieve permission management, that is, data security can't rely on the front-end. Just like ordinary form submission, the front-end performs data verification to improve efficiency, To improve the user experience, the back end is the real way to ensure data integrity. (for example, when the front-end registers, an e-mail address is sent. The front-end has passed the verification, but the back-end needs to be verified again, because only the verification of the back-end can really ensure that the data is legitimate. The front-end is just to improve the user experience and give users a quick response, so as to prevent users from knowing the error when they send the data to the back-end and then return).

Therefore, the real data security is implemented at the back end. In the design process of the back end, it is necessary to ensure that each interface can be accessed only on the basis of meeting certain permissions. In other words, you are not afraid to expose the back-end data interface. Even if it is exposed, you can't access it as long as you don't have a corresponding role.

For a good user experience, the front end needs to hide the interfaces or menus that users cannot access.

Some people say, what if the user directly enters the path of a page in the address bar? At this time, if there is no additional processing, the user can enter a path directly into a page of the system, but there is no need to worry about data leakage. Because there is no relevant role, you cannot access the relevant interface. However, if the user does not operate in this way and enters a blank page, the user experience is poor. At this time, we can use the route navigation guard in Vue to monitor the page Jump. If the user wants to go to an unauthorized page, he can directly intercept it at the front route navigation guard, Redirect the login page, or just stay on the current page and don't let users jump.

In a word, all operations at the front end are to improve the user experience, not data security. The real effectiveness should be done at the back end.

For permission management in the back end, each role has a unified path.

Index. In the routing table Add permissions to the JS file as follows:

When the Home page reads the data and displays it, if the currently logged in user does not have the routing table index JS, the specified menu will not be displayed

preface

With the development of mobile Internet, the field of front-end development is becoming wider and wider. The front-end has long bid farewell to the era of cut-off diagram and ushered in the era of large-scale and engineering front-end. In recent years, with the rise of front-end frameworks such as react, angular and vue, the architecture of front-end and back-end separation is rapidly popular. But at the same time, permission control also brings problems.

After the front and back ends are separated, although the front end will also perform permission control, they are relatively simple. Moreover, only the front-end permission control is not the real meaning of permission control. Users can bypass the front-end control and directly send requests to the back-end.

Permission design

So far, the most popular permission design model is RBAC model, role-based access control

Apache Shrio and Spring Security, which are popular in the market, are designed based on this model. Permission system can be said to be the most basic in the whole system, but it can also be very complex. In actual projects, multiple systems, multiple user types and multiple use scenarios will be encountered, which requires specific analysis of specific problems, but the core RBAC model is unchanged, and we can expand it to meet the needs.

The following is a simple database table design of users, roles, operations and institutions, which basically meets most business scenarios.

front control

If the user logs in successfully, a Token will be generated, which will be attached with some basic user information. It is not recommended to attach role permission information. This Token will be attached when the user sends a request to the back end.

The front end is generally controlled by menus and buttons. After the user logs in and authenticates successfully, the menu information is obtained and rendered in real time according to the user ID. Button control, the situation is complicated. If the requirements are not very high, you can query it at one time and put it into the local cache for local authentication.

Here is a simple implementation:

hasRole: function(roles){
  var roleNames = this.userInfo.roleNames;
  if(roleNames!=""){
      var role = roles.split(",");
      var array  = roleNames.split(",");
      for(var i=0;i<role.length;i++){
          if(array.indexOf(role[i])>-1){
             return true;
          }
      }
  }
  return false;
}

press-button control:

<i-button type="primary" v-if="hasRole('admin')" icon="ios-cloud-download"  @click="exportCashReports">export</i-button>

Back end control

Although the front end is controlled, this is far from enough. Special users can avoid the front end control and directly send requests to the back end. At this time, if the back end does not perform security verification on the requests, it is easy to expose sensitive data.

In the era of single architecture, if I use a security framework, I only need an annotation or an interception configuration.

For example:

@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean (SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        shiroFilterFactoryBean.setLoginUrl("/login.html");
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
        Map<String, Filter> filtersMap = new LinkedHashMap<>();
        filtersMap.put("kickout", kickoutSessionControlFilter());
        shiroFilterFactoryBean.setFilters(filtersMap);
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        /**
         * Static file
         */
        filterChainDefinitionMap.put("/css/**","anon");
        filterChainDefinitionMap.put("/images/**","anon");
        filterChainDefinitionMap.put("/js/**","anon");
        filterChainDefinitionMap.put("/file/**","anon");
        /**
         * Sign in
         */
        filterChainDefinitionMap.put("/login.html","anon");
        filterChainDefinitionMap.put("/sys/logout","anon");
        filterChainDefinitionMap.put("/sys/login","anon");
        /**
         * Management background
         */
        filterChainDefinitionMap.put("/sys/**", "roles[admin]");
        filterChainDefinitionMap.put("/**", "kickout,authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
}

Or this:

 /**
 * Role list
 */
 @PostMapping("/list")
 @RequiresRoles(value="admin")
 public Result list(SysRole role){
     return sysRoleService.list(role);
 }

After the front and back ends are separated, the back end will not save any user information, and can only be verified through the token transmitted by the front end. Here, refer to Shrio's implementation idea and create an annotation to realize permission verification.

 
/**
 * Permission annotation
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresRoles {
 
    /**
     * A single String role name or multiple comma-delimitted role names required in order for the method
     * invocation to be allowed.
     */
    String[] value();
 
    /**
     * The logical operation for the permission check in case multiple roles are specified. AND is the default
     * @since 1.1.0
     */
    Logical logical() default Logical.OR;
}

Then write an interceptor to intercept the request. Here, some authentication codes are intercepted:

/**
 * @Description
 * @Date 2020/6/12
 */
@Component
public class AuthUtils {
 
    @Autowired
    private RedisUtils redisUtils;
 
    public boolean check(Object handler,String userId){
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Annotation permAnnotation= handlerMethod.getMethod().getAnnotation(RequiresPermissions.class);
        if(permAnnotation!=null) {
            String[] role = handlerMethod.getMethod().getAnnotation(RequiresPermissions.class).value();
            List<String> roleList = Arrays.asList(role);
            /**
             * Get the user's actual permission
             */
            List<Object> list = redisUtils.lGet("perms:"+userId,0,-1);
            List<String> permissions = roleList.stream().filter(item -> list.contains(item)).collect(toList());
            if (permissions.size() == 0) {
                return false;
            }
        }else{
            Annotation roleAnnotation= handlerMethod.getMethod().getAnnotation(RequiresRoles.class);
            if(roleAnnotation!=null){
                String[] role = handlerMethod.getMethod().getAnnotation(RequiresRoles.class).value();
                List<String> roleList = Arrays.asList(role);
                /**
                 * Get user's actual role
                 */
                List<Object> list = redisUtils.lGet("roles:"+userId,0,-1);
                List<String> roles = roleList.stream().filter(item -> list.contains(item)).collect(toList());
                if (roles.size() == 0) {
                    return false;
                }
            }
        }
        return true;
    }
}
 

Above, after the user logs in successfully, the user's role information will be saved to the cache. In the production environment, you can choose to read the database and cache according to the real-time business requirements.

Summary

Front and rear end development is not a silver bullet, nor is micro service. Don't blindly follow the trend of model selection, learn from the experience of large manufacturers, let alone talk about technical debt. What is suitable for your team is the best.

Of course, the project is not big, how to force, how to expand the team scale, how to promote and raise salary, so what I have in front of me is nonsense, which should be scored or scored!!!

Keywords: Javascript Front-end Spring Boot Vue.js

Added by jamesp on Tue, 18 Jan 2022 09:21:47 +0200