Subject.login() 的执行流程源码分析

Shiro 框架中 Subject 只有一个实现类,org.apache.shiro.subject.support.DelegatingSubject,以该类进行分析。

直接看到 login() 方法,它传入一个参数 token,可能是 UsernamePasswordToken,又或者是自定义的 token。

    public void login(AuthenticationToken token) throws AuthenticationException {
        clearRunAsIdentitiesInternal();
        Subject subject = securityManager.login(this, token);
     
        PrincipalCollection principals;
     
        String host = null;
     
        if (subject instanceof DelegatingSubject) {
            DelegatingSubject delegating = (DelegatingSubject) subject;
            // we have to do this in case there are assumed identities - we don't want to
            // lose the 'real' principals:
            principals = delegating.principals;
            host = delegating.host;
        } else {
            principals = subject.getPrincipals();
        }
     
        if (principals == null || principals.isEmpty()) {
            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);
        }
        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 = decorate(session);
        } else {
            this.session = null;
        }
    }

方法会执行到如下位置,调用了 securityManager 的 login() 方法。

Subject subject = securityManager.login(this, token);

securityManager 在 shiro 框架中有一个实现类 org.apache.shiro.mgt.DefaultSecurityManager,直接看他的 login() 方法。

    public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
        AuthenticationInfo info;
        try {
            info = authenticate(token);
        } catch (AuthenticationException ae) {
            try {
                onFailedLogin(token, ae, subject);
            } catch (Exception e) {
                if (log.isInfoEnabled()) {
                    log.info("onFailedLogin method threw an " +
                            "exception.  Logging and propagating original AuthenticationException.", e);
                }
            }
            throw ae; //propagate
        }
     
        Subject loggedIn = createSubject(token, info, subject);
     
        onSuccessfulLogin(token, info, loggedIn);
     
        return loggedIn;
    }

注意到,以上方法会执行到 authenticate() 进行认证,这是 DefaultSecurityManager 的父类 org.apache.shiro.mgt.AuthenticatingSecurityManager提供的方法,使用了一个叫 authenticator 的认证器进行认证。

    public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
        return this.authenticator.authenticate(token);
    }

authenticator 认证器在 shiro 框架中只有一个实现类,org.apache.shiro.authc.pam.ModularRealmAuthenticator
在这里插入图片描述
认证的时候先执行 AbstractAuthenticator 的方法

public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
 
    if (token == null) {
        throw new IllegalArgumentException("Method argument (authentication token) cannot be null.");
    }
 
    log.trace("Authentication attempt received for token [{}]", token);
 
    AuthenticationInfo info;
    try {
        info = doAuthenticate(token);
        if (info == null) {
            String msg = "No account information found for authentication token [" + token + "] by this " +
                "Authenticator instance.  Please check that it is configured correctly.";
            throw new AuthenticationException(msg);
        }
    } catch (Throwable t) {
        AuthenticationException ae = null;
        if (t instanceof AuthenticationException) {
            ae = (AuthenticationException) t;
        }
        if (ae == null) {
            // Exception thrown was not an expected AuthenticationException. Therefore it is
            // probably a little more
            // severe or unexpected. So, wrap in an AuthenticationException, log to warn,
            // and propagate:
            String msg = "Authentication failed for token submission [" + token + "].  Possible unexpected " +
                "error? (Typical or expected login exceptions should extend from AuthenticationException).";
            ae = new AuthenticationException(msg, t);
            if (log.isWarnEnabled())
                log.warn(msg, t);
        }
        try {
            notifyFailure(token, ae);
        } catch (Throwable t2) {
            if (log.isWarnEnabled()) {
                String msg = "Unable to send notification for failed authentication attempt - listener error?.  " +
                    "Please check your AuthenticationListener implementation(s).  Logging sending exception " +
                    "and propagating original AuthenticationException instead...";
                log.warn(msg, t2);
            }
        }
 
        throw ae;
    }
 
    log.debug("Authentication successful for token [{}].  Returned account [{}]", token, info);
 
    notifySuccess(token, info);
 
    return info;
}

然后方法走到 doAuthenticate() 执行在 ModularRealmAuthenticator 实现的方法,本质上就是调用 Realm 的方法。这里还有个注意点:认证时对单 Realm 和多 Realm 进行了区别判断。

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

总结:
通过简单的分析,明白了为什么 Springboot 应用使用需要 注入 DefaultWebSecurityManager,并且设置 自定义的 Realm。最终的 认证逻辑是交给 Realm 来执行的。

转载于:https://blog.csdn.net/qq_39291919/article/details/108289019

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值