shiro源码分析(二)Subject和Session

Shiro源码分析之认证过程   在前面分析的基础上我们来继续看下认证的过程 Shiro源码分析之SecurityManager对象获取 Shiro认证过程的源码 认证过程的时序图 源码跟踪 1.DelegatingSubject类中的login 方法 public void login(AuthenticationToken token) throws AuthenticationException { clear... 阅读详情
继续上一篇文章的案例,第一次使用SecurityUtils.getSubject()来获取Subject时 
Java代码  收藏代码
  1. public static Subject getSubject() {  
  2.         Subject subject = ThreadContext.getSubject();  
  3.         if (subject == null) {  
  4.             subject = (new Subject.Builder()).buildSubject();  
  5.             ThreadContext.bind(subject);  
  6.         }  
  7.         return subject;  
  8.     }  
  
使用ThreadLocal模式来获取,若没有则创建一个并绑定到当前线程。此时创建使用的是Subject内部类Builder来创建的,Builder会创建一个SubjectContext接口的实例DefaultSubjectContext,最终会委托securityManager来根据SubjectContext信息来创建一个Subject,下面详细说下该过程,在DefaultSecurityManager的createSubject方法中: 
Java代码  收藏代码
  1. public Subject createSubject(SubjectContext subjectContext) {  
  2.         SubjectContext context = copy(subjectContext);  
  3.   
  4.         context = ensureSecurityManager(context);  
  5.   
  6.         context = resolveSession(context);  
  7.   
  8.         context = resolvePrincipals(context);  
  9.   
  10.         Subject subject = doCreateSubject(context);  
  11.   
  12.         save(subject);  
  13.   
  14.         return subject;  
  15.     }  

首先就是复制SubjectContext,SubjectContext 接口继承了Map<String, Object>,然后加入了几个重要的SecurityManager、SessionId、Subject、PrincipalCollection、Session、boolean authenticated、boolean sessionCreationEnabled、Host、AuthenticationToken、AuthenticationInfo等众多信息。 

然后来讨论下接口设计: 

 

讨论1:首先是SubjectContext为什么要去实现Map<String, Object>? 
SubjectContext提供了常用的get、set方法,还提供了一个resolve方法,以SecurityManager为例:
 
Java代码  收藏代码
  1. SecurityManager getSecurityManager();  
  2.   
  3.  void setSecurityManager(SecurityManager securityManager);  
  4.   
  5.  SecurityManager resolveSecurityManager();  

这些get、set方法则用于常用的设置和获取,而resolve则表示先调用getSecurityManager,如果获取不到,则使用其他途径来获取,如DefaultSubjectContext的实现: 
Java代码  收藏代码
  1. public SecurityManager resolveSecurityManager() {  
  2.         SecurityManager securityManager = getSecurityManager();  
  3.         if (securityManager == null) {  
  4.             if (log.isDebugEnabled()) {  
  5.                 log.debug("No SecurityManager available in subject context map.  " +  
  6.                         "Falling back to SecurityUtils.getSecurityManager() lookup.");  
  7.             }  
  8.             try {  
  9.                 securityManager = SecurityUtils.getSecurityManager();  
  10.             } catch (UnavailableSecurityManagerException e) {  
  11.                 if (log.isDebugEnabled()) {  
  12.                     log.debug("No SecurityManager available via SecurityUtils.  Heuristics exhausted.", e);  
  13.                 }  
  14.             }  
  15.         }  
  16.         return securityManager;  
  17.     }  

如果getSecurityManager获取不到,则使用SecurityUtils工具来获取。 
再如resolvePrincipals
 
Java代码  收藏代码
  1. public PrincipalCollection resolvePrincipals() {  
  2.         PrincipalCollection principals = getPrincipals();  
  3.   
  4.         if (CollectionUtils.isEmpty(principals)) {  
  5.             //check to see if they were just authenticated:  
  6.             AuthenticationInfo info = getAuthenticationInfo();  
  7.             if (info != null) {  
  8.                 principals = info.getPrincipals();  
  9.             }  
  10.         }  
  11.   
  12.         if (CollectionUtils.isEmpty(principals)) {  
  13.             Subject subject = getSubject();  
  14.             if (subject != null) {  
  15.                 principals = subject.getPrincipals();  
  16.             }  
  17.         }  
  18.   
  19.         if (CollectionUtils.isEmpty(principals)) {  
  20.             //try the session:  
  21.             Session session = resolveSession();  
  22.             if (session != null) {  
  23.                 principals = (PrincipalCollection) session.getAttribute(PRINCIPALS_SESSION_KEY);  
  24.             }  
  25.         }  
  26.   
  27.         return principals;  
  28.     }  

普通的getPrincipals()获取不到,尝试使用其他属性来获取。 
讨论2:此时就有一个问题,有必要再对外公开getPrincipals方法吗?什么情况下外界会去调用getPrincipals方法而不会去调用resolvePrincipals方法? 

然后我们继续回到上面的类图设计上: 
DefaultSubjectContext继承了MapContext,MapContext又实现了Map<String, Object>,看下此时的MapContext有什么东西:
 
Java代码  收藏代码
  1. public class MapContext implements Map<String, Object>, Serializable {  
  2.   
  3.     private static final long serialVersionUID = 5373399119017820322L;  
  4.   
  5.     private final Map<String, Object> backingMap;  
  6.   
  7.     public MapContext() {  
  8.         this.backingMap = new HashMap<String, Object>();  
  9.     }  
  10.   
  11.     public MapContext(Map<String, Object> map) {  
  12.         this();  
  13.         if (!CollectionUtils.isEmpty(map)) {  
  14.             this.backingMap.putAll(map);  
  15.         }  
  16.     }  
  17.   //略  
  18. }  

MapContext内部拥有一个类型为HashMap的backingMap属性,大部分方法都由HashMap来实现,然后仅仅更改某些行为,MapContext没有选择去继承HashMap,而是使用了组合的方式,更加容易去扩展,如backingMap的类型不一定非要选择HashMap,可以换成其他的Map实现,一旦MapContext选择继承HashMap,如果想对其他的Map类型进行同样的功能增强的话,就需要另写一个类来继承它然后改变一些方法实现,这样的话就会有很多重复代码。这也是设计模式所强调的少用继承多用组合。但是MapContext的写法使得子类没法去替换HashMap,哎,心塞 。 
MapContext又提供了如下几个返回值不可修改的方法:
 
Java代码  收藏代码
  1. public Set<String> keySet() {  
  2.         return Collections.unmodifiableSet(backingMap.keySet());  
  3.     }  
  4.   
  5.     public Collection<Object> values() {  
  6.         return Collections.unmodifiableCollection(backingMap.values());  
  7.     }  
  8.   
  9.     public Set<Entry<String, Object>> entrySet() {  
  10.         return Collections.unmodifiableSet(backingMap.entrySet());  
  11.     }  

有点扯远了。继续回到DefaultSecurityManager创建Subject的地方: 
Java代码  收藏代码
  1. public Subject createSubject(SubjectContext subjectContext) {  
  2.         //create a copy so we don't modify the argument's backing map:  
  3.         SubjectContext context = copy(subjectContext);  
  4.   
  5.         //ensure that the context has a SecurityManager instance, and if not, add one:  
  6.         context = ensureSecurityManager(context);  
  7.   
  8.         //Resolve an associated Session (usually based on a referenced session ID), and place it in the context before  
  9.         //sending to the SubjectFactory.  The SubjectFactory should not need to know how to acquire sessions as the  
  10.         //process is often environment specific - better to shield the SF from these details:  
  11.         context = resolveSession(context);  
  12.   
  13.         //Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first  
  14.         //if possible before handing off to the SubjectFactory:  
  15.         context = resolvePrincipals(context);  
  16.   
  17.         Subject subject = doCreateSubject(context);  
  18.   
  19.         //save this subject for future reference if necessary:  
  20.         //(this is needed here in case rememberMe principals were resolved and they need to be stored in the  
  21.         //session, so we don't constantly rehydrate the rememberMe PrincipalCollection on every operation).  
  22.         //Added in 1.2:  
  23.         save(subject);  
  24.   
  25.         return subject;  
  26.     }  

对于context,把能获取到的参数都凑齐,SecurityManager、Session。resolveSession尝试获取context的map中获取Session,若没有则尝试获取context的map中的Subject,如果存在的话,根据此Subject来获取Session,若没有再尝试获取sessionId,若果有了sessionId则构建成一个DefaultSessionKey来获取对应的Session。 
整个过程如下;
 
Java代码  收藏代码
  1. protected SubjectContext resolveSession(SubjectContext context) {  
  2.         if (context.resolveSession() != null) {  
  3.             log.debug("Context already contains a session.  Returning.");  
  4.             return context;  
  5.         }  
  6.         try {  
  7.             //Context couldn't resolve it directly, let's see if we can since we have direct access to   
  8.             //the session manager:  
  9.             Session session = resolveContextSession(context);  
  10.             if (session != null) {  
  11.                 context.setSession(session);  
  12.             }  
  13.         } catch (InvalidSessionException e) {  
  14.             log.debug("Resolved SubjectContext context session is invalid.  Ignoring and creating an anonymous " +  
  15.                     "(session-less) Subject instance.", e);  
  16.         }  
  17.         return context;  
  18.     }  

先看下context.resolveSession(): 
Java代码  收藏代码
  1. public Session resolveSession() {  
  2.   //这里则是直接从map中取出Session  
  3.         Session session = getSession();  
  4.         if (session == null) {  
  5.             //try the Subject if it exists:  
  6.            //若果没有,尝试从map中取出Subject   
  7.             Subject existingSubject = getSubject();  
  8.             if (existingSubject != null) {  
  9.                 //这里就是Subject获取session的方法,需要详细看下  
  10.                 session = existingSubject.getSession(false);  
  11.             }  
  12.         }  
  13.         return session;  
  14.     }  

existingSubject.getSession(false):通过Subject获取Session如下 
Java代码  收藏代码
  1. public Session getSession(boolean create) {  
  2.         if (log.isTraceEnabled()) {  
  3.             log.trace("attempting to get session; create = " + create +  
  4.                     "; session is null = " + (this.session == null) +  
  5.                     "; session has id = " + (this.session != null && session.getId() != null));  
  6.         }  
  7.   
  8.         if (this.session == null && create) {  
  9.   
  10.             //added in 1.2:  
  11.             if (!isSessionCreationEnabled()) {  
  12.                 String msg = "Session creation has been disabled for the current subject.  This exception indicates " +  
  13.                         "that there is either a programming error (using a session when it should never be " +  
  14.                         "used) or that Shiro's configuration needs to be adjusted to allow Sessions to be created " +  
  15.                         "for the current Subject.  See the " + DisabledSessionException.class.getName() + " JavaDoc " +  
  16.                         "for more.";  
  17.                 throw new DisabledSessionException(msg);  
  18.             }  
  19.   
  20.             log.trace("Starting session for host {}", getHost());  
  21.             SessionContext sessionContext = createSessionContext();  
  22.             Session session = this.securityManager.start(sessionContext);  
  23.             this.session = decorate(session);  
  24.         }  
  25.         return this.session;  
  26.     }  

getSession()的参数表示是否创建session,如果Session为空,并且传递的参数为true,则会创建一个Session。然而这里传递的是false,也就是说不会在创建Subject的时候来创建Session,所以把创建Session过程说完后,再回到此处是要记着不会去创建一个Session。但是我们可以来看下是如何创建Session的,整体三大步骤,先创建一个SessionContext ,然后根据SessionContext 来创建Session,最后是装饰Session,由于创建Session过程内容比较多,先说说装饰Session。 
Java代码  收藏代码
  1. protected Session decorate(Session session) {  
  2.         if (session == null) {  
  3.             throw new IllegalArgumentException("session cannot be null");  
  4.         }  
  5.         return new StoppingAwareProxiedSession(session, this);  
  6.     }  

装饰Session就是讲Session和DelegatingSubject封装起来。 
然后来说Session的创建过程,这和Subject的创建方式差不多。 
同样是SessionContext的接口设计:
 

 
和SubjectContext相当雷同。 
看下SessionContext的主要内容:
 
Java代码  收藏代码
  1. void setHost(String host);  
  2.     String getHost();  
  3.   
  4.     Serializable getSessionId();  
  5.   
  6.     void setSessionId(Serializable sessionId);  

主要两个内容,host和sessionId。 
接下来看下如何由SessionContext来创建Session:
 
Java代码  收藏代码
  1. protected Session doCreateSession(SessionContext context) {  
  2.         Session s = newSessionInstance(context);  
  3.         if (log.isTraceEnabled()) {  
  4.             log.trace("Creating session for host {}", s.getHost());  
  5.         }  
  6.         create(s);  
  7.         return s;  
  8.     }  
  9.   
  10.     protected Session newSessionInstance(SessionContext context) {  
  11.         return getSessionFactory().createSession(context);  
  12.     }  

和Subject一样也是由一个SessionFactory根据SessionContext来创建出一个Session,看下默认的SessionFactory SimpleSessionFactory的创建过程: 
Java代码  收藏代码
  1. public Session createSession(SessionContext initData) {  
  2.         if (initData != null) {  
  3.             String host = initData.getHost();  
  4.             if (host != null) {  
  5.                 return new SimpleSession(host);  
  6.             }  
  7.         }  
  8.         return new SimpleSession();  
  9.     }  

如果SessionContext有host信息,就传递给Session,然后就是直接new一个Session接口的实现SimpleSession,先看下Session接口有哪些内容: 
Java代码  收藏代码
  1. public interface Session {  
  2.     Serializable getId();  
  3.     Date getStartTimestamp();  
  4.     Date getLastAccessTime();  
  5.     long getTimeout() throws InvalidSessionException;  
  6.     void setTimeout(long maxIdleTimeInMillis) throws InvalidSessionException;  
  7.     String getHost();  
  8.     void touch() throws InvalidSessionException;  
  9.     void stop() throws InvalidSessionException;  
  10.     Collection<Object> getAttributeKeys() throws InvalidSessionException;  
  11.     Object getAttribute(Object key) throws InvalidSessionException;  
  12.     void setAttribute(Object key, Object value) throws InvalidSessionException;  
  13.     Object removeAttribute(Object key) throws InvalidSessionException;  
  14. }  

id:Session的唯一标识,创建时间、超时时间等内容。 
再看SimpleSession的创建过程:
 
Java代码  收藏代码
  1. public SimpleSession() {  
  2.         this.timeout = DefaultSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT;  
  3.         this.startTimestamp = new Date();  
  4.         this.lastAccessTime = this.startTimestamp;  
  5.     }  
  6.   
  7.     public SimpleSession(String host) {  
  8.         this();  
  9.         this.host = host;  
  10.     }  

设置下超时时间为DefaultSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT 30分钟,startTimestamp 和lastAccessTime设置为现在开始。就这样构建出了一个Session的实例,然后就是需要将该实例保存起来: 
Java代码  收藏代码
  1. protected Session doCreateSession(SessionContext context) {  
  2.         Session s = newSessionInstance(context);  
  3.         if (log.isTraceEnabled()) {  
  4.             log.trace("Creating session for host {}", s.getHost());  
  5.         }  
  6.         create(s);  
  7.         return s;  
  8.     }  
  9. protected void create(Session session) {  
  10.         if (log.isDebugEnabled()) {  
  11.             log.debug("Creating new EIS record for new session instance [" + session + "]");  
  12.         }  
  13.         sessionDAO.create(session);  
  14.     }  

即该进行create(s)操作了,又和Subject极度的相像,使用sessionDAO来保存刚才创建的Session。再来看下SessionDAO接口: 
Java代码  收藏代码
  1. public interface SessionDAO {  
  2.     Serializable create(Session session);  
  3.     Session readSession(Serializable sessionId) throws UnknownSessionException;  
  4.     void update(Session session) throws UnknownSessionException;  
  5.     void delete(Session session);  
  6.     Collection<Session> getActiveSessions();  
  7. }  

也就是对所有的Session进行增删该查,SessionDAO 接口继承关系如下: 

 
AbstractSessionDAO:有一个重要的属性SessionIdGenerator,它负责给Session创建sessionId,SessionIdGenerator接口如下: 
Java代码  收藏代码
  1. public interface SessionIdGenerator {  
  2.     Serializable generateId(Session session);  
  3. }  

很简单,参数为Session,返回sessionId。SessionIdGenerator 的实现有两个JavaUuidSessionIdGenerator、RandomSessionIdGenerator。而AbstractSessionDAO默认采用的是JavaUuidSessionIdGenerator,如下: 
Java代码  收藏代码
  1. public AbstractSessionDAO() {  
  2.         this.sessionIdGenerator = new JavaUuidSessionIdGenerator();  
  3.     }  

MemorySessionDAO继承了AbstractSessionDAO,它把Session存储在一个ConcurrentMap<Serializable, Session> sessions集合中,key为sessionId,value为Session。 
CachingSessionDAO:主要配合在别的地方存储session。先不介绍,之后的文章再详细说。 
对于本案例来说SessionDAO为MemorySessionDAO。至此整个Session的创建过程就走通了。 

刚才虽然说了整个Session的创建过程,回到上文所说的,不会去创建Session的地方。在创建Subject搜集session信息时,使用的此时的Subject的Session、sessionId都为空,所以获取不到Session。然后就是doCreateSubject:
 
Java代码  收藏代码
  1. protected Subject doCreateSubject(SubjectContext context) {  
  2.         return getSubjectFactory().createSubject(context);  
  3.     }  

就是通过SubjectFactory工厂接口来创建Subject的,而DefaultSecurityManager默认使用的 
SubjectFactory是DefaultSubjectFactory:
 
Java代码  收藏代码
  1. public DefaultSecurityManager() {  
  2.         super();  
  3.         this.subjectFactory = new DefaultSubjectFactory();  
  4.         this.subjectDAO = new DefaultSubjectDAO();  
  5.     }  

继续看DefaultSubjectFactory是怎么创建Subject的: 
Java代码  收藏代码
  1. public Subject createSubject(SubjectContext context) {  
  2.         SecurityManager securityManager = context.resolveSecurityManager();  
  3.         Session session = context.resolveSession();  
  4.         boolean sessionCreationEnabled = context.isSessionCreationEnabled();  
  5.         PrincipalCollection principals = context.resolvePrincipals();  
  6.         boolean authenticated = context.resolveAuthenticated();  
  7.         String host = context.resolveHost();  
  8.   
  9.         return new DelegatingSubject(principals, authenticated, host, session, sessionCreationEnabled, securityManager);  
  10.     }  

仍然就是将这些属性传递给DelegatingSubject,也没什么好说的。创建完成之后,就需要将刚创建的Subject保存起来,仍回到: 
Java代码  收藏代码
  1. public Subject createSubject(SubjectContext subjectContext) {  
  2.         //create a copy so we don't modify the argument's backing map:  
  3.         SubjectContext context = copy(subjectContext);  
  4.   
  5.         //ensure that the context has a SecurityManager instance, and if not, add one:  
  6.         context = ensureSecurityManager(context);  
  7.   
  8.         //Resolve an associated Session (usually based on a referenced session ID), and place it in the context before  
  9.         //sending to the SubjectFactory.  The SubjectFactory should not need to know how to acquire sessions as the  
  10.         //process is often environment specific - better to shield the SF from these details:  
  11.         context = resolveSession(context);  
  12.   
  13.         //Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first  
  14.         //if possible before handing off to the SubjectFactory:  
  15.         context = resolvePrincipals(context);  
  16.   
  17.         Subject subject = doCreateSubject(context);  
  18.   
  19.         //save this subject for future reference if necessary:  
  20.         //(this is needed here in case rememberMe principals were resolved and they need to be stored in the  
  21.         //session, so we don't constantly rehydrate the rememberMe PrincipalCollection on every operation).  
  22.         //Added in 1.2:  
  23.         save(subject);  
  24.   
  25.         return subject;  
  26.     }  

来看下save方法: 
Java代码  收藏代码
  1. protected void save(Subject subject) {  
  2.         this.subjectDAO.save(subject);  
  3.     }  

可以看到又是使用另一个模块来完成的即SubjectDAO,SubjectDAO接口如下: 
Java代码  收藏代码
  1. public interface SubjectDAO {  
  2.     Subject save(Subject subject);  
  3.     void delete(Subject subject);  
  4. }  

很简单,就是保存和删除一个Subject。我们看下具体的实现类DefaultSubjectDAO是如何来保存的: 
Java代码  收藏代码
  1. public Subject save(Subject subject) {  
  2.         if (isSessionStorageEnabled(subject)) {  
  3.             saveToSession(subject);  
  4.         } else {  
  5.             log.trace("Session storage of subject state for Subject [{}] has been disabled: identity and " +  
  6.                     "authentication state are expected to be initialized on every request or invocation.", subject);  
  7.         }  
  8.   
  9.         return subject;  
  10.     }  

首先就是判断isSessionStorageEnabled,是否要存储该Subject的session来 
DefaultSubjectDAO:有一个重要属性SessionStorageEvaluator,它是用来决定一个Subject的Session来记录Subject的状态,接口如下
 
Java代码  收藏代码
  1. public interface SessionStorageEvaluator {  
  2.     boolean isSessionStorageEnabled(Subject subject);  
  3. }  

其实现为DefaultSessionStorageEvaluator: 
Java代码  收藏代码
  1. public class DefaultSessionStorageEvaluator implements SessionStorageEvaluator {  
  2.   
  3.     private boolean sessionStorageEnabled = true;  
  4.   
  5.     public boolean isSessionStorageEnabled(Subject subject) {  
  6.         return (subject != null && subject.getSession(false) != null) || isSessionStorageEnabled();  
  7.     }  

决定策略就是通过DefaultSessionStorageEvaluator 的sessionStorageEnabled的true或false 和subject是否有Session对象来决定的。如果允许存储Subject的Session的话,下面就说具体的存储过程: 
Java代码  收藏代码
  1. protected void saveToSession(Subject subject) {  
  2.         //performs merge logic, only updating the Subject's session if it does not match the current state:  
  3.         mergePrincipals(subject);  
  4.         mergeAuthenticationState(subject);  
  5.     }  
  6. protected void mergePrincipals(Subject subject) {  
  7.         //merge PrincipalCollection state:  
  8.   
  9.         PrincipalCollection currentPrincipals = null;  
  10.   
  11.         //SHIRO-380: added if/else block - need to retain original (source) principals  
  12.         //This technique (reflection) is only temporary - a proper long term solution needs to be found,  
  13.         //but this technique allowed an immediate fix that is API point-version forwards and backwards compatible  
  14.         //  
  15.         //A more comprehensive review / cleaning of runAs should be performed for Shiro 1.3 / 2.0 +  
  16.         if (subject.isRunAs() && subject instanceof DelegatingSubject) {  
  17.             try {  
  18.                 Field field = DelegatingSubject.class.getDeclaredField("principals");  
  19.                 field.setAccessible(true);  
  20.                 currentPrincipals = (PrincipalCollection)field.get(subject);  
  21.             } catch (Exception e) {  
  22.                 throw new IllegalStateException("Unable to access DelegatingSubject principals property.", e);  
  23.             }  
  24.         }  
  25.         if (currentPrincipals == null || currentPrincipals.isEmpty()) {  
  26.             currentPrincipals = subject.getPrincipals();  
  27.         }  
  28.   
  29.         Session session = subject.getSession(false);  
  30.   
  31.         if (session == null) {  
  32.            //只有当Session为空,并且currentPrincipals不为空的时候才会去创建Session  
  33.            //Subject subject = SecurityUtils.getSubject()此时两者都是为空的,  
  34.            //不会去创建Session  
  35.             if (!CollectionUtils.isEmpty(currentPrincipals)) {  
  36.                 session = subject.getSession();  
  37.                 session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);  
  38.             }  
  39.             //otherwise no session and no principals - nothing to save  
  40.         } else {  
  41.             PrincipalCollection existingPrincipals =  
  42.                     (PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);  
  43.   
  44.             if (CollectionUtils.isEmpty(currentPrincipals)) {  
  45.                 if (!CollectionUtils.isEmpty(existingPrincipals)) {  
  46.                     session.removeAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);  
  47.                 }  
  48.                 //otherwise both are null or empty - no need to update the session  
  49.             } else {  
  50.                 if (!currentPrincipals.equals(existingPrincipals)) {  
  51.                     session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);  
  52.                 }  
  53.                 //otherwise they're the same - no need to update the session  
  54.             }  
  55.         }  
  56.     }  

上面有我们关心的重点,当subject.getSession(false)获取的Session为空时(它不会去创建Session),此时就需要去创建Session,subject.getSession()则默认调用的是subject.getSession(true),则会进行Session的创建,创建过程上文已详细说明了。 
在第一次创建Subject的时候
 
Java代码  收藏代码
  1. Subject subject = SecurityUtils.getSubject();    

虽然Session为空,但此时还没有用户身份信息,也不会去创建Session。案例中的subject.login(token),该过程则会去创建Session,具体看下过程: 
Java代码  收藏代码
  1. public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {  
  2.         AuthenticationInfo info;  
  3.         try {  
  4.             info = authenticate(token);  
  5.         } catch (AuthenticationException ae) {  
  6.             try {  
  7.                 onFailedLogin(token, ae, subject);  
  8.             } catch (Exception e) {  
  9.                 if (log.isInfoEnabled()) {  
  10.                     log.info("onFailedLogin method threw an " +  
  11.                             "exception.  Logging and propagating original AuthenticationException.", e);  
  12.                 }  
  13.             }  
  14.             throw ae; //propagate  
  15.         }  
  16.          //在该过程会进行Session的创建  
  17.         Subject loggedIn = createSubject(token, info, subject);  
  18.   
  19.         onSuccessfulLogin(token, info, loggedIn);  
  20.   
  21.         return loggedIn;  
  22.     }  

对于验证过程上篇文章已经简单说明了,这里不再说明,重点还是在验证通过后,会设置Subject的身份,即用户名: 
Java代码  收藏代码
  1. protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) {  
  2.         SubjectContext context = createSubjectContext();  
  3.         context.setAuthenticated(true);  
  4.         context.setAuthenticationToken(token);  
  5.         context.setAuthenticationInfo(info);  
  6.         if (existing != null) {  
  7.             context.setSubject(existing);  
  8.         }  
  9.         return createSubject(context);  
  10.     }  

有了认证成功的AuthenticationInfo信息,SubjectContext在resolvePrincipals便可以获取用户信息,即通过AuthenticationInfo的getPrincipals()来获得。 
Java代码  收藏代码
  1. public PrincipalCollection resolvePrincipals() {  
  2.         PrincipalCollection principals = getPrincipals();  
  3.   
  4.         if (CollectionUtils.isEmpty(principals)) {  
  5.             //check to see if they were just authenticated:  
  6.             AuthenticationInfo info = getAuthenticationInfo();  
  7.             if (info != null) {  
  8.                 principals = info.getPrincipals();  
  9.             }  
  10.         }  
  11.   
  12.         if (CollectionUtils.isEmpty(principals)) {  
  13.             Subject subject = getSubject();  
  14.             if (subject != null) {  
  15.                 principals = subject.getPrincipals();  
  16.             }  
  17.         }  
  18.   
  19.         if (CollectionUtils.isEmpty(principals)) {  
  20.             //try the session:  
  21.             Session session = resolveSession();  
  22.             if (session != null) {  
  23.                 principals = (PrincipalCollection) session.getAttribute(PRINCIPALS_SESSION_KEY);  
  24.             }  
  25.         }  
  26.   
  27.         return principals;  
  28.     }  

PrincipalCollection不为空了,在save(subject)的时候会得到session为空,同时PrincipalCollection不为空,则会执行Session的创建。也就是说在认证通过后,会执行Session的创建,Session创建完成之后会进行一次装饰,即用StoppingAwareProxiedSession将创建出来的session和subject关联起来,然后又进行如下操作: 
Java代码  收藏代码
  1. public void login(AuthenticationToken token) throws AuthenticationException {  
  2.         clearRunAsIdentitiesInternal();  
  3.         //这里的Subject则是经过认证后创建的并且也含有刚才创建的session,类型为  
  4.         //StoppingAwareProxiedSession,即是该subject本身和session的合体。  
  5.         Subject subject = securityManager.login(this, token);  
  6.   
  7.         PrincipalCollection principals;  
  8.   
  9.         String host = null;  
  10.   
  11.         if (subject instanceof DelegatingSubject) {  
  12.             DelegatingSubject delegating = (DelegatingSubject) subject;  
  13.             //we have to do this in case there are assumed identities - we don't want to lose the 'real' principals:  
  14.             principals = delegating.principals;  
  15.             host = delegating.host;  
  16.         } else {  
  17.             principals = subject.getPrincipals();  
  18.         }  
  19.   
  20.         if (principals == null || principals.isEmpty()) {  
  21.             String msg = "Principals returned from securityManager.login( token ) returned a null or " +  
  22.                     "empty value.  This value must be non null and populated with one or more elements.";  
  23.             throw new IllegalStateException(msg);  
  24.         }  
  25.         this.principals = principals;  
  26.         this.authenticated = true;  
  27.         if (token instanceof HostAuthenticationToken) {  
  28.             host = ((HostAuthenticationToken) token).getHost();  
  29.         }  
  30.         if (host != null) {  
  31.             this.host = host;  
  32.         }  
  33.         Session session = subject.getSession(false);  
  34.         if (session != null) {  
  35.             //在这里可以看到又进行了一次装饰  
  36.             this.session = decorate(session);  
  37.         } else {  
  38.             this.session = null;  
  39.         }  
  40.     }  

subject 创建出来之后,暂且叫内部subject,就是把认证通过的内部subject的信息和session复制给我们外界使用的subject.login(token)的subject中,这个subject暂且叫外部subject,看下session的赋值,又进行了一次装饰,这次装饰则把session(类型为StoppingAwareProxiedSession,即是内部subject和session的合体)和外部subject绑定到一起。 
最后来总结下,首先是Subject和Session的接口类图: 


然后就是Subject subject = SecurityUtils.getSubject()的一个简易的流程图: 


最后是subject.login(token)的简易流程图: 




S7-1500与WinCC GroupDisplay组显示的高效配置指南 本文详细介绍了S7-1500 PLC与WinCC GroupDisplay组显示功能的高效配置方法,涵盖报警状态配置、WinCC端组显示控件设置及常见问题排查。通过实战案例和优化建议,帮助工程师快速掌握工业自动化项目中报警信息可视化的关键技术,提升生产线监控效率。 阅读详情

相关推荐

AD9361 FIR 滤波器设计

matlab 得到滤波器系数

weixin_44630490的博客 1万+

ModelViewSet使用!!

ModelViewSet源码 class ModelViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, G

dasfa11的博客 6147

差分信号_差分信号的原理及其在PCB的设计

差分线是 PCB 设计中非常重要的一部分信号线,信号处理要求也是相当严谨,今天为大家介绍下差分信号的原理以及其在 PCB 设计中的处理方法。 什么是差分信号 差分传输是一种信号传输的技术,区别于传统的一根信号线一根地线的做法,差分传输在这两根线上都传输信号,这两个信号的振幅相等,相位相差 180 度,极性相反。在这两根线上传输的信号就是差分...

weixin_31849185的博客 1812

Shiro中的session学习,在线用户显示、用户下线

一、代码示例 import org.apache.shiro.session.Session; import org.apache.shiro.session.mgt.eis.SessionDAO; import org.apache.shiro.subject.SimplePrincipalCollection; import org.apache.shiro.subject.suppo...

程序员小嗨 3554

从Zemax到Speos:HUD设计数据无缝迁移避坑指南(含STEP文件精度验证)

本文详细介绍了从Zemax到Speos的HUD设计数据无缝迁移方法,重点解决Zernike曲面参数保真、虚拟厚度设置和坐标系对齐等核心问题。通过STEP文件精度验证和工程化解决方案,帮助工程师避免数据迁移中的常见陷阱,提升HUD系统设计效率。适用于Ansys Zemax和Ansys Speos用户,优化光学性能验证流程。

h6i7j8的博客 1064

Win本地部署DeepSeek

Ollama 提供了简单的接口,用于创建、运行和管理这些模型,同时还维护了一个丰富的预构建模型库,可以轻松集成到各种应用程序中。重启ollama,具体操作为打开搜索ollama进程,将其结束进程,之后再执行相关模型下载命令即可,或重启电脑!Ollama支持多种DeepSeek模型版本,用户可以根据硬件配置选择合适的模型。配置添加系统环境变量OLLAMA_MODELS,设置其值为新的路径,点击确定应用。安装完成后,在终端输入以下命令,检查Ollama版本。:14B、32B或70B版本,适合高端GPU。

m0_67297673的博客 2394

shiro获取当前登录用户subject.getPrincipal()报null空指针

SecurityUtils.getSubject().getPrincipal()为空,网上查了一堆复制粘贴的文章都是说配置加载顺序问题,我遇到的并不是这样的问题,我是偶尔一个报空,其他的正常.既然是这个参数报空,那就找到代码的这里,发现原来代码的逻辑是从redis中拿,并且把如果redis没有就去查数据库的代码给注释掉了.恢复注释即可.自定义realm–》doGetAuthenticationInfo–》

iijik55的博客 2788

分布式 Agent 系统设计:高并发场景下的挑战与解决方案

分布式 Agent 系统由多个分布在不同节点上的智能 Agent 组成,这些 Agent 具有自主决策能力,能够通过通信协作完成复杂任务。每个 Agent 可以看作是一个独立的实体,具备感知环境、处理信息、执行动作的功能,它们相互协作,共同实现系统的整体目标。分布式 Agent 系统在高并发场景下面临着资源调度、通信延迟、状态一致性、容错处理和性能瓶颈等诸多挑战。通过采用动态资源分配、高效通信协议、分布式共识算法、冗余备份等解决方案,可以有效应对这些挑战,提高系统的性能、可靠性和可用性。

shuizhudan223的博客 1294

第十章 会话管理——《跟我学Shiro》

  目录贴: 跟我学Shiro目录贴   Shiro提供了完整的企业级会话管理功能,不依赖于底层容器(如web容器tomcat),不管JavaSE还是JavaEE环境都可以使用,提供了会话管理、会话事件监听、会话存储/持久化、容器无关的集群、失效/过期支持、对Web的透明支持、SSO单点登录的支持等特性。即直接使用Shiro的会话管理可以直接替换如Web容器的会话管理。   会话 ...

jinnianshilongnian的专栏 1043

提示工程从初级到高级:如何评估提示词的鲁棒性?架构师分享测试方法

鲁棒性(Robustness)原本是工程领域的概念,指系统在“非理想条件”下保持性能稳定的能力。输入鲁棒性:面对不规范、有噪音、格式变异的用户输入,提示能否引导模型输出正确结果?模型鲁棒性:同一提示在不同大模型(或同一模型的不同版本)下,输出是否一致?场景鲁棒性:在边界场景(极端case、歧义case、多任务混合)下,提示能否保持逻辑一致性?提示词V1:请将以下用户评论分类为「正面」「中性」或「负面」:{comment}。要求:输出唯一类别,不要额外说明。

小程序开发 497

把 SAP Fiori Launchpad Designer 的 Catalog 与 Group 传输讲透:从 CONF、CUST 到 Transport Request 的完整理解

SAP Fiori项目中,catalog和group的传输问题往往源于对FLPD层次模型的理解不足。FLPD包含configuration(系统级)和customizing(客户端级)两个层面,分别对应workbench request和customizing request传输方式。常见误区包括:混淆/UI2/FLPD_CONF和/UI2/FLPD_CUST入口、未提前创建传输请求、不了解层次覆盖机制(customizing会覆盖configuration)。当客户端修改configuration层的对象

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 102

shiro源码分析01 -- Subject

什么是Subject对象 在shiro中我们通常会将Subject对象理解为一个用户,同样的它也有可能是一个三方程序,它是一个抽象的概念,可以理解为任何与系统交互的“东西”都是Subject。 Subject对象内置属性 Subject其实是一个声明接口,使用的默认类是WebDelegatingSubject,是DelegatingSubject的子类。其中这个WebDelegatingSubject里面含有以下的参数: principals:主键(或者标记的集合) authenticate

Layduo 509

饥饿游戏搜索算法(HGS)详解:从生物激励到智能优化

饥饿游戏搜索算法(HGS)是一种受动物饥饿驱动觅食行为启发的智能优化算法。该算法通过模拟生物饥饿程度与觅食策略的关系,将个体适应度映射为饥饿感,并据此动态调整搜索行为。HGS的核心机制包括:1) 饥饿权重计算(W1/W2),2) 三种位置更新策略(随机探索、正向/负向协作),3) 自适应平衡参数E。算法通过迭代优化逐步改进解的质量,具有参数自适应、全局搜索能力强等特点,在复杂优化问题中表现出色。相比传统优化方法,HGS更注重个体内部状态对搜索行为的影响,实现了探索与开发的自然平衡。

qq_42212808的博客 156

Shiro源码分析之Subject和SecurityManager

Subject 毫无疑问,Subject是Shiro最重要的一个概念。 “Subject”只是一个安全术语,意味着应用程序用户的特定于安全性的“视图”。Shiro Subject实例代表单个应用程序用户的安全状态和相关操作。 创建 初次创建是在AbstractShiroFilter#doFil...

chengtaopai5214的博客 301

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

Shiro源码分析③ :认证流程

一、前言 二、登录流程 下面开始登录流程的代码解析 如下,登录接口如下: @PostMapping("login") public String login() { UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("张三", "123456"); Subject subject = SecurityUtils.getSubject(); subjec

猫吻鱼的博客 1257

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

shiro 更新subject_Springboot整合Shiro之请求源码分析

上文已说到Springboot启动时候往Serlvet容器中注入SpringShiroFilter过滤器对象,该过滤器对象实现了Filter接口,当有请求到来时会进行拦截操作,根据链式调用最终会调用到父类AbstractShiroFilter的doFilterInternal,先包装请求对象Request为ShiroHttpServletRequest,Response为ShiroHttpServ...

weixin_39833454的博客 223

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

shiro源码分析

接上一篇,https://blog.csdn.net/luxinghong199106/article/details/105707188 这一篇主要通过2个流程,一个登录流程和一个登陆成功后的访问流程来看下 subject 的创建过程。因为subject 是 shiro 的核心,搞懂了它的创建过程,基本就搞懂了 shiro 的核心逻辑。(这一部分代码是基于传统表单验证的,不是基于JWT的) ...

luxinghong199106的博客 560

Shiro源码分析(八)——获取Session

2021SC@SDUSC

MIKE2333的博客 300

Shiro Session管理——操作session

默认情况下,Shiro会使用容器自带的session机制,但若是容器不存在session,那么Shiro会提供内置的企业级session来管理。我们可以看到这个框架图,我们的整个交互都是与security Manager做交互,而这里面就有一个Session Manager的管理器,Shiro当然内置了实现,我们也可以根据接口拓展其功能,那么下面,我们就来了解一下shiro中关于Session管理的部分内容。如果不设置是默认的会话管理器的全局过期时间。这是一个管理器实现类,是shiro提供的可用的结构。..

m0_67391121的博客 1570

org.apache.shiro.session.UnknownSessionException: There is no session with id [xxxx]的解决方案

org.apache.shiro.session.UnknownSessionException: There is no session with id [xxxx]的解决方案 背景描述 SpringBoot项目,使用Shiro进行权限管理。测试过程中发现执行文件导入时最开始一切正常,但是导入几次之后再次执行导入就会报错,此时执行其他功能一切正常 排查过程 [x] 1. 网上搜索,...

weixin_30648587的博客 2275

shiro源码学习

快速开始源码分析 shiro整合spring web.xml的配置 Subject对象创建过程 SecurityUtils获取Subject详解

你就像甜甜的益达 1190

Shiro框架的授权实现源码分析之登录身份信息保存

Shiro框架的授权实现源码分析之登录身份信息保存

smalllongonline的专栏 1340

源码分析之Spring Security 和 Shiro 请求处理流程

一、Spring Security登录执行流程 1、首先用ServletFilter拦截器(AbstractAuthenticationProcessingFilter) 对应UsernamePasswordAuthenticationFilter: 拦截到登录的请求(通常是form Login,比如 /login + POST ) 解析出登录信息principal和credentials(对应username和password),封装成Authenticat...

zollty的专栏 657

Springboot整合Shiro之请求源码分析

上文已说到Springboot启动时候往serlvet容器中注入SpringShiroFilter过滤器,该过滤器实现了Filter接口,当有请求到来时 会进行拦截操作,根据链式调用最终会调用到父类AbstractShiroFilter的doFilterInternal

xulifeng1199的博客 224
上一篇: shiro1.4.0是不是有bug啊
下一篇: shiro session存redis
hxpjava1
博客等级 码龄19年 1365粉丝 511原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值