SHIRO源码解读——Authenticator身份认证

身份认证,即在应用中证明他就是他本人。一般提供如他们的身份ID一些标识信息来 表明他就是他本人,如提供身份证,用户名/密码来证明。在 shiro 中,用户需要提供 principals (身份)和 credentials(证明)给 shiro,从而应用能 验证用户身份。本文重点关注利用shiro进行身份认证时,shiro的内部工作流程。

一、使用shiro身份认证demo

使用shiro进行身份认证的最简单的demo如下:

public void testHelloworld() {
    //1、获取 SecurityManager 工厂,此处使用 Ini 配置文件初始化SecurityManager 
    Factory 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.
 * 

**/

至此,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 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身份认证分析到此为止,祝工作顺利,天天开心!

你可能感兴趣的:(SHIRO源码解读——Authenticator身份认证)