上一次说完了登陆中认证和授权的流程,今天学习下在Authenticator和Authorizer之后,下
一步的流程。
1.3,Subject、SecurityManager的认证流程解析
学习过第二章,实际搭建的同学会了解,我们一般登陆验证的代码:
1 //获取Subject对象
2 Subject subject = SecurityUtils.getSubject();
3 //创建用户名密码Token
4 UsernamePasswordToken usernamePasswordToken =
5 new UsernamePasswordToken(username, password);
6 //捕获相应的异常
7 try{
8 //实际登陆操作,验证用户名密码
9 subject.login(usernamePasswordToken);
10 }catch (UnauthenticatedException e){
11 System.err.println("UnauthenticatedException");
12 }catch (UnknownAccountException e){
13 System.err.println("UnknownAccountException");
14 }catch (IncorrectCredentialsException e){
15 System.err.println("IncorrectCredentialsException");
16 }
首先我们一步一步分析。
第2行,首先程序通过SecurityUtils拿到Subject,SecurityUtils可以看作是一个Shiro的工具
类,内部主要就是负责获取Subject和SecurityManager全局资源。那么Subject作为我们使用
Shiro的API核心工具,我们只有拿到它才可以做登陆、鉴权、等一系列操作。那么我们就来研
究下Subject的实例过程吧。
下面是Subject由SecurityUtils中getSubject()方法获取:
public static Subject getSubject() {
//首先去ThreadContext寻找当前线程是否已经绑定了Subject
Subject subject = ThreadContext.getSubject();
//如果查找为空则进行新建Subject 之后进行线程绑定
if (subject == null) {
subject = (new Subject.Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
}
在获取Subject的时候,SecurityUtils每次都会先去ThreadContext中去查找是否已存在当前
线程绑定的Subject,ThreadContext即为线程相关的上下文,像Subject作为线程相关的资源
都会放在里面的resources对象中。
public abstract class ThreadContext {
/**
* Private internal log instance.
*/
private static final Logger log = LoggerFactory.getLogger(ThreadContext.class);
//预先定义好的securityManager的Key字符串
//一般为 org.apache.shiro.mgt.DefaultSecurityManager_SECURITY_MANAGER_KEY
public static final String SECURITY_MANAGER_KEY = ThreadContext.class.getName() + "_SECURITY_MANAGER_KEY";
public static final String SUBJECT_KEY = ThreadContext.class.getName() + "_SUBJECT_KEY";
//存储键值对,核心存储与当前线程相关的对象
private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();
ThreadContext内部主要提供了put、get、bind绑定、unbind解绑定等操作。每次获取
Subject的时候会做一些基本检查,例如线程追踪等一些操作,你就把它理解为Map对象通过
get(KEY)进行获取就可以了。
因为我们是第一次运行,故此时在ThreadContext内肯定是获取不到Subject对象的,于是
下一步就是New一个Subject对象:
<-------SecurityUtils类--------->
subject = (new Subject.Builder()).buildSubject();<-------Subject类--------->
//追踪发现
//新建Subject首先要获取SecurityManager所有又返回SecurityUtils中获取
public Builder() {
this(SecurityUtils.getSecurityManager());
}
<-------SecurityUtils类--------->
//查看SecurityUtils中的getSecurityManager
public static SecurityManager getSecurityManager() throws UnavailableSecurityManagerException { //首先和Subject如出一辙,先去ThreadContext尝试获取是否已经创建的securityManager
SecurityManager securityManager = ThreadContext.getSecurityManager();
//此时securityManager获取肯定为null
if (securityManager == null) {
//这里就比较重要的官方说这是VM单例中的备份,
//当ThreadContext中没有的时候才用此securityManager进行获取Subject
securityManager = SecurityUtils.securityManager;
}
//此时我们已经获取了DefaultSecurityManager
if (securityManager == null) {
String msg = "No SecurityManager accessible to the calling code, either bound to the " +
ThreadContext.class.getName() + " or as a vm static singleton. This is an invalid application " +
"configuration.";
throw new UnavailableSecurityManagerException(msg);
}
return securityManager;
}
在获取到securityManager之后,接着进行Subject的获取。
public Builder(SecurityManager securityManager) {
//进行对Subject中的属性赋值
if (securityManager == null) {
throw new NullPointerException("SecurityManager method argument cannot be null.");
}
//赋值securityManager
this.securityManager = securityManager;
//赋值subjectContext,可是现在为空,所以先进行创建subjectContext
//subjectContext是构建subject的基础
this.subjectContext = newSubjectContextInstance();
if (this.subjectContext == null) {
throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' " +
"cannot be null.");
}
this.subjectContext.setSecurityManager(securityManager);
}
这里介绍下SubjectContext,它是Subject的一个数据视图,用于构建Subject的实例。作为
接口,由DefaultSubjectContext具体类去实现。DefaultSubjectContext继承MapContext类,
MapContext类中的backMap为空,故此时找不到DefaultSubjectContext。那么此时会调用
DefaultSubjectContext中
//把securityManager
public void setSecurityManager(SecurityManager securityManager) {
nullSafePut(SECURITY_MANAGER, securityManager);
}
//进行推入DefaultSubjectContext
protected void nullSafePut(String key, Object value) { if (value != null) {
put(key, value);
}
}
到这里就拿到了DefaultSubjectContext对象了。
//真正的创建Subject对象
public Subject buildSubject() {
return this.securityManager.createSubject(this.subjectContext);
}
可以看到Subject对象本质还是由SecurityManager进行创建。下面是创建的核心方法:
public Subject createSubject(SubjectContext subjectContext) {
//create a copy so we don't modify the argument's backing map:
//先复制一个备份
SubjectContext context = copy(subjectContext);
//ensure that the context has a SecurityManager instance, and if not, add one:
//确保必须有SecurityManager实例
context = ensureSecurityManager(context);
//Resolve an associated Session (usually based on a referenced session ID), and place it in the context before
//sending to the SubjectFactory. The SubjectFactory should not need to know how to acquire sessions as the
//process is often environment specific - better to shield the SF from these details:
context = resolveSession(context);
//Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first
//if possible before handing off to the SubjectFactory:
context = resolvePrincipals(context);
//上面都是一些业务上的逻辑
//下面开始创建Subject
Subject subject = doCreateSubject(context);
//save this subject for future reference if necessary:
//(this is needed here in case rememberMe principals were resolved and they need to be stored in the
//session, so we don't constantly rehydrate the rememberMe PrincipalCollection on every operation).
//Added in 1.2:
save(subject);
return subject;
}
protected Subject doCreateSubject(SubjectContext context) {
return getSubjectFactory().createSubject(context);
}
public SubjectFactory getSubjectFactory() {
return subjectFactory;
}
//兜兜转转又把创建SecurityManager的工作交给了Subject工厂类
public Subject createSubject(SubjectContext context) {
//先获取securityManager
SecurityManager securityManager = context.resolveSecurityManager();
Session session = context.resolveSession();
boolean sessionCreationEnabled = context.isSessionCreationEnabled();
PrincipalCollection principals = context.resolvePrincipals();
boolean authenticated = context.resolveAuthenticated();
String host = context.resolveHost();
//上面分别是设置session 设置凭证 和设置认证情况 一些基本属性
return new DelegatingSubject(principals, authenticated, host, session, sessionCreationEnabled, securityManager);
}
最好又把Subject创建的任务交给了托管类
public DelegatingSubject(PrincipalCollection principals, boolean authenticated, String host,
Session session, boolean sessionCreationEnabled, SecurityManager securityManager) {
if (securityManager == null) {
throw new IllegalArgumentException("SecurityManager argument cannot be null.");
}
this.securityManager = securityManager;
this.principals = principals;
this.authenticated = authenticated;
this.host = host;
if (session != null) {
this.session = decorate(session);
}
this.sessionCreationEnabled = sessionCreationEnabled;
}
到此为止真正的创建完成。
之后执行SAVE保存在Session中,返回一个DelegationSubject类。
public Subject save(Subject subject) {
if (isSessionStorageEnabled(subject)) {
saveToSession(subject);
} else {
log.trace("Session storage of subject state for Subject [{}] has been disabled: identity and " +
"authentication state are expected to be initialized on every request or invocation.", subject);
}
return subject;
}
最后一步就是进行绑定,把他推进ThreadContext中的resources中,官方说明一般Subject获
取的主要来源都是ThreadContext
public static void bind(Subject subject) {
if (subject != null) {
put(SUBJECT_KEY, subject);
}
}
至此Subject获取结束。
有很多待修改,请多原谅.