本文转载自:认证流程和授权流程
源码分析的第二篇以Subject的初始化为题。
一、回顾Apache Shiro创建Subject的步骤如下:
//1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager
Factory<org.apache.shiro.mgt.SecurityManager> factory =
new IniSecurityManagerFactory("classpath:shiro.ini");
//2、得到SecurityManager实例 并绑定给SecurityUtils
org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)
Subject subject = SecurityUtils.getSubject();
Apache Shiro 创建Subject 规则如下:
首先通过new IniSecurityManagerFactory并指定一个ini配置文件来创建一个SecurityManager工厂;
接着获取SecurityManager并绑定到SecurityUtils,这是一个全局设置,设置一次即可;
通过SecurityUtils得到Subject,其会自动绑定到当前线程;
接口Subject的文档介绍如下:
外界通过Subject接口来和SecurityManager进行交互,该接口含有登录、退出、权限判断、获取session,其中的Session可不是平常我们所使用的HttpSession等,而是shiro自定义的,是一个数据上下文,与一个Subject相关联的。
Apache Shiro 源代码创建步骤如下:
1、org.apache.shiro.Subject类的静态方法getSubject();
public static Subject getSubject()
{
Subject subject = ThreadContext.getSubject();
if(subject == null)
{
subject = (new org.apache.shiro.subject.Subject.Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
}
一看就是使用的是ThreadLocal设计模式,获取当前线程相关联的Subject 对象,如果没有则创建一个,然后绑定到当前线程。
2、ThreadContext是org.apache.shiro.util包下的一个工具类,它是用来操作和当前线程绑定的SecurityManager和Subject,它必然包含了一个ThreadLocal对象如下:
private static final Logger log = LoggerFactory.getLogger(org/apache/shiro/util/ThreadContext);
public static final String SECURITY_MANAGER_KEY = (new StringBuilder()).append(org/apache/shiro/util/ThreadContext.getName()).append("_SECURITY_MANAGER_KEY").toString();
public static final String SUBJECT_KEY = (new StringBuilder()).append(org/apache/shiro/util/ThreadContext.getName()).append("_SUBJECT_KEY").toString();
private static final ThreadLocal resources = new InheritableThreadLocalMap();
/*省略*/
ThreadLocal中所存放的数据是一个Map集合,集合中所存的key有两个SECURITY_MANAGER_KEY 和SUBJECT_KEY ,就是通过这两个key来存取SecurityManager和Subject两个对象的。
3、当前线程还没有绑定一个Subject时,就需要通过Subject.Builder来创建一个然后绑定到当前线程。Builder是Subject的一个内部类,它拥有两个重要的属性,SubjectContext和SecurityManager,创建Builder时使用SecurityUtils工具来获取它的全局静态变量SecurityManager,SubjectContext则是使用newSubjectContextInstance创建一个DefaultSubjectContext对象:
public Builder()
{
this(SecurityUtils.getSecurityManager());
}
public Builder(SecurityManager securityManager)
{
if(securityManager == null)
throw new NullPointerException("SecurityManager method argument cannot be null.");
this.securityManager = securityManager;
subjectContext = newSubjectContextInstance();
if(subjectContext == null)
{
throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' cannot be null.");
} else
{
subjectContext.setSecurityManager(securityManager);
return;
}
}
}
4、
Builder准备工作完成后,调用buildSubject来创建一个Subject:
public Subject buildSubject()
{
return securityManager.createSubject(subjectContext);
}
最终还是通过securityManager根据subjectContext来创建一个Subject。最终是通过一个SubjectFactory来创建的,SubjectFactory是一个接口,接口方法为Subject createSubject(SubjectContext context),默认的SubjectFactory实现是DefaultSubjectFactory,DefaultSubjectFactory创建的Subject是DelegatingSubject。【至此,我们通过源码分析已知Subject创建流程】
5、调用Subject.isPermitted/hasRole方法,其本质是委托给SecurityManager的hasRole方法。
public boolean hasRole(String roleIdentifier)
{
return hasPrincipals() && securityManager.hasRole(getPrincipals(), roleIdentifier);
}
6、
AuthorizingSecurityManager实现了Authorizer接口,但是AuthorizingSecurityManager是通过内部Authorizer引用来完成具体的功能,默认采用的是ModularRealmAuthorizer。如下:
public abstract class AuthorizingSecurityManager extends AuthenticatingSecurityManager
{
public AuthorizingSecurityManager()
{
authorizer = new ModularRealmAuthorizer();
}
public boolean hasRole(PrincipalCollection principals, String roleIdentifier)
{
return authorizer.hasRole(principals, roleIdentifier);
}
/*省略*/
}
来看看这个Authorizer模块的接口设计:
/*jadclipse*/// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) radix(10) lradix(10)
// Source File Name: Authorizer.java
package org.apache.shiro.authz;
import java.util.Collection;
import java.util.List;
import org.apache.shiro.subject.PrincipalCollection;
// Referenced classes of package org.apache.shiro.authz:
// AuthorizationException, Permission
public interface Authorizer
{
public abstract boolean isPermitted(PrincipalCollection principalcollection, String s);
public abstract boolean isPermitted(PrincipalCollection principalcollection, Permission permission);
public transient abstract boolean[] isPermitted(PrincipalCollection principalcollection, String as[]);
public abstract boolean[] isPermitted(PrincipalCollection principalcollection, List list);
public transient abstract boolean isPermittedAll(PrincipalCollection principalcollection, String as[]);
public abstract boolean isPermittedAll(PrincipalCollection principalcollection, Collection collection);
public abstract void checkPermission(PrincipalCollection principalcollection, String s)
throws AuthorizationException;
public abstract void checkPermission(PrincipalCollection principalcollection, Permission permission)
throws AuthorizationException;
public transient abstract void checkPermissions(PrincipalCollection principalcollection, String as[])
throws AuthorizationException;
public abstract void checkPermissions(PrincipalCollection principalcollection, Collection collection)
throws AuthorizationException;
public abstract boolean hasRole(PrincipalCollection principalcollection, String s);
public abstract boolean[] hasRoles(PrincipalCollection principalcollection, List list);
public abstract boolean hasAllRoles(PrincipalCollection principalcollection, Collection collection);
public abstract void checkRole(PrincipalCollection principalcollection, String s)
throws AuthorizationException;
public abstract void checkRoles(PrincipalCollection principalcollection, Collection collection)
throws AuthorizationException;
public transient abstract void checkRoles(PrincipalCollection principalcollection, String as[])
throws AuthorizationException;
}
/*
DECOMPILATION REPORT
Decompiled from: E:\webapp2\workspace\ShiroAuthentication\lib\shiro-core-1.2.4.jar
Total time: 56 ms
Jad reported messages/errors:
Exit status: 0
Caught exceptions:
*/
从上面的接口中,可以分成两大类,第一类是验证用户的某个或某些权限,第二类是验证用户的某个角色或某些角色。角色则是一组权限的集合,所以后者是粗粒度的验证,而前者是细粒度的验证。对于那些check方法则是验证不通过时抛出异常。
接口实现类图为:
可以看到很多的Realm都实现了该接口,即这些Realm不仅提供登陆验证,还提供权限验证。
先来看下默认使用的ModularRealmAuthorizer:
public class ModularRealmAuthorizer implements Authorizer, PermissionResolverAware, RolePermissionResolverAware {
protected Collection realms;
protected PermissionResolver permissionResolver;
protected RolePermissionResolver rolePermissionResolver;
//略
}
可以看到,它有三个重要属性,Realm集合和PermissionResolver 、RolePermissionResolver 。PermissionResolver 是什么呢?
package org.apache.shiro.authz.permission;
import org.apache.shiro.authz.Permission;
public interface PermissionResolver
{
public abstract Permission resolvePermission(String s);
}
就是将权限字符串解析成Permission 对象,同理RolePermissionResolver 如下:
package org.apache.shiro.authz.permission;
import java.util.Collection;
public interface RolePermissionResolver
{
public abstract Collection resolvePermissionsInRole(String s);
}
将角色字符串解析成Permission 集合
我们首先来看看ModularRealmAuthorizer类的几个方法:
public ModularRealmAuthorizer(Collection<Realm> realms) {
setRealms(realms);
}
public void setRealms(Collection<Realm> realms) {
this.realms = realms;
applyPermissionResolverToRealms();
applyRolePermissionResolverToRealms();
}
public void setPermissionResolver(PermissionResolver permissionResolver) {
this.permissionResolver = permissionResolver;
applyPermissionResolverToRealms();
}
public void setRolePermissionResolver(RolePermissionResolver rolePermissionResolver) {
this.rolePermissionResolver = rolePermissionResolver;
applyRolePermissionResolverToRealms();
}
protected void applyRolePermissionResolverToRealms() {
RolePermissionResolver resolver = getRolePermissionResolver();
Collection<Realm> realms = getRealms();
if (resolver != null && realms != null && !realms.isEmpty()) {
for (Realm realm : realms) {
if (realm instanceof RolePermissionResolverAware) {
((RolePermissionResolverAware) realm).setRolePermissionResolver(resolver);
}
}
}
}
protected void applyPermissionResolverToRealms() {
PermissionResolver resolver = getPermissionResolver();
Collection<Realm> realms = getRealms();
if (resolver != null && realms != null && !realms.isEmpty()) {
for (Realm realm : realms) {
if (realm instanceof PermissionResolverAware) {
((PermissionResolverAware) realm).setPermissionResolver(resolver);
}
}
}
}
以上这几个方法,其目的都是如果哪些Realm 想要PermissionResolver 或RolePermissionResolver 参数,则将ModularRealmAuthorizer 的对应参数传给它。
7、我们再来看看ModularRealmAuthorizer 是如何实现Authorizer接口的:
protected void assertRealmsConfigured() throws IllegalStateException {
Collection<Realm> realms = getRealms();
if (realms == null || realms.isEmpty()) {
String msg = "Configuration error: No realms have been configured! One or more realms must be " +
"present to execute an authorization operation.";
throw new IllegalStateException(msg);
}
}
public boolean isPermitted(PrincipalCollection principals, String permission) {
assertRealmsConfigured();
for (Realm realm : getRealms()) {
if (!(realm instanceof Authorizer)) continue;
if (((Authorizer) realm).isPermitted(principals, permission)) {
return true;
}
}
return false;
}
首先是判断Collection<Realm> realms集合是否为空,然后就是将那些实现了Authorizer接口的Realm 来判断是否具有某个权限,也就是ModularRealmAuthorizer本身并不去权限验证,而是交给那些具有权限验证功能的Realm去验证(即那些Realm实现了Authorizer接口)。所以
ModularRealmAuthorizer并不具有太多实际内容,我们转战那些实现了Authorizer接口的Realm,去看看他们的验证过程。
这时候,就需要看Authorizer接口的另一个分支即下图AuthorizingRealm分支:
AuthorizingRealm涉及到Realm,所以再把Realm说清楚。Realm接口如下
public interface Realm {
String getName();
boolean supports(AuthenticationToken token);
AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;
}
Realm 本身只具有验证用户是否合法的功能,不具有授权的功能。
AuthenticatingRealm及其子类主要完成认证流程,首先是其的初始化,AuthenticatingRealm及其子类都实现了Initializable接口,初始化的时候会首先获取其缓存,如下:
public final void init() {
//trigger obtaining the authorization cache if possible
getAvailableAuthenticationCache();
onInit();
}
private Cache<Object, AuthenticationInfo> getAvailableAuthenticationCache() {
Cache<Object, AuthenticationInfo> cache = getAuthenticationCache();
boolean authcCachingEnabled = isAuthenticationCachingEnabled();
if (cache == null && authcCachingEnabled) {
cache = getAuthenticationCacheLazy();
}
return cache;
}
private Cache<Object, AuthenticationInfo> getAuthenticationCacheLazy() {
if (this.authenticationCache == null) {
log.trace("No authenticationCache instance set. Checking for a cacheManager...");
CacheManager cacheManager = getCacheManager();
if (cacheManager != null) {
String cacheName = getAuthenticationCacheName();
log.debug("CacheManager [{}] configured. Building authentication cache '{}'", cacheManager, cacheName);
this.authenticationCache = cacheManager.getCache(cacheName);
}
}
return this.authenticationCache;
}
首先会获取Cache<Object, AuthenticationInfo> cache属性,如果没有,再判断是否允许缓存,如果允许,则通过CacheManager 来获取,之前已分析过,如果还没有则会创建一个Cache,然后返回。
再看下认证过程如下:
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info = getCachedAuthenticationInfo(token);
if (info == null) {
//otherwise not cached, perform the lookup:
info = doGetAuthenticationInfo(token);
log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
if (token != null && info != null) {
cacheAuthenticationInfoIfPossible(token, info);
}
} else {
log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
}
if (info != null) {
assertCredentialsMatch(token, info);
} else {
log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token);
}
return info;
}
首先从缓存中尝试是否能找到AuthenticationInfo ,如果找不到,则需要子类去完成具体的认证细节,然后再存储到缓存中,因为本类并没有具体的数据源,只有缓存源,所以本类只是搭建了认证流程,具体的认证细节则由具体的子类来完成,所以 doGetAuthenticationInfo(token)是一个protected的抽象方法,如下:
protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;
当缓存中存在或者子类进行具体的认证后,下一步的操作是要进行密码匹配的过程,AuthenticatingRealm有一个属性CredentialsMatcher credentialsMatcher,接口如下:
public interface CredentialsMatcher {
boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info);
}
就是匹配我们认证时的AuthenticationToken 和刚才已找到的AuthenticationInfo 是否匹配。有如下的实现类:AllowAllCredentialsMatcher、PasswordMatcher、SimpleCredentialsMatcher等等。AuthenticatingRealm的构造函数默认使用的是SimpleCredentialsMatcher:
public AuthenticatingRealm() {
this(null, new SimpleCredentialsMatcher());
}
public AuthenticatingRealm(CacheManager cacheManager) {
this(cacheManager, new SimpleCredentialsMatcher());
}
public AuthenticatingRealm(CredentialsMatcher matcher) {
this(null, matcher);
}
这一块内容先暂时不讲,后续文章再来详细说明。
当你匹配通过了,则就算认证成功了。认证流程就在AuthenticatingRealm中完成了。
我们再向它的子类AuthorizingRealm研究,这个就有涉及到授权的功能了。AuthenticatingRealm是将整个认证流程框架化,AuthorizingRealm则是将整个授权流程框架化,AuthorizingRealm也有授权缓存,所以会通过父类CachingRealm来获取CacheManager,同时也有一个子缓存开关authorizationCachingEnabled,和AuthenticatingRealm基本类似,属性如下:
public abstract class AuthorizingRealm extends AuthenticatingRealm
implements Authorizer, Initializable, PermissionResolverAware, RolePermissionResolverAware {
private static final String DEFAULT_AUTHORIZATION_CACHE_SUFFIX = ".authorizationCache";
private static final AtomicInteger INSTANCE_COUNT = new AtomicInteger();
private boolean authorizationCachingEnabled;
private Cache<Object, AuthorizationInfo> authorizationCache;
private String authorizationCacheName;
private PermissionResolver permissionResolver;
private RolePermissionResolver permissionRoleResolver;
public AuthorizingRealm() {
this(null, null);
}
public AuthorizingRealm(CacheManager cacheManager) {
this(cacheManager, null);
}
public AuthorizingRealm(CredentialsMatcher matcher) {
this(null, matcher);
}
public AuthorizingRealm(CacheManager cacheManager, CredentialsMatcher matcher) {
super();
if (cacheManager != null) setCacheManager(cacheManager);
if (matcher != null) setCredentialsMatcher(matcher);
this.authorizationCachingEnabled = true;
this.permissionResolver = new WildcardPermissionResolver();
int instanceNumber = INSTANCE_COUNT.getAndIncrement();
this.authorizationCacheName = getClass().getName() + DEFAULT_AUTHORIZATION_CACHE_SUFFIX;
if (instanceNumber > 0) {
this.authorizationCacheName = this.authorizationCacheName + "." + instanceNumber;
}
}
//略
}
AtomicInteger 同样是用于对那些具有授权功能的Realm进行数量统计的,authorizationCachingEnabled缓存子开关,authorizationCache缓存,authorizationCacheName缓存名字。 PermissionResolver permissionResolver、RolePermissionResolver permissionRoleResolver这两个则是对字符串进行解析对应的Permission和Collection<Permission>的。我们来看下AuthorizingRealm的主要功能,对于授权接口Authorizer的实现:
public boolean hasRole(PrincipalCollection principal, String roleIdentifier) {
AuthorizationInfo info = getAuthorizationInfo(principal);
return hasRole(roleIdentifier, info);
}
首先就是获取授权信息,看下getAuthorizationInfo:
protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {
if (principals == null) {
return null;
}
AuthorizationInfo info = null;
if (log.isTraceEnabled()) {
log.trace("Retrieving AuthorizationInfo for principals [" + principals + "]");
}
Cache<Object, AuthorizationInfo> cache = getAvailableAuthorizationCache();
if (cache != null) {
if (log.isTraceEnabled()) {
log.trace("Attempting to retrieve the AuthorizationInfo from cache.");
}
Object key = getAuthorizationCacheKey(principals);
info = cache.get(key);
if (log.isTraceEnabled()) {
if (info == null) {
log.trace("No AuthorizationInfo found in cache for principals [" + principals + "]");
} else {
log.trace("AuthorizationInfo found in cache for principals [" + principals + "]");
}
}
}
if (info == null) {
// Call template method if the info was not found in a cache
info = doGetAuthorizationInfo(principals);
// If the info is not null and the cache has been created, then cache the authorization info.
if (info != null && cache != null) {
if (log.isTraceEnabled()) {
log.trace("Caching authorization info for principals: [" + principals + "].");
}
Object key = getAuthorizationCacheKey(principals);
cache.put(key, info);
}
}
return info;
}
同样很容易理解,先得到缓存,从缓存中去找有没有授权信息,如果没有,则需要子类去完成具体的授权细节即doGetAuthorizationInfo,授权完成后放置缓存中。同样doGetAuthorizationInfo是protected的抽象方法,由子类去实现。PermissionResolver permissionResolver、RolePermissionResolver permissionRoleResolver则是发挥如下作用:
private Collection<Permission> getPermissions(AuthorizationInfo info) {
Set<Permission> permissions = new HashSet<Permission>();
if (info != null) {
Collection<Permission> perms = info.getObjectPermissions();
if (!CollectionUtils.isEmpty(perms)) {
permissions.addAll(perms);
}
perms = resolvePermissions(info.getStringPermissions());
if (!CollectionUtils.isEmpty(perms)) {
permissions.addAll(perms);
}
perms = resolveRolePermissions(info.getRoles());
if (!CollectionUtils.isEmpty(perms)) {
permissions.addAll(perms);
}
}
if (permissions.isEmpty()) {
return Collections.emptySet();
} else {
return Collections.unmodifiableSet(permissions);
}
}
即有了授权信息AuthorizationInfo 后,获取所有的权限Permission,有三种途径来收集,第一种就是info.getObjectPermissions() info中直接含有Permission对象集合,第二种就是info.getStringPermissions() info中有字符串形式的权限表示,第三种就是info.getRoles() info中含有角色集合,角色也是一组权限的集合,看下resolvePermissions(info.getStringPermissions()):
private Collection<Permission> resolvePermissions(Collection<String> stringPerms) {
Collection<Permission> perms = Collections.emptySet();
PermissionResolver resolver = getPermissionResolver();
if (resolver != null && !CollectionUtils.isEmpty(stringPerms)) {
perms = new LinkedHashSet<Permission>(stringPerms.size());
for (String strPermission : stringPerms) {
Permission permission = getPermissionResolver().resolvePermission(strPermission);
perms.add(permission);
}
}
return perms;
}
也很简单,对于每一个strPermission 通过PermissionResolver 转化成Permission 对象,对于resolveRolePermissions也同理,不再说明。这里具体的转化细节先暂且不说,后续再将。
现在终于把认证流程和授权框架流程大致说完了,即AuthenticatingRealm和AuthorizingRealm的内容,他们分别留给子类protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;具体的认证方法和protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)具体的授权方法。