手机版
你好,游客 登录 注册 搜索
背景:
阅读新闻

自定义Spring Security权限控制管理(实战篇)

[日期:2016-12-09] 来源:javafan.cn  作者:dong ying [字体: ]

上篇《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权限控制之旅吧。

更多Spring Security相关教程见以下内容

Spring Security 学习笔记  http://www.linuxidc.com/Linux/2016-10/135820.htm

Spring Security3.1高级详细开发指南 PDF http://www.linuxidc.com/Linux/2016-05/131482.htm

Spring Security 学习之数据库认证 http://www.linuxidc.com/Linux/2014-02/97407.htm

Spring Security 学习之LDAP认证 http://www.linuxidc.com/Linux/2014-02/97406.htm

Spring Security 学习之OpenID认证 http://www.linuxidc.com/Linux/2014-02/97405.htm

Spring Security 学习之X.509认证 http://www.linuxidc.com/Linux/2014-02/97404.htm

Spring Security 学习之HTTP基本认证和HTTP摘要认证 http://www.linuxidc.com/Linux/2014-02/97403.htm

Spring Security 学习之HTTP表单验证 http://www.linuxidc.com/Linux/2014-02/97402.htm

Spring Security异常之You must provide a configuration attribute  http://www.linuxidc.com/Linux/2015-02/113364.htm

Spring Security 的详细介绍请点这里
Spring Security 的下载地址请点这里 

本文永久更新链接地址http://www.linuxidc.com/Linux/2016-12/138096.htm

linux
本文评论   查看全部评论 (0)
表情: 表情 姓名: 字数

       

评论声明
  • 尊重网上道德,遵守中华人民共和国的各项有关法律法规
  • 承担一切因您的行为而直接或间接导致的民事或刑事法律责任
  • 本站管理人员有权保留或删除其管辖留言中的任意内容
  • 本站有权在网站内转载或引用您的评论
  • 参与本评论即表明您已经阅读并接受上述条款