zoukankan      html  css  js  c++  java
  • 自定义Spring Security权限控制管理(实战篇)

    上篇《话说Spring Security权限管理(源码)》介绍了Spring Security权限控制管理的源码及实现,然而某些情况下,它默认的实现并不能满足我们项目的实际需求,有时候需要做一些自己的实现,本次将围绕上次的内容进行一次项目实战。

    实战背景

    背景描述

    项目中需要做细粒的权限控制,细微至url + httpmethod (满足restful,例如: https://.../xxx/users/1, 某些角色只能查看(HTTP GET), 而无权进行增改删(POST, PUT, DELETE))。

    表设计

    为避嫌,只列出要用到的关键字段,其余敬请自行脑补。

    1. admin_user 管理员用户表, 关键字段( id, role_id )。
    2. t_role 角色表, 关键字段( id, privilege_id )。
    3. t_privilege 权限表, 关键字段( id, url, method )

    三个表的关联关系就不用多说了吧,看字段一眼就能看出。

    实现前分析

    我们可以逆向思考:

    要实现我们的需求,最关键的一步就是让Spring Security的AccessDecisionManager来判断所请求的url + httpmethod 是否符合我们数据库中的配置。然而,AccessDecisionManager并没有来判定类似需求的相关Voter, 因此,我们需要自定义一个Voter的实现(默认注册的AffirmativeBased的策略是只要有Voter投出ACCESS_GRANTED票,则判定为通过,这也正符合我们的需求)。实现voter后,有一个关键参数(Collection attributes),ConfigAttribute根据不同的情况,所代表的语义不一样。我们在此也需要实现。然而,Collection attributes参数由SecurityMetadataSource获取,因此,我们还应该实现SecurityMetadataSource。众所周知,在Spring Security中,当前用户认证信息都是通过Authentication表示,因此,我们还应该让Authentication包含用户(admin)实例。Authentication同时还包含了用户的权限信息(GrantedAuthority), 因此还应该实现GrantedAuthority。

    总结一下思路步骤:

    1.自定义voter实现。

    2.自定义ConfigAttribute实现。

    3.自定义SecurityMetadataSource实现。

    4.Authentication包含用户实例(这个其实不用说,大家应该都已经这么做了)。

    5.自定义GrantedAuthority实现。

    项目实战

    1.自定义GrantedAuthority实现

    UrlGrantedAuthority.java

    public class UrlGrantedAuthority implements GrantedAuthority {
    
        private final String httpMethod;
    
        private final String url;
    
        public UrlGrantedAuthority(String httpMethod, String url) {
            this.httpMethod = httpMethod;
            this.url = url;
        }
    
        @Override
        public String getAuthority() {
            return url;
        }
    
        public String getHttpMethod() {
            return httpMethod;
        }
    
        public String getUrl() {
            return url;
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
    
            UrlGrantedAuthority target = (UrlGrantedAuthority) o;
            if (httpMethod.equals(target.getHttpMethod()) && url.equals(target.getUrl())) return true;
            return false;
        }
    
        @Override
        public int hashCode() {
            int result = httpMethod != null ? httpMethod.hashCode() : 0;
            result = 31 * result + (url != null ? url.hashCode() : 0);
            return result;
        }
    }
    

    2.自定义认证用户实例

    public class SystemUser implements UserDetails {
    
        private final Admin admin;
    
        private List<MenuOutput> menuOutputList;
    
        private final List<GrantedAuthority> grantedAuthorities;
    
        public SystemUser(Admin admin, List<AdminPrivilege> grantedPrivileges, List<MenuOutput> menuOutputList) {
            this.admin = admin;
            this.grantedAuthorities = grantedPrivileges.stream().map(it -> {
                String method = it.getMethod() != null ? it.getMethod().getLabel() : null;
                return new UrlGrantedAuthority(method, it.getUrl());
            }).collect(Collectors.toList());
            this.menuOutputList = menuOutputList;
        }
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return this.grantedAuthorities;
        }
    
        @Override
        public String getPassword() {
            return admin.getPassword();
        }
    
        @Override
        public String getUsername() {
            return null;
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return true;
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        @Override
        public boolean isEnabled() {
            return true;
        }
    
        public Long getId() {
            return admin.getId();
        }
    
        public Admin getAdmin() {
            return admin;
        }
    
        public List<MenuOutput> getMenuOutputList() {
            return menuOutputList;
        }
    
        public String getSalt() {
            return admin.getSalt();
        }
    }	
    

    3.自定义UrlConfigAttribute实现

    public class UrlConfigAttribute implements ConfigAttribute {
    
        private final HttpServletRequest httpServletRequest;
    
        public UrlConfigAttribute(HttpServletRequest httpServletRequest) {
            this.httpServletRequest = httpServletRequest;
        }
    
    
        @Override
        public String getAttribute() {
            return null;
        }
    
        public HttpServletRequest getHttpServletRequest() {
            return httpServletRequest;
        }
    }
    

    4.自定义SecurityMetadataSource实现

    public class UrlFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    
        @Override
        public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
            final HttpServletRequest request = ((FilterInvocation) object).getRequest();
            Set<ConfigAttribute> allAttributes = new HashSet<>();
            ConfigAttribute configAttribute = new UrlConfigAttribute(request);
            allAttributes.add(configAttribute);
            return allAttributes;
        }
    
        @Override
        public Collection<ConfigAttribute> getAllConfigAttributes() {
            return null;
        }
    
        @Override
        public boolean supports(Class<?> clazz) {
            return FilterInvocation.class.isAssignableFrom(clazz);
        }
    
    }
    

    5.自定义voter实现

    public class UrlMatchVoter implements AccessDecisionVoter<Object> {
    
        @Override
        public boolean supports(ConfigAttribute attribute) {
            if (attribute instanceof UrlConfigAttribute) return true;
            return false;
        }
    
        @Override
        public boolean supports(Class<?> clazz) {
            return true;
        }
    
        @Override
        public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
            if(authentication == null) {
                return ACCESS_DENIED;
            }
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
    
            for (ConfigAttribute attribute : attributes) {
                if (!(attribute instanceof UrlConfigAttribute)) continue;
                UrlConfigAttribute urlConfigAttribute = (UrlConfigAttribute) attribute;
                for (GrantedAuthority authority : authorities) {
                    if (!(authority instanceof UrlGrantedAuthority)) continue;
                    UrlGrantedAuthority urlGrantedAuthority = (UrlGrantedAuthority) authority;
                    if (StringUtils.isBlank(urlGrantedAuthority.getAuthority())) continue;
                    //如果数据库的method字段为null,则默认为所有方法都支持
                    String httpMethod = StringUtils.isNotBlank(urlGrantedAuthority.getHttpMethod()) ? urlGrantedAuthority.getHttpMethod()
                            : urlConfigAttribute.getHttpServletRequest().getMethod();
                    //用Spring已经实现的AntPathRequestMatcher进行匹配,这样我们数据库中的url也就支持ant风格的配置了(例如:/xxx/user/**)        
                    AntPathRequestMatcher antPathRequestMatcher = new AntPathRequestMatcher(urlGrantedAuthority.getAuthority(), httpMethod);
                    if (antPathRequestMatcher.matches(urlConfigAttribute.getHttpServletRequest()))
                        return ACCESS_GRANTED;
                }
            }
            return ACCESS_ABSTAIN;
        }
    }
    

    6.自定义FilterSecurityInterceptor实现

    public class UrlFilterSecurityInterceptor extends FilterSecurityInterceptor {
    
        public UrlFilterSecurityInterceptor() {
            super();
        }
    
        @Override
        public void init(FilterConfig arg0) throws ServletException {
            super.init(arg0);
        }
    
        @Override
        public void destroy() {
            super.destroy();
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            super.doFilter(request, response, chain);
        }
    
        @Override
        public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
            return super.getSecurityMetadataSource();
        }
    
        @Override
        public SecurityMetadataSource obtainSecurityMetadataSource() {
            return super.obtainSecurityMetadataSource();
        }
    
        @Override
        public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) {
            super.setSecurityMetadataSource(newSource);
        }
    
        @Override
        public Class<?> getSecureObjectClass() {
            return super.getSecureObjectClass();
        }
    
        @Override
        public void invoke(FilterInvocation fi) throws IOException, ServletException {
            super.invoke(fi);
        }
    
        @Override
        public boolean isObserveOncePerRequest() {
            return super.isObserveOncePerRequest();
        }
    
        @Override
        public void setObserveOncePerRequest(boolean observeOncePerRequest) {
            super.setObserveOncePerRequest(observeOncePerRequest);
        }
    }
    

    配置文件关键配置

    <security:http>
    	...
    	<security:custom-filter ref="filterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR" />
    </security:http>
    
    <security:authentication-manager alias="authenticationManager">
        <security:authentication-provider ref="daoAuthenticationProvider"/>
    </security:authentication-manager>
    
    <bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
        <constructor-arg>
            <list>
                <bean id="authenticatedVoter" class="org.springframework.security.access.vote.AuthenticatedVoter" />
                <bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter" />
                <bean id="urlMatchVoter" class="com.mobisist.app.security.access.voter.UrlMatchVoter" />
            </list>
        </constructor-arg>
    </bean>
    
    <bean id="securityMetadataSource" class="com.mobisist.app.security.access.UrlFilterInvocationSecurityMetadataSource" />
    
    <bean id="filterSecurityInterceptor"
          class="com.mobisist.app.security.access.UrlFilterSecurityInterceptor">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="accessDecisionManager" ref="accessDecisionManager"/>
        <property name="securityMetadataSource" ref="securityMetadataSource" />
    </bean>	
    

    好啦,接下来享受你的Spring Security权限控制之旅吧。


    欢迎访问我的个人博客:

    www.javafan.cn

  • 相关阅读:
    dubbo学习(一)认识
    MySQL学习(九)小结
    MySQL学习(八)删除表数据
    MySQL学习(六)change-buffer
    RPC 学习(一)认识
    MySQL学习(五)事务隔离
    MySQL学习(四)死锁及死锁检测
    计算机操作系统 --- 进程和进程的上下文切换
    MySQL 学习(三)事务学习
    消息队列(七)--- RocketMQ延时发送和消息重试(半原创)
  • 原文地址:https://www.cnblogs.com/dongying/p/6128268.html
Copyright © 2011-2022 走看看