shiro——认证原理(源码流程)

目录

 

1. Subject 的本质

1.1 login( token ) 登录

1.1.1 this.clearRunAsIdentitiesInternal() 解析 ——1

1.1.2 this.securityManager.login(this, token) 解析 ——2


1. Subject 的本质

一般我们用下面的语句进行登录认证,没有抛出异常就是验证成功。

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就可以操作本用户的很多东西。

1.1 login( token ) 登录

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);
        }
    }

1.1.1 this.clearRunAsIdentitiesInternal() 解析 ——1

首先,既然我们的用户要执行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);
        }
}

1.1.2 this.securityManager.login(this, token) 解析 ——2

我们配置的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;
            }
        }
}

 

你可能感兴趣的:(shiro)