shiro源码分析(一)--大致流程

去GitHub搜索shiro,克隆源码,然后会看到里面有samples模块,先从quickstart看起

一、例子源码

首先是配置文件shiro.ini

[users]
root = secret, admin
guest = guest, guest
presidentskroob = 12345, president
darkhelmet = ludicrousspeed, darklord, schwartz
lonestarr = vespa, goodguy, schwartz

[roles]
admin = *
schwartz = lightsaber:*
goodguy = winnebago:drive:eagle5

ini配置中主要配置有四大类:main,users,roles,urls,这里只包含了users,roles。
[main]
#提供了对根对象 securityManager 及其依赖的配置
securityManager=org.apache.shiro.mgt.DefaultSecurityManager
securityManager.realms=$jdbcRealm

[users]
#提供了对用户/密码及其角色的配置,用户名=密码,角色 1,角色 2
username=password,role1,role2

[roles]
#提供了角色及权限之间关系的配置,角色=权限 1,权限 2
role1=permission1,permission2

[urls]
#用于 web,提供了对 web url 拦截相关的配置,url=拦截器[参数],拦截器
/index.html = anon
/admin/** = authc, roles[admin], perms[“permission1”]

java类主方法

	public static void main(String[] args) {
		//1、工厂解析配置文件
		Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
		//2、获取安全管理器
        SecurityManager securityManager = factory.getInstance();
		//设置安全管理器
		SecurityUtils.setSecurityManager(securityManager);
		//3、获取当前用户
		Subject currentUser = SecurityUtils.getSubject();
		//4、获取session
		Session session = currentUser.getSession();
        session.setAttribute("someKey", "aValue");
        String value = (String) session.getAttribute("someKey");
		...
		if (!currentUser.isAuthenticated()) {
			//把登录的信息封装到token中
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            token.setRememberMe(true);
            try {
			//5、登录
                currentUser.login(token);
            }catch...
		}
	
		...
		6、登出
        currentUser.logout();
	}

二、跟踪源码分析

1、解析配置文件new IniSecurityManagerFactory(“classpath:shiro.ini”)

	public IniSecurityManagerFactory(String iniResourcePath) {
        this(Ini.fromResourcePath(iniResourcePath));
    }
	public IniSecurityManagerFactory(Ini config) {
        this();
        setIni(config);
    }

解析配置文件是用了Ini类的fromResourcePath方法。

	public static Ini fromResourcePath(String resourcePath) throws ConfigurationException {
        ...
        Ini ini = new Ini();
        ini.loadFromPath(resourcePath);
        return ini;
    }
	public void loadFromPath(String resourcePath) throws ConfigurationException {
        InputStream is;
        try {
            is = ResourceUtils.getInputStreamForPath(resourcePath);
        } catch...
        load(is);
    }
	public void load(InputStream is) throws ConfigurationException {
        ...
        InputStreamReader isr;
        try {
            isr = new InputStreamReader(is, DEFAULT_CHARSET_NAME);
        } catch...
        load(isr);
    }
	public void load(Reader reader) {
        Scanner scanner = new Scanner(reader);
        try {
            load(scanner);
        } finally {
            ...
        }
    }
	public void load(Scanner scanner) {
		//当前解析的配置类别
        String sectionName = DEFAULT_SECTION_NAME;
        StringBuilder sectionContent = new StringBuilder();
        while (scanner.hasNextLine()) {
            String rawLine = scanner.nextLine();
            String line = StringUtils.clean(rawLine);
			//#号和;号开头的跳过
            if (line == null || line.startsWith(COMMENT_POUND) || line.startsWith(COMMENT_SEMICOLON)) {
                continue;
            }
            String newSectionName = getSectionName(line);
            if (newSectionName != null) {
                //解析到达了新的配置类别,所以要把缓存中的内容先解析
                addSection(sectionName, sectionContent);
                //相当于清空
                sectionContent = new StringBuilder();
                sectionName = newSectionName;//更新当前要解析的配置类别
				...
            } else {
                //添加内容当缓存
                sectionContent.append(rawLine).append("\n");
            }
        }
        //解析缓存的配置
        addSection(sectionName, sectionContent);
    }

解析配置文件是调用了Ini对象的loadFromPath方法,Ini类主要结构如下
public class Ini implements Map<String, Ini.Section> {
//装每一类配置的内容,如main、users、roles…
private final Map<String, Section> sections;
}
接下来是解析每一类配置

	private void addSection(String name, StringBuilder content) {
        if (content.length() > 0) {
            String contentString = content.toString();
            String cleaned = StringUtils.clean(contentString);
            if (cleaned != null) {
                Section section = new Section(name, contentString);
                if (!section.isEmpty()) {
                    sections.put(name, section);
                }
            }
        }
    }

解析的内容存在Section对象里,然后放进sections中缓存。Section是Ini的内部类,主要结构如下
public static class Section implements Map<String, String> {
private final String name;//配置类别:如users、roles
private final Map<String, String> props;//如:root --> 123,admin
}

2、获取安全管理器

	public T getInstance() {
        T instance;
        if (isSingleton()) {//默认是true
            if (this.singletonInstance == null) {
                this.singletonInstance = createInstance();
            }
            instance = this.singletonInstance;
        } else {
            instance = createInstance();
        }
        if (instance == null) {
            throw ...
        }
        return instance;
    }

	public T createInstance() {
		//如果没提供配置文件,shiro会尝试加载classpath:shiro.ini文件
        Ini ini = resolveIni();
        T instance;
        if (CollectionUtils.isEmpty(ini)) {
            ...
            instance = createDefaultInstance();
            if (instance == null) {
                throw ...
            }
        } else {
            instance = createInstance(ini);
            if (instance == null) {
                throw ...
            }
        }
        return instance;
    }

最后调用的是IniSecurityManagerFactory的createInstance方法来创建实例

	protected SecurityManager createInstance(Ini ini) {
        ...
        SecurityManager securityManager = createSecurityManager(ini);
        if (securityManager == null) {
            throw ...
        }
        return securityManager;
    }

	private SecurityManager createSecurityManager(Ini ini) {
        return createSecurityManager(ini, getConfigSection(ini));
    }

	private Ini.Section getConfigSection(Ini ini) {
		//获取[main]的配置
        Ini.Section mainSection = ini.getSection(MAIN_SECTION_NAME);
        if (CollectionUtils.isEmpty(mainSection)) {
            //尝试获取“”的配置
            mainSection = ini.getSection(Ini.DEFAULT_SECTION_NAME);
        }
        return mainSection;
    }

在上面解析配置文件的时候可以看到,一开始解析的配置是DEFAULT_SECTION_NAME("“空字符串),所以这里当[main]不存在的时候会尝试获取”"的配置。

接下来开始创建安全管理器

	private SecurityManager createSecurityManager(Ini ini, Ini.Section mainSection) {
		//添加对象缓存到ReflectionBuilder对象
        getReflectionBuilder().setObjects(createDefaults(ini, mainSection));
		//处理[main]配置
        Map<String, ?> objects = buildInstances(mainSection);
		//获取以SECURITY_MANAGER_NAME为key的对象
        SecurityManager securityManager = getSecurityManagerBean();
		//这里根据securityManager里是否已有Realm对象,没有就返回true
        boolean autoApplyRealms = isAutoApplyRealms(securityManager);

        if (autoApplyRealms) {
            //从缓存的bean中找出realm
            Collection<Realm> realms = getRealms(objects);
            //设进securityManager里
            if (!CollectionUtils.isEmpty(realms)) {
                applyRealmsToSecurityManager(realms, securityManager);
            }
        }
        return securityManager;
    }

这里涉及到ReflectionBuilder类,个人把它理解成是相当于spring中的容器的概念,它主要负责对bean的实例化和管理,里面还实现了事件订阅监听机制,如bean被初始化后会发布InitializedBeanEvent事件

创建默认的bean如下:

	protected Map<String, ?> createDefaults(Ini ini, Ini.Section mainSection) {
        Map<String, Object> defaults = new LinkedHashMap<String, Object>();
		//创建了一个DefaultSecurityManager
        SecurityManager securityManager = createDefaultInstance();
		//放入了map中,键为SECURITY_MANAGER_NAME
        defaults.put(SECURITY_MANAGER_NAME, securityManager);
		//如果[users]、[roles]配置不为空,就会创建一个Realm
        if (shouldImplicitlyCreateRealm(ini)) {
            Realm realm = createRealm(ini);
            if (realm != null) {//放入map中
                defaults.put(INI_REALM_NAME, realm);
            }
        }
		...
        return defaults;
    }

可以看到已经默认创建了一个DefaultSecurityManager,所以就算不配置[main]也不会报错

3、获取当前用户 SecurityUtils.getSubject()

	public static Subject getSubject() {
        Subject subject = ThreadContext.getSubject();
        if (subject == null) {//如果为空就创建
            subject = (new Subject.Builder()).buildSubject();
            ThreadContext.bind(subject);//绑定到线程
        }
        return subject;
    }

>涉及到了ThreadContext类,结构大致如下
>
	public abstract class ThreadContext {
		//用于绑定线程数据
		private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();
>
		public static Subject getSubject() {
        	return (Subject) get(SUBJECT_KEY);
    	}
>
		public static Object get(Object key) {
	        ...
	        Object value = getValue(key);
			...
	        return value;
	    }
>
		private static Object getValue(Object key) {
	        Map<Object, Object> perThreadResources = resources.get();
	        return perThreadResources != null ? perThreadResources.get(key) : null;
	    }
>
		public static void bind(Subject subject) {
	        if (subject != null) {
	            put(SUBJECT_KEY, subject);
	        }
	    }
		public static void put(Object key, Object value) {
	        if (key == null) {
	            throw ...
	        }
	        if (value == null) {
	            remove(key);
	            return;
	        }
	        ensureResourcesInitialized();
	        resources.get().put(key, value);
			...
	    }
	}

可以看到主要是用到了ThreadLocal类来绑定线程数据

创建用户(new Subject.Builder()).buildSubject():

	public Builder() {
        this(SecurityUtils.getSecurityManager());
    }

	public Builder(SecurityManager securityManager) {
        if (securityManager == null) {
            throw ...
        }
        this.securityManager = securityManager;
		//这里创建一个DefaultSubjectContext对象
        this.subjectContext = newSubjectContextInstance();
        if (this.subjectContext == null) {
            throw ...
        }
        this.subjectContext.setSecurityManager(securityManager);
    }

这里涉及到SubjectContext接口,SubjectContext装载着用户相关的信息,如host、session、token等,DefaultSubjectContext是它的一个实现类。

	public Subject buildSubject() {
        return this.securityManager.createSubject(this.subjectContext);
    }
	
	public Subject createSubject(SubjectContext subjectContext) {
        //新建一个subjectContext,并且复制原来的属性
        SubjectContext context = copy(subjectContext);
        //保证SubjectContext包含安全管理器
        context = ensureSecurityManager(context);

        //这里如果context的sessionId不为空,就会从SessionManager查询对应sessionId的Session对象,最后将其设进context里
        context = resolveSession(context);
        context = resolvePrincipals(context);
		//开始创建Subject
        Subject subject = doCreateSubject(context);
		//调用SubjectDAO的save方法
        save(subject);
        return subject;
    }
	protected Subject doCreateSubject(SubjectContext context) {
        return getSubjectFactory().createSubject(context);
    }

	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对象

4、获取当前用户的session对象 currentUser.getSession()

	public Session getSession() {
        return getSession(true);
    }

	public Session getSession(boolean create) {
        if (this.session == null && create) {
            if (!isSessionCreationEnabled()) {
                throw ...
            }
            SessionContext sessionContext = createSessionContext();
            Session session = this.securityManager.start(sessionContext);
            this.session = decorate(session);
        }
        return this.session;
    }

上面说到Subject的实际类型是DelegatingSubject类型

创建session上下文SessionContext(SessionContext包含sessionId、host信息)

	protected SessionContext createSessionContext() {
        SessionContext sessionContext = new DefaultSessionContext();
        if (StringUtils.hasText(host)) {
            sessionContext.setHost(host);
        }
        return sessionContext;
    }

创建session

	public Session start(SessionContext context) throws AuthorizationException {
        return this.sessionManager.start(context);
    }

例子中是通过SessionManager是DefaultSessionManager,其创建的session是一个SimpleSession对象

装饰session对象

	protected Session decorate(Session session) {
        ...
        return new StoppingAwareProxiedSession(session, this);
    }

StoppingAwareProxiedSession重写了Session的stop方法,主要是在session由于过期等原因被移除的时候,同时也在DelegatingSubject中移除,以防止内存泄露

5、登录currentUser.login(token)

	public void login(AuthenticationToken token) throws AuthenticationException {
		//从session中移除RUN_AS_PRINCIPALS_SESSION_KEY属性
        clearRunAsIdentitiesInternal();
		//调用安全管理器进行登录,会返回一个Subject对象
        Subject subject = securityManager.login(this, token);
        PrincipalCollection principals;
        String host = null;
        if (subject instanceof DelegatingSubject) {
            principals = delegating.principals;
            host = delegating.host;
        } else {
            principals = subject.getPrincipals();
        }

        if (principals == null || principals.isEmpty()) {
            throw ...
        }
		//更新当前的成员
        this.principals = principals;
        this.authenticated = true;
        if (token instanceof HostAuthenticationToken) {
            host = ((HostAuthenticationToken) token).getHost();
        }
        if (host != null) {
            this.host = host;
        }
		更新session
        Session session = subject.getSession(false);
        if (session != null) {
            this.session = decorate(session);
        } else {
            this.session = null;
        }
    }

可以看到主要的登录逻辑有安全管理器来实现

	public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
        AuthenticationInfo info;
        try {
            info = authenticate(token);
        } catch (AuthenticationException ae) {
            try {//回调
                onFailedLogin(token, ae, subject);
            } catch...
            throw ae; //继续往上抛
        }
		//【标记1】生成Subject
        Subject loggedIn = createSubject(token, info, subject);
		//回调
        onSuccessfulLogin(token, info, loggedIn);

        return loggedIn;
    }

这里的异常继续往上抛就可以给开发者捕获

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

	public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
        if (token == null) {
            throw ...
        }
        AuthenticationInfo info;
        try {
            info = doAuthenticate(token);
            if (info == null) {//用户不存在
                throw ...
            }
        } catch (Throwable t) {
            ...
            try {
                notifyFailure(token, ae);//通知监听者
            } catch...
            throw ae;
        }
        notifySuccess(token, info);//通知监听者

        return info;
    }

	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方法

	protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
        if (!realm.supports(token)) {
            throw ...
        }
        AuthenticationInfo info = realm.getAuthenticationInfo(token);
        if (info == null) {
            throw ...
        }
        return info;
    }

最后是调用了realm的getAuthenticationInfo来完成验证

	public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		//尝试从缓存中获取
        AuthenticationInfo info = getCachedAuthenticationInfo(token);
        if (info == null) {
            //缓存获取失败,则通过其他方式获取,通常是查数据库
            info = doGetAuthenticationInfo(token);
            if (token != null && info != null) {
				//根据是否需要缓存来进行缓存
                cacheAuthenticationInfoIfPossible(token, info);
            }
        }
        if (info != null) {//开始比对验证
            assertCredentialsMatch(token, info);
        } else {
            //用户不存在
        }
        return info;
    }

上面可以看到通过doGetAuthenticationInfo来获取用户信息,这也是我们平时自己实现realm要实现的方法。

比对验证

	protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
		//使用验证器进行验证
        CredentialsMatcher cm = getCredentialsMatcher();
        if (cm != null) {
            if (!cm.doCredentialsMatch(token, info)) {
                //验证失败
                throw new IncorrectCredentialsException(msg);
            }
        } else {
			//不存在验证器
            throw ...
        }
    }

利用验证器来进行验证

【标记1】如果都验证通过了,就会创建一个Subject返回

	protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) {
		//创建了一个DefaultSubjectContext
        SubjectContext context = createSubjectContext();
        context.setAuthenticated(true);//设置已经认证
        context.setAuthenticationToken(token);
        context.setAuthenticationInfo(info);
        if (existing != null) {
            context.setSubject(existing);
        }
        return createSubject(context);
    }

这里的SubjectContext会把认证的标记为设置成true,然后就是创建Subject,这个上面已经分析过,最后会返回一个DelegatingSubject。安全管理器登录逻辑就完成了。

接下里就是对安全管理器登录逻辑返回的Subject信息进行复制到当前subject

		PrincipalCollection principals;
        String host = null;
        if (subject instanceof DelegatingSubject) {
            DelegatingSubject delegating = (DelegatingSubject) subject;
            host = delegating.host;
        } else {
            principals = subject.getPrincipals();
        }

        if (principals == null || principals.isEmpty()) {
            throw ...
        }
        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);//装饰session
        } else {
            this.session = null;
        }

6、当前用户登出currentUser.logout()

	public void logout() {
        try {
			//清楚session中的RUN_AS_PRINCIPALS_SESSION_KEY属性
            clearRunAsIdentitiesInternal();
            this.securityManager.logout(this);
        } finally {//清空信息
            this.session = null;
            this.principals = null;
            this.authenticated = false;
        }
    }

	public void logout(Subject subject) {

        if (subject == null) {
            throw ...
        }

        beforeLogout(subject);//删除RememberMe管理器中的相关信息

        PrincipalCollection principals = subject.getPrincipals();
        if (principals != null && !principals.isEmpty()) {
            Authenticator authc = getAuthenticator();
            if (authc instanceof LogoutAware) {
				//这里可以清理缓存和通知监听器
                ((LogoutAware) authc).onLogout(principals);
            }
        }

        try {
            delete(subject);//这里调用subjectDAO的delete方法
        } catch (Exception e) {
            if (log.isDebugEnabled()) {
                String msg = "Unable to cleanly unbind Subject.  Ignoring (logging out).";
                log.debug(msg, e);
            }
        } finally {
            try {
                stopSession(subject);//设置session过期
            } catch (Exception e) {
                if (log.isDebugEnabled()) {
                    String msg = "Unable to cleanly stop Session for Subject [" + subject.getPrincipal() + "] " +
                            "Ignoring (logging out).";
                    log.debug(msg, e);
                }
            }
        }
    }

总结:

shiro框架可以说是化繁为简,各种组件各司其职,分工明确,值得深入学习。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值