251.Spring Boot+Spring Security:基于URL动态权限:自定义Filter_Spring Boot+Spring S

 

最近一朋友做了一个小程序,看着还挺好玩的,于是乎就帮朋友捎带推广了下,走过路过的小伙伴们,你心中是否也是有一个愿望,赶紧来这里和大家一起分享和实现吧~ 来,来,来,我在这里等你~ 

【点击????????????直接进入~】

 

 

说明

(1)JDK版本:1.8

(2)Spring Boot 2.0.6

(3)Spring Security 5.0.9

(4)Spring Data JPA 2.0.11.RELEASE

(5)hibernate5.2.17.Final

(6)MySQLDriver 5.1.47

(7)MySQL 8.0.12

 

需求缘起

       在上一篇我们通过自定义AccessDesionManager实现了动态权限控制,本节将通过自定义Filter进行实现动态权限,理解了上一篇文章的话,对于这篇文章就没有什么难点了。代码基于《基于URL动态权限:准备工作》在往下编码。

编码思路

       对于URL动态权限配置,主要解决如下几个问题:

(1)基于URL的用户权限信息保存在哪里:需要定义一张权限表,保存权限信息,然后角色和权限有一个关联关系(准备工作已经完成)。

(2)怎么加载用户的权限信息:仍然是通过loadUserByUsername进行加载,用户的权限信息这块的编码不变(在之前已经编码)。

(3)URL对应的权限配置:这个主要是通过FilterInvocationSecurityMetadataSource进行配置。

(4)如何决定某一个用户是否有权限访问某个URL : 自定义AccessDecisionManager类的decide方法,决定某一个用户是否有权限访问某个URL。

(5)使用自定义的Filter进行拦截处理请求。

 

一、基于URL动态权限配置

       接下来我们看下具体的编码步骤,我们的代码在基本工作之后进行编码,所以权限的实体类,初始化数据就不重复说明了。

1.1 加载权限信息

       继承FilterInvocationSecurityMetadataSource重写getAttributes的方法进行通过uri获取权限配置信息:

package com.kfit.config;

import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;

import com.kfit.permission.service.PermissionService;

@Component
public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource{

    @Autowired
    private PermissionService permissionService;





    /**
     * 此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法。
     * object-->FilterInvocation
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        System.out.println("MyFilterInvocationSecurityMetadataSource.getAttributes()");

        Map<String, Collection<ConfigAttribute>> map = permissionService.getPermissionMap();
        FilterInvocation filterInvocation = (FilterInvocation) object;
        System.out.println(filterInvocation.getFullRequestUrl());

        if (isMatcherAllowedRequest(filterInvocation)) return null ; //return null 表示允许访问,不做拦截

        HttpServletRequest request = filterInvocation.getHttpRequest();
        String resUrl;
        //URL规则匹配.
        AntPathRequestMatcher matcher;
        for(Iterator<String> it  = map.keySet().iterator();it.hasNext();) {
            resUrl = it.next();
            matcher = new AntPathRequestMatcher(resUrl);
            if(matcher.matches(request)) {
                System.out.println(map.get(resUrl));
                return map.get(resUrl);
            }
        }
        //SecurityConfig.createList("ROLE_USER");
         //方式一:没有匹配到,直接是白名单了.不登录也是可以访问的。
        //return null;

        //方式二:配有匹配到,需要指定相应的角色:
        return SecurityConfig.createList("ROLE_admin");
    }




    /**
     * 判断当前请求是否在允许请求的范围内
     * @param fi 当前请求
     * @return 是否在范围中
     */
    private boolean isMatcherAllowedRequest(FilterInvocation fi){
        return allowedRequest().stream().map(AntPathRequestMatcher::new)
                .filter(requestMatcher -> requestMatcher.matches(fi.getHttpRequest()))
                .toArray().length > 0;
    }

    /**
     * 参考:
     * https://blog.csdn.net/pujiaolin/article/details/73928491
     * @return 定义允许请求的列表
     */
    private List<String> allowedRequest(){
        return Arrays.asList("/login","/css/**","/fonts/**","/js/**","/scss/**","/img/**");
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }

}

 

1.2 权限校验

       继承AccessDecisionManager重写decide方法进行编码:

package com.kfit.config;

import java.util.Collection;
import java.util.Iterator;

import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

@Component
public class MyAccessDecisionManager implements AccessDecisionManager{

    /**
     * 方法是判定是否拥有权限的决策方法,
     * (1)authentication 是释CustomUserService中循环添加到 GrantedAuthority 对象中的权限信息集合.
     * (2)object 包含客户端发起的请求的requset信息,可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
     * (3)configAttributes 为MyFilterInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果,此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法
     */
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
            throws AccessDeniedException, InsufficientAuthenticationException {
        System.out.println("MyAccessDecisionManager.decide()");
        if(configAttributes == null  || configAttributes.size()==0) {
            throw new AccessDeniedException("permission denied");
        }

        ConfigAttribute cfa;
        String needRole;
        //遍历基于URL获取的权限信息和用户自身的角色信息进行对比.
        for(Iterator<ConfigAttribute> it=configAttributes.iterator();it.hasNext();) {
            cfa = it.next();
            needRole = cfa.getAttribute();
            System.out.println("decide,needRole:"+needRole+",authentication="+authentication);
            //authentication 为CustomUserDetailService中添加的权限信息.
            for(GrantedAuthority grantedAuthority:authentication.getAuthorities()) {
                if(needRole.equals(grantedAuthority.getAuthority())) {
                    return;
                }
            }
        }
        throw new AccessDeniedException("permission denied");
    }



    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }

}

 

1.3 自定义Filter

       自定义一个Filter继承AbstractSecurityInterceptor,在这个方法里主要注入我们上面编写的两个类:
(1)安全信息数据源:MyFilterInvocationSecurityMetadataSource

(2)访问决策管理器:MyAccessDecisionManager

具体代码如下:

package com.kfit.config;

import java.util.Collection;
import java.util.Iterator;

import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

@Component
public class MyAccessDecisionManager implements AccessDecisionManager{

    /**
     * 方法是判定是否拥有权限的决策方法,
     * (1)authentication 是释CustomUserService中循环添加到 GrantedAuthority 对象中的权限信息集合.
     * (2)object 包含客户端发起的请求的requset信息,可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
     * (3)configAttributes 为MyFilterInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果,此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法
     */
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
            throws AccessDeniedException, InsufficientAuthenticationException {
        System.out.println("MyAccessDecisionManager.decide()");
        if(configAttributes == null  || configAttributes.size()==0) {
            throw new AccessDeniedException("permission denied");
        }

        ConfigAttribute cfa;
        String needRole;
        //遍历基于URL获取的权限信息和用户自身的角色信息进行对比.
        for(Iterator<ConfigAttribute> it=configAttributes.iterator();it.hasNext();) {
            cfa = it.next();
            needRole = cfa.getAttribute();
            System.out.println("decide,needRole:"+needRole+",authentication="+authentication);
            //authentication 为CustomUserDetailService中添加的权限信息.
            for(GrantedAuthority grantedAuthority:authentication.getAuthorities()) {
                if(needRole.equals(grantedAuthority.getAuthority())) {
                    return;
                }
            }
        }
        throw new AccessDeniedException("permission denied");
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }

}

      到这里就可以测试下效果了,和之前的结果应该是一样的。