第 37 章 用户信息

37.1. UserDetails

Spring Security中的UserDetails被作为一个通用的权限主体,凡是涉及到username和password的情况,都会使用到UserDetails和它对应的服务。

常用的服务有从内存中读取用户信息的InMemoryDaoImpl和用数据库中读取用户信息的JdbcDaoImp。它们都实现了UserDetailsService,因此都可以使用loadUserByUsername()方法获得对应用户的信息。

如果使用了LDAP,还会接触到LdapUserDetailsService,它用来从LDAP中获取用户信息。

在org.springframework.security.core.userdetails包下还包含一个check目录,它主要用来校验用户是否过期,是否被锁定,是否被禁用。

还可以看到一个hierarchicalroles,它的作用是处理角色继承关系,如果希望使用角色继承策略,需要将原始的UserDetailsService通过UserDetailsServiceWrapper进行一下封装,从而获得由UserDetailsWrapper封装的UserDetails,以此来实现角色继承机制。

37.2. 使用角色继承

在Spring Security中,我们可以指定角色间的继承关系,这样可以重用角色权限,减少配置的代码量,让权限配置整体上显得更清晰。

为了使用角色继承功能,我们需要对原有的配置文件进行一些修改。

<authentication-provider user-service-ref="userDetailsServiceWrapper"/>

<beans:bean id="userDetailsServiceWrapper"
    class="org.springframework.security.access.hierarchicalroles.UserDetailsServiceWrapper">
    <beans:property name="userDetailsService" ref="userDetailsService"/>
    <beans:property name="roleHierarchy">
        <beans:bean class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
            <beans:property name="hierarchy" value="ROLE_ADMIN > ROLE_USER"/>
        </beans:bean>
    </beans:property>
</beans:bean>

<user-service id="userDetailsService">
    <user name="admin" password="admin" authorities="ROLE_ADMIN" />
    <user name="user" password="user" authorities="ROLE_USER" />
</user-service>
        

我们将原有的user-service单独抽离出来,在userDetailsService的基础上生成一个userDetailsServiceWrapper,这个wrapper的作用就是在原有的user-service的基础上启用角色继承功能。

我们使用RoleHierarchyImpl为userDetailsServiceWrapper配置了角色继承的策略,ROLE_ADMIN > ROLE_USER表示ROLE_ADMIN将继承ROLE_USER所有用的所有角色,只要是允许ROLE_USER访问的资源,ROLE_ADMIN也都有权限进行访问。这样我们在user-service中的配置就可以从ROLE_ADMIN,ROLE_USER简化为ROLE_ADMIN了,而intercept-url中的配置也可以从ROLE_ADMIN,ROLE_USER改为ROLE_USER了。

如果希望配置更多继承关系,可以使用换行进行分隔,比如:

<property name="hierarchy">
    <value>
        ROLE_A > ROLE_B
        ROLE_B > ROLE_AUTHENTICATED
        ROLE_AUTHENTICATED > ROLE_UNAUTHENTICATED
    </value>
</property>
        

实例在ch205。

37.3. 为ACL添加角色继承

注意

需要验证角色继承,需要提供一个例子来验证这个功能是否可以实现

目前,直至Spring Security-3.0.0.M1都不支持在acl中使用RoleHierarchy,不过在官网的jira上有人提交了一个patch,如果情况顺利的话,这个patch应该在Spring Security-3.0.0.RC1中被应用到svn中,我们就可以为acl实现角色继承了。

http://jira.springframework.org/browse/SEC-1049

如果希望在Spring Security-2.x中在acl部分实现角色继承,需要进行如下配置。

首先根据jira上的patch自己创建一个SidRoleHierarchyRetrievalStrategyImpl.java。

/* Copyright 2008 Thomas Champagne
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.security.acls.sid;

import java.util.List;

import org.springframework.security.Authentication;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.userdetails.hierarchicalroles.RoleHierarchy;
import org.springframework.util.Assert;

/**
 * Extended SidRetrievalStrategyImpl which uses a {@link RoleHierarchy} definition to determine the
 * roles allocated to the current user.
 * @author Thomas Champagne
 */
public class SidRoleHierarchyRetrievalStrategyImpl extends SidRetrievalStrategyImpl {
    private RoleHierarchy roleHierarchy = null;

    public SidRoleHierarchyRetrievalStrategyImpl(RoleHierarchy roleHierarchy) {
        Assert.notNull(roleHierarchy, "RoleHierarchy must not be null");
        this.roleHierarchy = roleHierarchy;
    }

    /**
     * Calls the <tt>RoleHierarchy</tt> to obtain the complete set of user authorities.
     */
    GrantedAuthority[] extractAuthorities(Authentication authentication) {
        return roleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities());
    }

    public Sid[] getSids(Authentication authentication) {
        GrantedAuthority[] authorities = this.extractAuthorities(authentication);
        Sid[] sids = new Sid[authorities.length + 1];

        sids[0] = new PrincipalSid(authentication);

        for (int i = 1; i <= authorities.length; i++) {
            sids[i] = new GrantedAuthoritySid(authorities[i - 1]);
        }

        return sids;
    }
}
        

然后在acl的配置文件中配置bean,并在AclEntryVoter,AclEntryAfterInvocationProvider和AclEntryAfterInvocationCollectionFilteringProvider中替换默认的SidRetrievalStrategy。

<bean id="sidRetrievalStrategy"
    class="org.springframework.security.acls.sid.SidRoleHierarchyRetrievalStrategyImpl">
    <constructor-arg ref="roleHierarchy"/>
</bean>

<bean id="afterAclRead" class="org.springframework.security.afterinvocation.AclEntryAfterInvocationProvider">
    <sec:custom-after-invocation-provider/>
    <constructor-arg ref="aclService"/>
    <constructor-arg>
        <list>
            <util:constant static-field="org.springframework.security.acls.domain.BasePermission.ADMINISTRATION"/>
            <util:constant static-field="org.springframework.security.acls.domain.BasePermission.READ"/>
        </list>
    </constructor-arg>
    <property name="sidRetrievalStrategy" ref="sidRetrievalStrategy"/>
</bean>

<bean id="afterAclCollectionRead" class="org.springframework.security.afterinvocation.AclEntryAfterInvocationCollectionFilteringProvider">
    <sec:custom-after-invocation-provider/>
    <constructor-arg ref="aclService"/>
    <constructor-arg>
        <list>
            <util:constant static-field="org.springframework.security.acls.domain.BasePermission.ADMINISTRATION"/>
            <util:constant static-field="org.springframework.security.acls.domain.BasePermission.READ"/>
        </list>
    </constructor-arg>
    <property name="sidRetrievalStrategy" ref="sidRetrievalStrategy"/>
</bean>
        

这样就在acl中添加了对角色继承的支持。

37.4. PasswordEncoder和SaltValue

默认提供的PasswordEncoder包含plaintext, sha, sha-256, md5, md4, {sha}, {ssha}。其中{sha}和{ssha}是专门为ldap准备的,plaintext意味着不对密码进行加密,如果我们不设置PasswordEncoder,默认就会使用它。

SaltValue是为了让密码加密更加安全,它有两种策略可以选择。user-property, system-wide分别对应着ReflectionSaltSource和SystemWideSaltSource,它们的区别是ReflectionSaltSource会使用反射,从用户的Principal对象汇总取出一个对应的属性来作为盐值,而SystemWideSaltSource会为所有用户都设置相同的盐值。

使用了PasswordEncoder和SaltValue的结果就是数据库中的密码变得难以辨认了,这就要注意在添加用户时要使用相同的策略对密码进行加密,这才能保证新用户可以正常登陆。