这里需要注意的是Authentication与UserDetails对象的区分,Authentication对象才是Spring Security使用的进行安全访问控制用户信息的安全对象,实际上Authentication对象有未认证和已认证两种状态,在作为参数传入认证管理器的时候,它是一个为认证的对象,它从客户端获取用户的身份认证信息,如用户名、密码,可以是从一个登录页面,也可以是从cookie中获取,并由系统自动生成一个Authentication对象,而这里的UserDetails代表的是一个用户安全信息的源,这个源可以是从数据库、LDAP服务器、CA中心返回,Spring Security要做的就是将未认证的Authentication对象与UserDetails对象进行匹配,成功后将UserDetails对象中的权限信息拷贝到Authentication中,组成一个完整的Authentication对象,与其他组件进行共享。
package org.springframework.security.core; import java.io.Serializable; import java.security.Principal; import java.util.Collection; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.context.SecurityContextHolder; public interface Authentication extends Principal, Serializable { /**权限集合*/ Collection<? extends GrantedAuthority> getAuthorities(); /**获取凭证*/ Object getCredentials(); /**获取认证一些额外信息*/ Object getDetails(); /**过去认证的实体*/ Object getPrincipal(); /**是否认证通过*/ boolean isAuthenticated(); /** * See {@link #isAuthenticated()} for a full description. * <p> * Implementations should <b>always</b> allow this method to be called with a * <code>false</code> parameter, as this is used by various classes to specify the * authentication token should not be trusted. If an implementation wishes to reject * an invocation with a <code>true</code> parameter (which would indicate the * authentication token is trusted - a potential security risk) the implementation * should throw an {@link IllegalArgumentException}. * * @param isAuthenticated <code>true</code> if the token should be trusted (which may * result in an exception) or <code>false</code> if the token should not be trusted * * @throws IllegalArgumentException if an attempt to make the authentication token * trusted (by passing <code>true</code> as the argument) is rejected due to the * implementation being immutable or implementing its own alternative approach to * {@link #isAuthenticated()} */ void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException; } 了解了Spring Security的上面三个对象,当我们需要数据库管理用户时,我们需要手动实现UserDetailsService对象中的loadUserByUsername方法,这就需要我们同时准备以下几张数据表,分别是用户表(user)、角色表(role)、权限表(permission)、用户和角色关系表(user_role)、权限和角色关系表(permission_role),UserDetails中的用户状态通过用户表里的属性去填充,UserDetails中的权限集合则是通过角色表、权限表、用户和角色关系表、权限和角色关系表构成的RBAC模型来提供,这样就可以把用户认证、用户权限集合放在数据库中进行管理了。
1.4 权限缓存 Spring Security的权限缓存和数据库管理有关,都是在用户认证上做文章,所以都与UserDetails有关,与数据库管理不同的是,Spring Security提供了一个可以缓存UserDetailsService的实现类,这个类的名字是CachingUserDetailsService
package org.springframework.security.authentication; import org.springframework.security.core.userdetails.UserCache; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.cache.NullUserCache; import org.springframework.util.Assert; /** * * @author Luke Taylor * @since 2.0 */ public class CachingUserDetailsService implements UserDetailsService { private UserCache userCache = new NullUserCache(); private final UserDetailsService delegate; public CachingUserDetailsService(UserDetailsService delegate) { this.delegate = delegate; } public UserCache getUserCache() { return userCache; } public void setUserCache(UserCache userCache) { this.userCache = userCache; } public UserDetails loadUserByUsername(String username) { UserDetails user = userCache.getUserFromCache(username); //缓存中不存在UserDetails时,通过UserDetailsService加载 if (user == null) { user = delegate.loadUserByUsername(username); } Assert.notNull(user, () -> "UserDetailsService " + delegate + " returned null for username " + username + ". " + "This is an interface contract violation"); //将UserDetials存入缓存,并将UserDetails返回 userCache.putUserInCache(user); return user; } }