Shiro源码分析(六)——认证流程

2021SC@SDUSC

一.Demo代码

Shiro认证即为我们平时的“登录”,这篇文章我们来探究一下Shieo登录的底层实现。

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.ini.IniSecurityManagerFactory;
import org.apache.shiro.lang.util.Factory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyQuickStart {

    // 使用工厂模式创建日志工具,方便打印日志。
    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);

    public static void main(String[] args) {
        // 创建Shiro SecurityManager并配置realms, users, roles 和权限的最简单方式是通过INI文件。
        // 我们向一个工厂中传入.ini文件,然后工厂会返回一个SecurityManager实例。
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:myshiro.ini");
        SecurityManager securityManager = factory.getInstance();

        // 在这个简单的demo中,将SecurityManager作为一个JVM单例进行访问。
        // 大多数应用的代码不这么写,而是依赖他们的容器或是在web应用中的web.xml文件
        SecurityUtils.setSecurityManager(securityManager);

        // 获取当前正在操作的用户->1
        Subject currentUser = SecurityUtils.getSubject();

        // 登录当前用户->2
        if (!currentUser.isAuthenticated()) {
            // 3
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            token.setRememberMe(true); // 记住我->4
            try {
                currentUser.login(token); // 登录(进行认证)->5
            } catch (UnknownAccountException uae) {  // 用户不存在
            	// token.getPrincipal()->6
                log.info("There is no user with username of " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {  // 密码错误
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) {  // 用户被锁定
                log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                        "Please contact your administrator to unlock it.");
            }
            // ... 在这里捕获更多异常,可以在你的应用程序中自定义一个
            catch (AuthenticationException ae) {
                // 意外情况?  错误?
            }
        }

        // 报告谁登录成功了:
        // 打印他们的认证凭据--principal(在这个例子中是用户名):
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

        // 退出登录->7
        currentUser.logout();

        System.exit(0);
    }
}

二.源码分析

该部分源码将分四篇文章进行分析,本篇文章分析代码的5.3-5.7部分。

5

public class DelegatingSubject implements Subject {
	public void login(AuthenticationToken token) throws AuthenticationException {
	
        clearRunAsIdentitiesInternal(); // 5.1
        Subject subject = securityManager.login(this, token); // 5.2

        PrincipalCollection principals;

        String host = null;

        if (subject instanceof DelegatingSubject) {
            DelegatingSubject delegating = (DelegatingSubject) subject;
            principals = delegating.principals;
            host = delegating.host;
        } else {
            principals = subject.getPrincipals(); // 5.3
        }

        if (principals == null || principals.isEmpty()) { // 5.4
            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(); // 5.5
        }
        if (host != null) {
            this.host = host;
        }
        Session session = subject.getSession(false); // 5.6
        if (session != null) {
            this.session = decorate(session); // 5.7
        } else {
            this.session = null;
        }
    }
}

5.3

public class DelegatingSubject implements Subject {
	// 以PrincipalCollection的形式返回这个Subject的主体(标识属性);
	// 如果这个Subject是匿名的,因为它还没有任何关联的帐户数据(例如,如果它们还没有登录),则返回null。
    public PrincipalCollection getPrincipals() {
        List<PrincipalCollection> runAsPrincipals = getRunAsPrincipalsStack();
        return CollectionUtils.isEmpty(runAsPrincipals) ? this.principals : runAsPrincipals.get(0);
    }
    
	// 获取PrincipalCollection集合
    private List<PrincipalCollection> getRunAsPrincipalsStack() {
        Session session = getSession(false);
        if (session != null) {
            return (List<PrincipalCollection>) session.getAttribute(RUN_AS_PRINCIPALS_SESSION_KEY);
        }
        return null;
    }
}

5.4

// PrincipalMap接口的默认实现。
// PrincipalMap是与相应Subject相关联的所有Subject的集合。
// Subject只是标识属性的安全术语,例如用户名、用户id或社会安全号码或任何可以被视为Subject的“标识”属性的东西。
// PrincipalCollection根据Subject第一次创建时它们来自的领域来组织其内部的principal。
// 要获取特定领域的主体,可以查看fromRealm方法,还可以通过getRealmNames()方法查看哪些领域对该集合做出了贡献。
public class SimplePrincipalMap implements PrincipalMap {
	// 如果该集合为空则返回true,否则返回false.
    public boolean isEmpty() {
        return CollectionUtils.isEmpty(this.combinedPrincipals);
    }
}

5.5

public class UsernamePasswordToken implements HostAuthenticationToken, RememberMeAuthenticationToken {
	// 尝试登录的位置,如果不知道或显式省略,则为空。
	private String host;
	// 返回发起身份验证尝试的客户端的主机名,或者如果Shiro环境不能或选择不解析主机名以提高性能,该方法返回客户端的IP地址的String形式。
	// 在web环境中使用时,该值通常与ServletRequest.getRemoteHost()值相同。
    public String getHost() {
        return host;
    }
}

5.6

public class DelegatingSubject implements Subject {
	// 返回与此主题关联的应用程序Session。基于布尔参数,该方法的功能如下:
	// 如果已经有一个与该Subject关联的现有会话,则返回该会话并忽略create参数。
	// 如果没有会话存在,并且create为真,那么将创建一个新的会话,并与这个Subject关联,然后返回。
	// 如果没有会话,并且create为false,则返回null。
	public Session getSession(boolean create) {
        if (log.isTraceEnabled()) {
            log.trace("attempting to get session; create = " + create +
                    "; session is null = " + (this.session == null) +
                    "; session has id = " + (this.session != null && session.getId() != null));
        }

        if (this.session == null && create) {
            //added in 1.2:
            if (!isSessionCreationEnabled()) {
                String msg = "Session creation has been disabled for the current subject.  This exception indicates " +
                        "that there is either a programming error (using a session when it should never be " +
                        "used) or that Shiro's configuration needs to be adjusted to allow Sessions to be created " +
                        "for the current Subject.  See the " + DisabledSessionException.class.getName() + " JavaDoc " +
                        "for more.";
                throw new DisabledSessionException(msg);
            }

            log.trace("Starting session for host {}", getHost());
            SessionContext sessionContext = createSessionContext();
            Session session = this.securityManager.start(sessionContext);
            this.session = decorate(session);
        }
        return this.session;
    }
}

5.7

public class DelegatingSubject implements Subject {

    protected Session decorate(Session session) {
        if (session == null) {
            throw new IllegalArgumentException("session cannot be null");
        }
        return new StoppingAwareProxiedSession(session, this);
    }

	// 内部类
    private class StoppingAwareProxiedSession extends ProxiedSession {

        private final DelegatingSubject owner;

        private StoppingAwareProxiedSession(Session target, DelegatingSubject owningSubject) {
            super(target);
            owner = owningSubject;
        }

        public void stop() throws InvalidSessionException {
            super.stop();
            owner.sessionStopped();
        }
    }
}

(完)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值