Shiro登录的使用以及原理(上)

 

    好久没写博客了,这段时间对最近项目做个总结,先从登入下手,话不多说直奔主题,Shiro的登录使用以及原理。

目录

一、Shiro主要作用

二、登录的使用

2.1 SecurityManager的生成与使用

2.1 SecurityUtils的作用以及使用

三 ThreadLocal小插曲


一、Shiro主要作用

    shiro主要的作用就是实现用户登录(认证,授权,加密等),用户登录后的用户信息存储(缓存),用户登出等。

二、登录的使用

    在使用登录的时候,最常见的一串代码就是通过工具类SecurityUtils获取Subject,然后对Token进行login();

// 得到subject然后对创建用户名/密码身份验证
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("hu", "123");
subject.login(token);

   这时候只对这串代码进行编译运行,你会发现会报一个异常信息

org.apache.shiro.UnavailableSecurityManagerException: No SecurityManager accessible to the calling code, either bound to the org.apache.shiro.util.ThreadContext or as a vm static singleton.  This is an invalid application configuration.

2.1 SecurityManager的生成与使用

根据报错信息以及对SecuriTyUtils的源码发现使用SecurityUtils.getSubject()的时候必须要为其设置一个securityManager,具体如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.apache.shiro;

import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.Subject.Builder;
import org.apache.shiro.util.ThreadContext;

public abstract class SecurityUtils {
    private static SecurityManager securityManager;

    public SecurityUtils() {
    }

    public static Subject getSubject() {
        // 通过ThreadContext获取对应的Subject,若未在ThredContext中加入该subject必定为空
        // ThreadContext可以通过源码了解到为使用过TreadLocal模式 具体看标题三
        //因为是TreadLocal所以表示每个线程初次进来的时候,获取到的subeject必为空
        Subject subject = ThreadContext.getSubject();
        if (subject == null) {
            //具体看下列代码块 主要执行为通过SecurityManager创建出Subject
            subject = (new Builder()).buildSubject();
            ThreadContext.bind(subject);
        }

        return subject;
    }

    public static void setSecurityManager(SecurityManager securityManager) {
        SecurityUtils.securityManager = securityManager;
    }

    public static SecurityManager getSecurityManager() throws UnavailableSecurityManagerException {
        SecurityManager securityManager = ThreadContext.getSecurityManager();
        if (securityManager == null) {
            securityManager = SecurityUtils.securityManager;
        }

        if (securityManager == null) {
            String msg = "No SecurityManager accessible to the calling code, either bound to the " + ThreadContext.class.getName() + " or as a vm static singleton.  This is an invalid application " + "configuration.";
            throw new UnavailableSecurityManagerException(msg);
        } else {
            return securityManager;
        }
    }
}
   // 在Subjct初次获取到为空的时候会调用的Subject的静态内部类,创建一个Builder,在通过buildSubject的方法进行实现Subject的生成 
   public static class Builder {
        private final SubjectContext subjectContext;
        private final SecurityManager securityManager;
        // 需要先设置对应的SecurityManage
        public Builder() {
            this(SecurityUtils.getSecurityManager());
        }
        // 通过securityManager创建出subject
        public Subject buildSubject() {
            return this.securityManager.createSubject(this.subjectContext);
        }
    ..........loading...............
    }

得出结论 Subject的实例都会(也是必须)绑定一个SecurityManager,对Subject的操作会转为Subject与SecurityManager之间的交互。

看来Subject的生成都是SecurityManager在做苦力活啊。

那么SecurityManager是怎么生成的?

先查阅下官方文档SecurityManager是怎么生成的

根据官方文档:http://greycode.github.io/shiro/doc/tutorial.html

Shiro登录的使用以及原理(上)

那么我们就先通过使用ini文件进行尝试下:

在resource下创建一个shiro.ini文件放入用户信息

[users]
hu=123

Shiro登录的使用以及原理(上)

然后通过官方给出的:

        //1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager
        Factory<SecurityManager> factory =
                new IniSecurityManagerFactory("classpath:shiro.ini");
        //2、得到SecurityManager实例 并绑定给SecurityUtils
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);

这么一个案例又让我们想起了那个Spring等源码中最喜欢用的工厂模式,看看这里的Factory接口啥样:

package org.apache.shiro.util;

public interface Factory<T> {
    T getInstance();
}

就是一个用来生成T对应实例的工厂,一个很一般的代码,噢,我的意思是,我的上帝啊,这真是一个写的很棒的代码,一个很完美的工厂。

那么看看IniSecurityManagerFactory干了啥?

看了代码构造函数就做了这些小事,就是将Ini对象赋值下

    // 调用了对应的构造函数,将iniResourcePath路径读取为代码可识别的Ini对象
    public IniSecurityManagerFactory(String iniResourcePath) {
        this(Ini.fromResourcePath(iniResourcePath));
    }

    // Ini对象具体就是根据文件路径读取对应的文件内的信息流   
    // 然后调用对应Ini的构造函数
    public IniSecurityManagerFactory(Ini config) {
        this.setIni(config);
    }
    

那么就是主要是在getInstance的方法中咯?通过对IniSecurityManagerFactory类中查看并未发现getInstance

那么就可以确定在了子类中的实现(运用了模板设计模式的实现)

public class IniSecurityManagerFactory extends IniFactorySupport<SecurityManager>
public abstract class IniFactorySupport<T> extends AbstractFactory<T>

看看AbstractFactory的源码吧,这里的单例对象为啥不用static?(个人理解,因为Factory是只会创建出一次该实现的bean对象,所以只会一个Factory那么就没必要考虑有多个单例对象,就没必要用static修饰,而且还能用非单例模式进行每次创建对象,如果理解有误希望能帮忙改正)

public abstract class AbstractFactory<T> implements Factory<T> {
    // 是否使用单例
    private boolean singleton = true;
    private T singletonInstance;

    public AbstractFactory() {
    }

    public boolean isSingleton() {
        return this.singleton;
    }

    public void setSingleton(boolean singleton) {
        this.singleton = singleton;
    }

    public T getInstance() {
        Object instance;
        // 如果为单例模式则只用创建一次对象 懒汉式
        if (this.isSingleton()) {
            if (this.singletonInstance == null) {
                this.singletonInstance = this.createInstance();
            }

            instance = this.singletonInstance;
        } else {
            instance = this.createInstance();
        }

        if (instance == null) {
            String msg = "Factory 'createInstance' implementation returned a null object.";
            throw new IllegalStateException(msg);
        } else {
            return instance;
        }
    }

    protected abstract T createInstance();
}

具体的实现方法又回到了对应的父类(模板设计模式)

// 先调用了抽象类IniFactorySupport的实现方法
    public T createInstance() {
        Ini ini = this.resolveIni();
        Object instance;
        String msg;
        if (CollectionUtils.isEmpty(ini)) {
            log.debug("No populated Ini available.  Creating a default instance.");
            instance = this.createDefaultInstance();
            if (instance == null) {
                msg = this.getClass().getName() + " implementation did not return a default instance in " + "the event of a null/empty Ini configuration.  This is required to support the " + "Factory interface.  Please check your implementation.";
                throw new IllegalStateException(msg);
            }
        } else {
            log.debug("Creating instance from Ini [" + ini + "]");
            instance = this.createInstance(ini);
            if (instance == null) {
                msg = this.getClass().getName() + " implementation did not return a constructed instance from " + "the createInstance(Ini) method implementation.";
                throw new IllegalStateException(msg);
            }
        }

        return instance;
    }
    //若为空则调用了IniSecurityManagerFactory中创建默认SecurityManager方法 默认的详细就跳过了
protected SecurityManager createDefaultInstance() {
        return new DefaultSecurityManager();
    }


// 非空则调用了IniSecurityManagerFactory中的方法
    protected SecurityManager createInstance(Ini ini) {
        if (CollectionUtils.isEmpty(ini)) {
            throw new NullPointerException("Ini argument cannot be null or empty.");
        } else {
            SecurityManager securityManager = this.createSecurityManager(ini);
            if (securityManager == null) {
                String msg = SecurityManager.class + " instance cannot be null.";
                throw new ConfigurationException(msg);
            } else {
                return securityManager;
            }
        }
    }
//createSecurityManager方法 此处的Section就是将文件内容封装成的对象(其中sectionName就是通过s.startsWith("[") && s.endsWith("]")进行判断) 即表示我们文件中users,然后将内容放入到对应的的映射中,根据分割号分给为map映射(如key为hu,value对应为123)
    private SecurityManager createSecurityManager(Ini ini) {
        // 因为我们文件中为加入main所以mainSection为null mainStion的具体作用暂不了解
        Section mainSection = ini.getSection("main");
        if (CollectionUtils.isEmpty(mainSection)) {
            mainSection = ini.getSection("");
        }

        return this.createSecurityManager(ini, mainSection);
    }

//继续调用 主要作用就是生成SecurityManager然后根据是否autoApplyRealms为其加上对应的realm
    private SecurityManager createSecurityManager(Ini ini, Section mainSection) {
        Map<String, ?> defaults = this.createDefaults(ini, mainSection);
        Map<String, ?> objects = this.buildInstances(mainSection, defaults);
        SecurityManager securityManager = this.getSecurityManagerBean();
        boolean autoApplyRealms = this.isAutoApplyRealms(securityManager);
        if (autoApplyRealms) {
            Collection<Realm> realms = this.getRealms(objects);
            if (!CollectionUtils.isEmpty(realms)) {
                this.applyRealmsToSecurityManager(realms, securityManager);
            }
        }

        return securityManager;
    }

//createDefaults的方法实现
    protected Map<String, ?> createDefaults(Ini ini, Section mainSection) {
        Map<String, Object> defaults = new LinkedHashMap();
        //生成默认的SecurityManager
        SecurityManager securityManager = this.createDefaultInstance();
        defaults.put("securityManager", securityManager);
        //判断是否需要生成对应的realm 主要用来后面的用户验证
        // 文件中是根据[roles]或是[users]标签来确定的 ,创建对应的IniRealm,users主要用于登录,roles用与权限的校验
        if (this.shouldImplicitlyCreateRealm(ini)) {
            Realm realm = this.createRealm(ini);
            if (realm != null) {
                defaults.put("iniRealm", realm);
            }
        }

        return defaults;
    }

上面代码开完对应的SecurityManager(接口)对应父类的具体实例也就生成了,这里用的是DefaultSecurityManager,看看DefaultSecurityManager里具体有哪些参数,基本后面的web等地方调用的SecurityManager都是继承了DefaultSecurityManager而实现的

public class DefaultSecurityManager extends SessionsSecurityManager {
    protected RememberMeManager rememberMeManager;
    protected SubjectDAO subjectDAO;
    protected SubjectFactory subjectFactory;
    ....
}
// session的管理
public abstract class SessionsSecurityManager extends AuthorizingSecurityManager {
    private SessionManager sessionManager = new DefaultSessionManager();
    ....
}
//用户认证 实际调用Subject登录就是用对应的SecurityManager进行登录认证
public abstract class AuthorizingSecurityManager extends AuthenticatingSecurityManager {
    private Authorizer authorizer = new ModularRealmAuthorizer();
    ....
}
public abstract class AuthenticatingSecurityManager extends RealmSecurityManager {
    private Authenticator authenticator = new ModularRealmAuthenticator();
}
// 用户认证具体的realm
public abstract class RealmSecurityManager extends CachingSecurityManager {
    private Collection<Realm> realms;
}
// 缓存相关
public abstract class CachingSecurityManager implements SecurityManager, Destroyable, CacheManagerAware {
    private CacheManager cacheManager;
}

ok,继续回到Subject的登录,SecurityManager的大致生成和作用粗略的进行了描述,接下来就是Subject登录的使用

2.2 Subject 的登录

SecurityManager生成后,进行研究上面的SecurityUtils.getSubject();的方法。

// 根据此处可以看出运用了ThreadContext的方法而该方法对其源码可以看出为InheritableThreadLocal的使用,如果说ThreadLocal的作用大家都基本了解,用于为当前线程创建一个局部线程变量,只能在该线程中使用,那么InheritableThreadLocal也类似,具体可以查看目录三
    public static Subject getSubject() {
        // 线程初次使用必定为空,则需要对其进行ThreadLocal的set,此处防止内存泄漏在bind的时候对空key的value进行了remove
        Subject subject = ThreadContext.getSubject();
        if (subject == null) {
            subject = (new Builder()).buildSubject();
            ThreadContext.bind(subject);
        }

        return subject;
    }
// new Builde()的过程
        public Builder() {
            this(SecurityUtils.getSecurityManager());
        }

        public Builder(SecurityManager securityManager) {
            if (securityManager == null) {
                throw new NullPointerException("SecurityManager method argument cannot be null.");
            } else {
                // 指定securityManager并生成subjectContext
                this.securityManager = securityManager;
                this.subjectContext = this.newSubjectContextInstance();
                if (this.subjectContext == null) {
                    throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' cannot be null.");
                } else {
                    this.subjectContext.setSecurityManager(securityManager);
                }
            }
        }


//根据securityManger和subjectContext生成对应的subject(DelegatingSubject或是WebDelegatingSubject)
WebDelegatingSubject继承了DelegatingSubject 多了ServletRequest和ServletResponse

    public Subject createSubject(SubjectContext subjectContext) {
        SubjectContext context = this.copy(subjectContext);
        //确保securityManager非空 
        context = this.ensureSecurityManager(context);
        //获取对应的session 
        context = this.resolveSession(context);
        //获取对应的PrincipalCollection
        context = this.resolvePrincipals(context);
        //根据subjectFactory生成subject
        Subject subject = this.doCreateSubject(context);
        //根据subjectDAO保存对应的subject  
        this.save(subject);
        return subject;
    }

获取到了subject后就是login的具体实现

    public void login(AuthenticationToken token) throws AuthenticationException {
        this.clearRunAsIdentitiesInternal();
        // 实际为对应的securityManager进行login
        Subject subject = this.securityManager.login(this, token);
        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);
        }
    }

// 实际为调用securityManager的login方法
    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生成  这一步有大量信息报错,如session如下2.3
        Subject loggedIn = this.createSubject(token, info, subject);
        // 更新各种信息,如session在数据库中的缓存
        this.onSuccessfulLogin(token, info, loggedIn);
        return loggedIn;
    }
// 通过securityManager的authenticate验证,实际调用Authenticator的authenticate方法
    public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
        return this.authenticator.authenticate(token);
    }

//AbstractAuthenticator的authenticate
    public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
        if (token == null) {
            throw new IllegalArgumentException("Method argumet (authentication token) cannot be null.");
        } else {
            log.trace("Authentication attempt received for token [{}]", token);

            AuthenticationInfo info;
            try {
                info = this.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 var8) {
                AuthenticationException ae = null;
                if (var8 instanceof AuthenticationException) {
                    ae = (AuthenticationException)var8;
                }

                if (ae == null) {
                    String msg = "Authentication failed for token submission [" + token + "].  Possible unexpected " + "error? (Typical or expected login exceptions should extend from AuthenticationException).";
                    ae = new AuthenticationException(msg, var8);
                }

                try {
                    this.notifyFailure(token, ae);
                } catch (Throwable var7) {
                    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, var7);
                    }
                }

                throw ae;
            }

            log.debug("Authentication successful for token [{}].  Returned account [{}]", token, info);
            this.notifySuccess(token, info);
            return info;
        }
    }

// 进行doAuthenticate
    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        this.assertRealmsConfigured();
        Collection<Realm> realms = this.getRealms();
        return realms.size() == 1 ? this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken) : this.doMultiRealmAuthentication(realms, authenticationToken);
    }

// 根据对应的realms进行判断是单一realm验证还是多个realm验证 以单一为例子,实际为根据对应的realm进行了验证,所以自己使用指定的securityManager时候,可以通过指定自己的realm进行身份验证
    protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
        if (!realm.supports(token)) {
            String msg = "Realm [" + realm + "] does not support authentication token [" + token + "].  Please ensure that the appropriate Realm implementation is " + "configured correctly or that the realm accepts AuthenticationTokens of this type.";
            throw new UnsupportedTokenException(msg);
        } else {
            AuthenticationInfo info = realm.getAuthenticationInfo(token);
            if (info == null) {
                String msg = "Realm [" + realm + "] was unable to find account data for the " + "submitted AuthenticationToken [" + token + "].";
                throw new UnknownAccountException(msg);
            } else {
                return info;
            }
        }
    }

 

2.3 登录线程结束后同一个sessionId的线程进入,获取对应sessionId对应的session

登录时候的session的生成,登录之前一般是没有session的,只有登录只会才会有对应的session生成,登录完成后会调用session生成的代码如下:

//这一段在登录完成之后
Subject loggedIn = this.createSubject(token, info, subject);
//执行
return this.createSubject(context);
//执行
this.save(subject);
//执行
    protected void save(Subject subject) {
        this.subjectDAO.save(subject);
    }
//继续执行 此处会根据是否保存session而保持session
    public Subject save(Subject subject) {
        if (this.isSessionStorageEnabled(subject)) {
            this.saveToSession(subject);
        } else {
            log.trace("Session storage of subject state for Subject [{}] has been disabled: identity and authentication state are expected to be initialized on every request or invocation.", subject);
        }

        return subject;
    }
// 主要进行了两个步骤
    protected void saveToSession(Subject subject) {
        this.mergePrincipals(subject);
        this.mergeAuthenticationState(subject);
    }


//下列这串中进行了session为空则生成并保持
    protected void mergePrincipals(Subject subject) {
        PrincipalCollection currentPrincipals = null;
        if (subject.isRunAs() && subject instanceof DelegatingSubject) {
            try {
                Field field = DelegatingSubject.class.getDeclaredField("principals");
                field.setAccessible(true);
                currentPrincipals = (PrincipalCollection)field.get(subject);
            } catch (Exception var5) {
                throw new IllegalStateException("Unable to access DelegatingSubject principals property.", var5);
            }
        }

        if (currentPrincipals == null || currentPrincipals.isEmpty()) {
            currentPrincipals = subject.getPrincipals();
        }

        Session session = subject.getSession(false);
        // session为空且登录完成则生成并保存
        if (session == null) {
            if (!CollectionUtils.isEmpty(currentPrincipals)) {
                session = subject.getSession();
                session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);
            }
        } else {
            PrincipalCollection existingPrincipals = (PrincipalCollection)session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
            if (CollectionUtils.isEmpty(currentPrincipals)) {
                if (!CollectionUtils.isEmpty(existingPrincipals)) {
                    session.removeAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
                }
            } else if (!currentPrincipals.equals(existingPrincipals)) {
                session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);
            }
        }

    }

因为每个web请求都是一个新的线程,所以需要先对线程进行绑定对应的subject,每次请求的时候都会调用到

 

org.apache.shiro.web.servlet.AbstractShiroFilter#doFilterInternal中调用Subject subject = this.createSubject(request, response);

public Subject createSubject(SubjectContext subjectContext) {  
    SubjectContext context = this.copy(subjectContext);  
    context = this.ensureSecurityManager(context);  
// 主要对seesion进行查找和处理
    context = this.resolveSession(context);  
    context = this.resolvePrincipals(context);  
    Subject subject = this.doCreateSubject(context);  
    this.save(subject);  
    return subject;  
}
    protected SubjectContext resolveSession(SubjectContext context) {
        if (context.resolveSession() != null) {
            log.debug("Context already contains a session.  Returning.");
            return context;
        } else {
            try {
                Session session = this.resolveContextSession(context);
                if (session != null) {
                    context.setSession(session);
                }
            } catch (InvalidSessionException var3) {
                log.debug("Resolved SubjectContext context session is invalid.  Ignoring and creating an anonymous (session-less) Subject instance.", var3);
            }

            return context;
        }
    }

       //根据key获取session

    protected Session resolveContextSession(SubjectContext context) throws InvalidSessionException {
        SessionKey key = this.getSessionKey(context);
        return key != null ? this.getSession(key) : null;
    }

    public Session getSession(SessionKey key) throws SessionException {
        return this.sessionManager.getSession(key);
    }
// 查询对应的session
    public Session getSession(SessionKey key) throws SessionException {
        Session session = this.lookupSession(key);
        return session != null ? this.createExposedSession(session, key) : null;
    }

    private Session lookupSession(SessionKey key) throws SessionException {
        if (key == null) {
            throw new NullPointerException("SessionKey argument cannot be null.");
        } else {
            return this.doGetSession(key);
        }
    }

    protected final Session doGetSession(SessionKey key) throws InvalidSessionException {
        this.enableSessionValidationIfNecessary();
        log.trace("Attempting to retrieve session with key {}", key);
        Session s = this.retrieveSession(key);
        if (s != null) {
            this.validate(s, key);
        }

        return s;
    }

// 通过这里可以看出 重写sessionManager的getSessionId方法来找出不同传值
    protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
        Serializable sessionId = this.getSessionId(sessionKey);
        if (sessionId == null) {
            log.debug("Unable to resolve session ID from SessionKey [{}].  Returning null to indicate a session could not be found.", sessionKey);
            return null;
        } else {
            Session s = this.retrieveSessionFromDataSource(sessionId);
            if (s == null) {
                String msg = "Could not find session with ID [" + sessionId + "]";
                throw new UnknownSessionException(msg);
            } else {
                return s;
            }
        }
    }
//再返回
boolean authenticated = wsc.resolveAuthenticated();中看出由session中的信息来判断是否登录
    public boolean resolveAuthenticated() {
        Boolean authc = (Boolean)this.getTypedValue(AUTHENTICATED, Boolean.class);
        if (authc == null) {
            AuthenticationInfo info = this.getAuthenticationInfo();
            authc = info != null;
        }

        if (!authc) {
            Session session = this.resolveSession();
            if (session != null) {
                Boolean sessionAuthc = (Boolean)session.getAttribute(AUTHENTICATED_SESSION_KEY);
                authc = sessionAuthc != null && sessionAuthc;
            }
        }

        return authc;
    }

 

三 ThreadLocal小插曲

ThreadLoacl类似于一个工具类,可以理解为在当前线程中设置对应的一个局部变量,这个变量可以起到上下文等操作,具体可以看另一个篇章。

 

 

上一篇:public/private/protected的具体区别


下一篇:剑指offer Java题解之JZ73 翻转单词序列