基于数据库Rbac数据模型控制权限

前面都是讲的怎么在权限规则基本不变的情况下,怎么写代码控制权限;

这一节要实现内管系统的场景;

这些所有的信息都必须存在数据库中。因为变动频繁,员工离职、部门调动,新增权限等;

通用RBAC数据模型

Role-Based-Access Control

通常由三直系表,两张关系表

对于资源表:存储数据的表现是 某一个url的别名是菜单或则按钮;所以url和多个菜单或则按钮绑定;

这样业务人员分配权限的时候才能看得懂

虽然是多对多,但是可以根据业务需要,进行一对多,比如一个用户只能拥有一个角色;

这个数据模型可以解决ui的显示问题和后台的程序权限控制

ui显示问题:提供一个查询该用户所有资源信息,然后由前端进行控制哪些资源显示或隐藏
url的程序控制:由security来处理

数据库中的数据如何交由security?

// 任意请求都必须走自定义的表达式
// 该表达式的含义也很简单:rbacService 在容器中的beanName名称,后面的是方法名和参数名
config.anyRequest().access("@rbacService.hasPermission(request,authentication)")

只要有了入口,那么来实现这个功能;

新建一个项目,用来写这个表达式的功能;然后demo项目引用和配置表达式

security-authorize 依赖

dependencies {
    compile 'javax.servlet:javax.servlet-api'
    // 核心的类都在该包中
    compile 'org.springframework.security:spring-security-core'
}

接口

package cn.mrcode.imooc.springsecurity.securityauthorize;

import org.springframework.security.core.Authentication;

import javax.servlet.http.HttpServletRequest;

public interface RbacService {

    boolean hasPermission(HttpServletRequest request, Authentication authentication);
}

实现

package cn.mrcode.imooc.springsecurity.securityauthorize;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import javax.servlet.http.HttpServletRequest;
import java.util.HashSet;
import java.util.Set;

@Component("rbacService")
public class RbacServiceImpl implements RbacService {

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
        Object principal = authentication.getPrincipal();

        boolean hasPermission = false;
        // 有可能 principal 是一个Anonymous
        // 所以只要是一个UserDetails那么就能标识是经过了我们自己的数据库查询的
        // 当前需要先配置UserDetailsServices
        if (principal instanceof UserDetails) {
            String username = ((UserDetails) principal).getUsername();
            //读取用户所拥有权限的所有URL
            Set<String> urls = new HashSet<>();
            for (String url : urls) {
                if (antPathMatcher.match(url, request.getRequestURI())) {
                    hasPermission = true;
                    break;
                }
            }

        }

        return hasPermission;
    }
}

demo项目中引用该包;并配置

package com.example.demo.security;

import cn.mrcode.imooc.springsecurity.securitycore.authorize.AuthorizeConfigProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.stereotype.Component;

/**
 * @author : zhuqiang
 * @version : V1.0
 * @date : 2018/8/12 21:25
 */
@Component
public class DemoAuthorizeConfigProvider implements AuthorizeConfigProvider {
    @Override
    public void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) {
        config.antMatchers(
                "/user/regist", // 注册请求
                // org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController
                // BasicErrorController 类提供的默认错误信息处理服务
                "/error",
                "/connect/*",
                "/auth/*",
                "/signin",
                "/social/signUp",  // app注册跳转服务
                "/swagger-ui.html",
                "/swagger-ui.html/**",
                "/webjars/**",
                "/swagger-resources/**",
                "/v2/**"
        )
                .permitAll();
        // 使用自定义的
        config.anyRequest().access("@rbacService.hasPermission(request,authentication)");
    }
}

注意:要把该包扫描到

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@SpringBootApplication(scanBasePackages =
        {
                "com.example.demo",
                "cn.mrcode.imooc.springsecurity.securitybrowser",
                "cn.mrcode.imooc.springsecurity.securityapp",
                "cn.mrcode.imooc.springsecurity.securitycore",
                "cn.mrcode.imooc.springsecurity.securityauthorize"  // 注意添加扫描包
        })
//@SpringBootApplication
@RestController
@EnableSwagger2
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @GetMapping("/hello")
    public String hello() {
        return "hello spring security";
    }
}

ok。完成

其他问题

现在有两个问题需要解决:

  1. 在通用的AuthorizeConfigProvider里面有一个 config.anyRequest()
return requestMatchers(ANY_REQUEST); 的源码,显示只能配置一个
  1. config.anyRequest()的配置也只能在所有的配置完成之后,进行调用

第一个问题

先注释掉,通用AuthorizeConfigProvider里面的;

那么所有需要登录的配置要怎么办呢?视频中说后面总结的时候讲解;这里先留坑

第二个问题

使用order注解来解决,让我们的业务配置在最后;

第一次长见识,order还能用来控制 依赖查找的顺序

@Component
public class DefaultAuthorizeConfigManager implements AuthorizeConfigManager {
    // 由于需要有序的,所以不能再使用set了
    // 依赖查找技巧
    @Autowired
    private List<AuthorizeConfigProvider> providers;

@Component
@Order(Integer.MIN_VALUE)
public class CommonAuthorizeConfigProvider implements AuthorizeConfigProvider {

@Component
@Order(Integer.MAX_VALUE)
public class DemoAuthorizeConfigProvider implements AuthorizeConfigProvider {