身份认证,即在应用中证明他就是他本人。一般提供如他们的身份ID一些标识信息来 表明他就是他本人,如提供身份证,用户名/密码来证明。在 shiro 中,用户需要提供 principals (身份)和 credentials(证明)给 shiro,从而应用能 验证用户身份。本文重点关注利用shiro进行身份认证时,shiro的内部工作流程。
一、使用shiro身份认证demo
使用shiro进行身份认证的最简单的demo如下:
public void testHelloworld() {
//1、获取 SecurityManager 工厂,此处使用 Ini 配置文件初始化SecurityManager
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//2、得到 SecurityManager 实例 并绑定给 SecurityUtils
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//3、得到 Subject 及创建用户名/密码身份验证 Token(即用户身份/凭证)
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
try {
//4、登录,即身份验证
subject.login(token);
} catch (AuthenticationException e) {
//5、身份验证失败
}
Assert.assertEquals(true, subject.isAuthenticated()); //断言用户已经登录
//6、退出
subject.logout();
}
从如上代码可总结出身份验证的步骤:
1、收集用户身份/凭证,即如用户名/密码;
2、调用 Subject.login 进行登录,如果失败将得到相应的 AuthenticationException 异常,根 据异常提示用户错误信息;否则登录成功;
3、最后调用 Subject.logout 进行退出操作。
那么SHIRO内部流程是怎样的了,在创建完SecurityManager后,接下来的步骤内部做了什么工作了?
二、创建Subject
创建完SecurityManager后,首先将SecurityManager绑定给了securityUtils,然后是获取Subject。
public static Subject getSubject() {
Subject subject = ThreadContext.getSubject();
if (subject == null) {
subject = (new Subject.Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
}
这步首先构造Subject.Builder,Subject采用的Builder模式,然后调用Builder的buildeSubject()方法创建一个Subject对象,具体过程如下:
public Builder() {
this(SecurityUtils.getSecurityManager());
}
public Builder(SecurityManager securityManager) {
this.securityManager = securityManager;
this.subjectContext = newSubjectContextInstance();
this.subjectContext. setSecurityManager(securityManager);
}
其中SubjectContext为新建一个默认的DefaultSubjectContext();
protected SubjectContext newSubjectContextInstance() {
return new DefaultSubjectContext();
}
创建好Builder后,调用builderSubject方法,其内部是委托给SecurityManager创建Subject的。
public Subject buildSubject() {
return this.securityManager.createSubject(this.subjectContext);
}
SecurityManager创建过程是:首先创建一个默认的DefaultSecurityManager,然后根据配置再更新其内部属性和成员,DefaultSecurityManager中创建Subject方法如下:
public Subject createSubject(SubjectContext subjectContext) {
SubjectContext context = copy(subjectContext);
context = ensureSecurityManager(context);
context = resolveSession(context);
context = resolvePrincipals(context);
Subject subject = doCreateSubject(context);
save(subject);
return subject;
}
其中创建Subject的方法为Subject subject = doCreateSubject(context),方法定义如下:
protected Subject doCreateSubject(SubjectContext context) {
return getSubjectFactory().createSubject(context);
}
方法内部使用的是SubjectFactory创建的,具体使用的是DefaultSubjectFactory,然后调用其createSubject方法,方法定义如下:
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);
}
从上述方法可以看到,方法返回的是一个DelegatingSubject,DelegatingSubject是Subject子类,其源码注释如下:
/**
* Implementation of the {@code Subject} interface that delegates
* method calls to an underlying {@link org.apache.shiro.mgt.SecurityManager SecurityManager} instance for security checks.
* It is essentially a {@code SecurityManager} proxy.
* <p/>
**/
至此,Subject创建完毕!
三、身份认证
首先看看Shiro核心逻辑的类图:
Shiro核心逻辑类图
后面的分析均是基于此类图,下面进行详细分析。
代码subject.login(token)即进行身份认证,首先看看login方法:
public void login(AuthenticationToken token) throws AuthenticationException {
Subject subject = securityManager.login(this, token);
PrincipalCollection principals;
String host = null;
if (subject instanceof DelegatingSubject) {
DelegatingSubject delegating = (DelegatingSubject) subject;
principals = delegating.principals;
host = delegating.host;
} else {
principals = subject.getPrincipals();
}
...
}
在上面代码的第三行:Subject subject = securityManager.login(this, token); 注意到其调用了SecurityManager的login方法,login方法最终的实现在类DefaultSecurityManager中,方法如下:
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
info = authenticate(token);
} catch (AuthenticationException ae) {
onFailedLogin(token, ae, subject);
}
Subject loggedIn = createSubject(token, info, subject);
onSuccessfulLogin(token, info, loggedIn);
return loggedIn;
}
身份校验authenticate方法是在DefaultSecurityManager的父类AuthenticatingSecurityManager定义的,AuthenticatingSecurityManager.authenticate方法如下:
public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
return this.authenticator.authenticate(token);
}
其中委托给Authenticator,AbstractAuthenticator中authenticate(token)方法定义如下:
public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
info = doAuthenticate(token);
}
} catch (Throwable t) {
notifyFailure(token, exception);
}
notifySuccess(token, info);
return info;
}
真正身份校验逻辑是在ModularRealmAuthenticator中,ModularRealmAuthticator的doAuthenticate(token)方法如下:
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
Collection<Realm> realms = getRealms();
if (realms.size() == 1) {
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
} else {
return doMultiRealmAuthentication(realms, authenticationToken);
}
}
当只有一个Realm时,就执行doSingleRealmAuthentication,当有多个Realm时,就执行doMultiRealmAuthentication。我们看看doSingleRealmAuthentication中干了什么:
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
AuthenticationInfo info = realm.getAuthenticationInfo(token);
return info;
}
上面代码:AuthenticationInfo info = realm.getAuthenticationInfo(token); realm为Realm接口,实际上调用的是其实现类AuthenticatingRealm中的getAuthenticationInfo方法,方法如下:
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info = this.getCachedAuthenticationInfo(token);
if (info == null) {
info = this.doGetAuthenticationInfo(token);
if (token != null && info != null) {
this.cacheAuthenticationInfoIfPossible(token, info);
}
}
...
return info;
}
其中的this.doGetAuthenticationInfo(token)是一个抽象方法,真正的实现在我们自己定义的Realm。
三、总结
身份认证流程身份认证流程如下:
1)如果没有创建Subject,创建一个Subject;
2)调用 Subject.login(token)进行登录,其会自动委托给 Security Manager,调用之前必须通过 SecurityUtils. setSecurityManager()设置;
3)SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份验证;
4)Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,此处可以自定义插入自己的实现;
5)Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;
6)Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返 回/抛出异常表示身份验证失败了。此处可以配置多个 Realm,将按照相应的顺序及策略进 行访问。
Shiro身份认证分析到此为止,祝工作顺利,天天开心!
网友评论