目录
1. Subject 的本质
1.1 login( token ) 登录
1.1.1 this.clearRunAsIdentitiesInternal() 解析 ——1
1.1.2 this.securityManager.login(this, token) 解析 ——2
一般我们用下面的语句进行登录认证,没有抛出异常就是验证成功。
SecurityUtils.getSubject().login(token);
相当于
//token是用户登录发送给后台的登录数据,一般用UsernamePasswordToken存储
Subject subject = SecurityUtils.getSubject();
subject.login(token)
SecurityUtils.getSubject() 创建的subject 实例化对象,其实是 DelegatingSubject 类对象。
首先理解一个东西:ThreadLocal 类,这是一个本地线程存储类,是一个存储器,用于存储多个线程的内容,将内容与线程一一对应,不会混淆,即使两个线程存了同一个名字的东西,也不会混淆,线程是可以直接获取自己存在存储器中的东西 。
创建Subject的过程: 首先用户的web请求来,spring就会为这个web请求创建一个线程去处理它,web的第一个请求一定是login登录,因为只有登录了才能使用,那么登录的时候,会分配一个线程,然后将用户的ID和线程关联起来,如何关联呢?就是在ThreadLocal中为当前线程存储上用户ID,好了,紧接着,用户做了一个web请求,请求中肯定有能够唯一标识用户的信息(即用户ID),spring后台先看看,是否这个用户的线程还在,如果在,那就可以直接去ThreadLocal中拿到用户ID,如果不在,那就重新创建一个Subject(想想,Subject就可以理解为用户标识信息)。
一路追踪,发现,我们的Subject其实是由DefaultSubjectFactory 工厂生成的,创建的是一个DelegatingSubject对象。
public class DefaultSubjectFactory implements SubjectFactory {
public DefaultSubjectFactory() {
}
public Subject createSubject(SubjectContext context) {
SecurityManager securityManager = context.resolveSecurityManager();
Session session = context.resolveSession();
boolean sessionCreationEnabled = context.isSessionCreationEnabled();
PrincipalCollection principals = context.resolvePrincipals();
boolean authenticated = context.resolveAuthenticated();
String host = context.resolveHost();
return new DelegatingSubject(principals, authenticated, host, session, sessionCreationEnabled, securityManager);
}
/** @deprecated */
@Deprecated
protected Subject newSubjectInstance(PrincipalCollection principals, boolean authenticated, String host, Session session, SecurityManager securityManager) {
return new DelegatingSubject(principals, authenticated, host, session, true, securityManager);
}
}
由此可见:Subject 是与 principals(身份标识) , authenticated(是否已被认证), host(主机), session(会话), sessionCreationEnabled(是否允许创建会话), securityManager(安全管理器,shiro的核心) ,与这些东西相绑定,所以我们可以知道,通过Subject就可以操作本用户的很多东西。
Subject.login(token) 其实就是 DelegatingSubject 类里面实现的login方法,源码如下:
public void login(AuthenticationToken token) throws AuthenticationException {
this.clearRunAsIdentitiesInternal(); ——1
Subject subject = this.securityManager.login(this, token); ——2
//后面是认证成功后的执行代码,就是将Subject的相关内容设置好
String host = null;
PrincipalCollection principals;
if (subject instanceof DelegatingSubject) {
DelegatingSubject delegating = (DelegatingSubject)subject;
principals = delegating.principals;
host = delegating.host;
} else {
principals = subject.getPrincipals();
}
if (principals != null && !principals.isEmpty()) {
this.principals = principals;
this.authenticated = true;
if (token instanceof HostAuthenticationToken) {
host = ((HostAuthenticationToken)token).getHost();
}
if (host != null) {
this.host = host;
}
Session session = subject.getSession(false);
if (session != null) {
this.session = this.decorate(session);
} else {
this.session = null;
}
} else {
String msg = "Principals returned from securityManager.login( token ) returned a null or empty value. This value must be non null and populated with one or more elements.";
throw new IllegalStateException(msg);
}
}
首先,既然我们的用户要执行login登录认证,那么不管之前是否有存储用户的会话,都应该清除干净,因为要重新认证了呀。这个方法就是清空Session,如果后台之前已经存储了用户的会话,那么久将会话清空(不是将会话删除),如果后台之前没有存储用户的会话,那么什么也不做(正好呀)。
private void clearRunAsIdentitiesInternal() {
try {
this.clearRunAsIdentities();
} catch (SessionException var2) {
log.debug("Encountered session exception trying to clear 'runAs' identities during logout. This can generally safely be ignored.", var2);
}
}
private void clearRunAsIdentities() {
Session session = this.getSession(false);
if (session != null) {
session.removeAttribute(RUN_AS_PRINCIPALS_SESSION_KEY);
}
}
我们配置的securityManager 是用DefaultWebSecurityManager 。
这次说一下如何构成subejct——使用subjectContext类。这个类就是一个map,然后将构建subject的所有属性都组织到一起,然后传递给一个subjectFactory,用于构成一个subject,所以在用SubjectFactory 工厂根据SubjectContext 来创建Subject之前,一定要配置好SubjectContext里的内容。 最终Subject是存进了用户的会话中,会话中有Map成员变量,Subject就存在这个atribute(Map类型)中,key为 DefaultSubjectContext_PRINCIPALS_SESSION_KEY。
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
info = this.authenticate(token); //登录认证
} catch (AuthenticationException var7) {
AuthenticationException ae = var7;
try {
this.onFailedLogin(token, ae, subject);
} catch (Exception var6) {
if (log.isInfoEnabled()) {
log.info("onFailedLogin method threw an exception. Logging and propagating original AuthenticationException.", var6);
}
}
throw var7;
}
//下面是登录成功后,才能执行的,如果登录失败,那么上面就抛出异常了
Subject loggedIn = this.createSubject(token, info, subject);
this.onSuccessfulLogin(token, info, loggedIn);
return loggedIn;
}
protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) {
SubjectContext context = this.createSubjectContext();//获取SubjectContext,用于交给Subject工厂去创建Subject
context.setAuthenticated(true); //设置为已被认证
context.setAuthenticationToken(token);//设置认证token
context.setAuthenticationInfo(info); //设置认证成功的认证信息
if (existing != null) {
context.setSubject(existing); //如果有,则要加上Subject
}
return this.createSubject(context); //创建subject
}
public Subject createSubject(SubjectContext subjectContext) {
SubjectContext context = this.copy(subjectContext);
context = this.ensureSecurityManager(context);//给context设置securityManager
context = this.resolveSession(context); //设置Session
context = this.resolvePrincipals(context); //设置身份标识信息
Subject subject = this.doCreateSubject(context);//创建Subject
this.save(subject); //将Subject存进用户的会话中
return subject;
}
protected Subject doCreateSubject(SubjectContext context) {
//用subject工厂根据Subjectcontext 来创建Subject
return this.getSubjectFactory().createSubject(context);
}
protected void save(Subject subject) {
//将Subject存进用户的会话中,SubjectDAO默认用的是DefaultSubjectDAO
this.subjectDAO.save(subject);
}
Authenticator是我们认证的入口,但这是个接口,默认是ModularRealmAuthorizer来实现的,调用authenticate( token ) 开始认证。
//认证过程
public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
return this.authenticator.authenticate(token); //开始认证
}
//省略了一些不重要的错误处理过程,只保留核心部分
public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
if (token == null) {
throw new IllegalArgumentException("Method argument (authentication token) cannot be null.");
} else {
log.trace("Authentication attempt received for token [{}]", token);
AuthenticationInfo info;
try {
info = this.doAuthenticate(token); //执行认证
} catch (Throwable var8) {
//错误处理
}
log.debug("Authentication successful for token [{}]. Returned account [{}]", token, info);
this.notifySuccess(token, info);
return info;
}
}
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
this.assertRealmsConfigured(); //配置好我们在shiro配置文件中配置的Realm
Collection realms = this.getRealms();
//如果只有一个Realm,就执行单Realm认证,如果是多个Realm,那就要按照认证策略去挨个认证
return realms.size() == 1 ? this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken) : this.doMultiRealmAuthentication(realms, authenticationToken);
}
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
if (!realm.supports(token)) {
//不支持处理
} else {
//这一句就是我们非常熟悉的Realm中的两个重要方法之一的认证方法
AuthenticationInfo info = realm.getAuthenticationInfo(token);
if (info == null) {
//认证成功信息为空的处理
} else {
return info;
}
}
}