本文为博主学习笔记
首先写以下配置类,常规操作,不多说了。

@Autowired
    UserService userService;
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService);
    }

和之前不同的是,这次的动态配置权限新增了两个配置类,一个MyFilter一个MyAccessDecisionManager类。
MyFilter类的作用是根据访问的路径与数据库中的路径进行匹配,如果匹配上,则返回访问该路径需要的所有角色。在这里,MyFilter这个类继承了FilterInvocationSecurityMetadataSource类,分析一下这个类,作用是SecurityMetadataSource实现的标记接口,这些实现旨在执行以FilterInvocations为键的查找。而这个类的父类是SecurityMetadataSource 类中有三个方法,分别是

//访问适用于给定安全对象的配置信息。
	Collection<ConfigAttribute> getAttributes(Object var1) throws IllegalArgumentException;
//如果可用,则返回实现类定义的所有配置信息。
    Collection<ConfigAttribute> getAllConfigAttributes();
//指示SecurityMetadataSource实现是否能够为指示的安全对象类型提供ConfigAttributes。
    boolean supports(Class<?> var1);

MyFIlter过滤器:

@Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        //这里需要强转称FilterInvocation的原因是因为要获取请求的url。FilterInvocation中有对应的方法
        String requestUrl = ((FilterInvocation) o).getRequestUrl();
        List<Menu> allMenu = menuService.getAllMenus();
        for (Menu menu : allMenu) {
            if (pathMatcher.match(menu.getPattern(),requestUrl)) {
                List<Role> roles = menu.getRoles();
                String[] rolesStr = new String[roles.size()];
                for (int i = 0; i < roles.size(); i++) {
                    rolesStr[i] = roles.get(i).getName();
                }
                //传递的是需要的角色数组
                return SecurityConfig.createList(rolesStr);
            }
        }
        return SecurityConfig.createList("ROLE_login");
    }

返回值这里需要注意,我们可以在IDEA编辑器里按住command点进去看一下。

spring security动态权限验证 spring security 动态权限菜单_身份验证


该类继承了父类ConfigAttribute,而getAttributes的返回值类型正好相符。

在SecurityConfig中的createList源码为

public static List<ConfigAttribute> createList(String... attributeNames) {
        Assert.notNull(attributeNames, "You must supply an array of attribute names");
        List<ConfigAttribute> attributes = new ArrayList(attributeNames.length);
        String[] var2 = attributeNames;
        int var3 = attributeNames.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            String attribute = var2[var4];
            attributes.add(new SecurityConfig(attribute.trim()));
        }

        return attributes;
    }

我们只需要把用户拥有的角色返回就行了。
接下来配置一个认证管理器。
MyAccessDecisionManager类实现了AccessDecisionManager接口,该接口中共有三个方法。

//为传递的参数解决访问控制决策。
 	void decide(Authentication var1, Object var2, Collection<ConfigAttribute> var3) throws AccessDeniedException, InsufficientAuthenticationException;

    boolean supports(ConfigAttribute var1);

    boolean supports(Class<?> var1);

在MyAccessDecisionManager类。方法的第一个参数作用是在AuthenticationManager.authenticate(Authentication)方法处理请求后,代表认证请求或已认证主体的令牌。
一旦对请求进行了身份验证,身份验证通常将通过使用的身份验证机制存储在由SecurityContextHolder管理的线程本地SecurityContext中。 通过创建Authentication实例并使用以下代码,无需使用Spring Security的身份验证机制之一即可实现显式身份验证
也就是说存储着用户当前的登录信息。方法的第三个参数的作用是与被调用的受保护对象关联的配置属性,也就是说,该参数中存储着用户需要哪些角色

@Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
    //ConfigAttribute的作用是存储与安全系统相关的配置属性。
        for (ConfigAttribute attribute : collection) {
        //判断用户角色是否与ROLE_login匹配
            if ("ROLE_login".equals(attribute.getAttribute())) {
                 if (authentication instanceof AnonymousAuthenticationToken) {
                     throw new AccessDeniedException("非法请求!");
                 } else {
                     return;
                 }
            }
            //我现在具备的角色
            //getAuthorities方法返回的是授权给委托人的权限;如果令牌尚未通过验证,则为空集合。 永不为空。
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for (GrantedAuthority authority : authorities) {
                if (authority.getAuthority().equals(attribute.getAttribute())){
                    return;
                }
            }
        }
        throw new AccessDeniedException("非法请求!");
    }

在配置类中重写config方法

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {

                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
//设置认证决策器
                        o.setAccessDecisionManager(myAccessDecisionManager);
                        o.setSecurityMetadataSource(myFilter);
                        return o;
                    }
                })
                .and()
                .formLogin()
                .permitAll()
                .and()
                .csrf()
                .disable();
    }