springboot+security整合(3)自定義鑒權


說明 springboot 版本 2.0.3
源碼地址:點擊跳轉

系列

  這篇講解如何自定義鑒權過程,實現根據數據庫查詢出的 url 和 method 是否匹配當前請求的 url 和 method 來決定有沒有權限。security 鑒權過程如下:

鑒權流程

一、 重寫 metadataSource 類

  1. 編寫 MyGranteAuthority 類,讓權限包含 url 和 method 兩個部分。
public class MyGrantedAuthority implements GrantedAuthority {
    private String method;
    private String url;

    public MyGrantedAuthority(String method, String url) {
        this.method = method;
        this.url = url;
    }

    @Override
    public String getAuthority() {
        return url;
    }

    public String getMethod() {
        return method;
    }

    public String getUrl() {
        return url;
    }

    @Override
    public boolean equals(Object obj) {
        if(this==obj) return true;
        if(obj==null||getClass()!= obj.getClass()) return false;
        MyGrantedAuthority grantedAuthority = (MyGrantedAuthority)obj;
        if(this.method.equals(grantedAuthority.getMethod())&&this.url.equals(grantedAuthority.getUrl()))
            return true;
        return false;
    }
}
  1. 編寫 MyConfigAttribute 類,實現 ConfigAttribute 接口,代碼如下:
public class MyConfigAttribute implements ConfigAttribute {
    private HttpServletRequest httpServletRequest;
    private MyGrantedAuthority myGrantedAuthority;

    public MyConfigAttribute(HttpServletRequest httpServletRequest) {
        this.httpServletRequest = httpServletRequest;
    }

    public MyConfigAttribute(HttpServletRequest httpServletRequest, MyGrantedAuthority myGrantedAuthority) {
        this.httpServletRequest = httpServletRequest;
        this.myGrantedAuthority = myGrantedAuthority;
    }

    public HttpServletRequest getHttpServletRequest() {
        return httpServletRequest;
    }

    @Override
    public String getAttribute() {
        return myGrantedAuthority.getUrl();
    }

    public MyGrantedAuthority getMyGrantedAuthority() {
        return myGrantedAuthority;
    }
}
  1. 編寫 MySecurityMetadataSource 類,獲取當前 url 所需要的權限
@Component
public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private JurisdictionMapper jurisdictionMapper;
    private List<Jurisdiction> jurisdictions;

    private void loadResource() {
        this.jurisdictions = jurisdictionMapper.selectAllPermission();
    }


    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        if (jurisdictions == null) this.loadResource();
        HttpServletRequest request = ((FilterInvocation) object).getRequest();
        Set<ConfigAttribute> allConfigAttribute = new HashSet<>();
        AntPathRequestMatcher matcher;
        for (Jurisdiction jurisdiction : jurisdictions) {
            //使用AntPathRequestMatcher比較可讓url支持ant風格,例如/user/*/a
            //*匹配一個或多個字符,**匹配任意字符或目錄
            matcher = new AntPathRequestMatcher(jurisdiction.getUrl(), jurisdiction.getMethod());
            if (matcher.matches(request)) {
                ConfigAttribute configAttribute = new MyConfigAttribute(request,new MyGrantedAuthority(jurisdiction.getMethod(),jurisdiction.getUrl()));
                allConfigAttribute.add(configAttribute);
                //這里是獲取到一個權限就返回,根據校驗規則也可獲取多個然后返回
                return allConfigAttribute;
            }
        }
        //未匹配到,說明無需權限驗證
        return null;
    }

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

    @Override
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }
}

二、 編寫 MyAccessDecisionManager 類

  實現 AccessDecisionManager 接口以實現權限判斷,直接 return 說明驗證通過,如不通過需要拋出對應錯誤,代碼如下:

@Component
public class MyAccessDecisionManager implements AccessDecisionManager{
    private Logger log = LoggerFactory.getLogger(this.getClass());

	@Override
	public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
			throws AccessDeniedException, InsufficientAuthenticationException {
	    //無需驗證放行
	    if(configAttributes==null || configAttributes.size()==0)
	        return;
	    if(!authentication.isAuthenticated()){
	        throw new InsufficientAuthenticationException("未登錄");
        }
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        for(ConfigAttribute attribute : configAttributes){
            MyConfigAttribute urlConfigAttribute = (MyConfigAttribute)attribute;
            for(GrantedAuthority authority: authorities){
                MyGrantedAuthority myGrantedAuthority = (MyGrantedAuthority)authority;
                if(urlConfigAttribute.getMyGrantedAuthority().equals(myGrantedAuthority))
                    return;
            }
        }
        throw new AccessDeniedException("無權限");
	}

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

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

三、 編寫 MyFilterSecurityInterceptor 類

  該類繼承 AbstractSecurityInterceptor 類,實現 Filter 接口,代碼如下:

@Component
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {

    //注入上面編寫的兩個類
    @Autowired
    private MySecurityMetadataSource mySecurityMetadataSource;

    @Autowired
    public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
        super.setAccessDecisionManager(myAccessDecisionManager);
    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {
    }


    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(request, response, chain);
        invoke(fi);
    }

    public void invoke(FilterInvocation fi) throws IOException, ServletException {
        //這里進行權限驗證
        InterceptorStatusToken token = super.beforeInvocation(fi);
        try {
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } finally {
            super.afterInvocation(token, null);
        }
    }

    @Override
    public void destroy() {
    }

    @Override
    public Class<?> getSecureObjectClass() {
        return FilterInvocation.class;
    }

    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return this.mySecurityMetadataSource;
    }
}

四、 加入到 security 的過濾器鏈中

.addFilterBefore(urlFilterSecurityInterceptor,FilterSecurityInterceptor.class)

完成

本篇原創發布於:https://www.tapme.top/blog/detail/2018-08-22-10-38


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM