shiro源码分析(二)Subject和Session

继续上一篇文章的案例,第一次使用SecurityUtils.getSubject()来获取Subject时 

?


1

2

3

4

5

6

7

8

public static Subject getSubject() {

        Subject subject = ThreadContext.getSubject();

        if (subject == null) {

            subject = (new Subject.Builder()).buildSubject();

            ThreadContext.bind(subject);

        }

        return subject;

    }

  
使用ThreadLocal模式来获取,若没有则创建一个并绑定到当前线程。此时创建使用的是Subject内部类Builder来创建的,Builder会创建一个SubjectContext接口的实例DefaultSubjectContext,最终会委托securityManager来根据SubjectContext信息来创建一个Subject,下面详细说下该过程,在DefaultSecurityManager的createSubject方法中: 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

public Subject createSubject(SubjectContext subjectContext) {

        SubjectContext context = copy(subjectContext);

 

        context = ensureSecurityManager(context);

 

        context = resolveSession(context);

 

        context = resolvePrincipals(context);

 

        Subject subject = doCreateSubject(context);

 

        save(subject);

 

        return subject;

    }

首先就是复制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为例: 

?


1

2

3

4

5

SecurityManager getSecurityManager();

 

    void setSecurityManager(SecurityManager securityManager);

 

    SecurityManager resolveSecurityManager();

这些get、set方法则用于常用的设置和获取,而resolve则表示先调用getSecurityManager,如果获取不到,则使用其他途径来获取,如DefaultSubjectContext的实现: 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

public SecurityManager resolveSecurityManager() {

        SecurityManager securityManager = getSecurityManager();

        if (securityManager == null) {

            if (log.isDebugEnabled()) {

                log.debug("No SecurityManager available in subject context map.  " +

                        "Falling back to SecurityUtils.getSecurityManager() lookup.");

            }

            try {

                securityManager = SecurityUtils.getSecurityManager();

            } catch (UnavailableSecurityManagerException e) {

                if (log.isDebugEnabled()) {

                    log.debug("No SecurityManager available via SecurityUtils.  Heuristics exhausted.", e);

                }

            }

        }

        return securityManager;

    }

如果getSecurityManager获取不到,则使用SecurityUtils工具来获取。 
再如resolvePrincipals 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

public PrincipalCollection resolvePrincipals() {

        PrincipalCollection principals = getPrincipals();

 

        if (CollectionUtils.isEmpty(principals)) {

            //check to see if they were just authenticated:

            AuthenticationInfo info = getAuthenticationInfo();

            if (info != null) {

                principals = info.getPrincipals();

            }

        }

 

        if (CollectionUtils.isEmpty(principals)) {

            Subject subject = getSubject();

            if (subject != null) {

                principals = subject.getPrincipals();

            }

        }

 

        if (CollectionUtils.isEmpty(principals)) {

            //try the session:

            Session session = resolveSession();

            if (session != null) {

                principals = (PrincipalCollection) session.getAttribute(PRINCIPALS_SESSION_KEY);

            }

        }

 

        return principals;

    }

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

然后我们继续回到上面的类图设计上: 
DefaultSubjectContext继承了MapContext,MapContext又实现了Map<String, Object>,看下此时的MapContext有什么东西: 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

public class MapContext implements Map<String, Object>, Serializable {

 

    private static final long serialVersionUID = 5373399119017820322L;

 

    private final Map<String, Object> backingMap;

 

    public MapContext() {

        this.backingMap = new HashMap<String, Object>();

    }

 

    public MapContext(Map<String, Object> map) {

        this();

        if (!CollectionUtils.isEmpty(map)) {

            this.backingMap.putAll(map);

        }

    }

  //略

}

MapContext内部拥有一个类型为HashMap的backingMap属性,大部分方法都由HashMap来实现,然后仅仅更改某些行为,MapContext没有选择去继承HashMap,而是使用了组合的方式,更加容易去扩展,如backingMap的类型不一定非要选择HashMap,可以换成其他的Map实现,一旦MapContext选择继承HashMap,如果想对其他的Map类型进行同样的功能增强的话,就需要另写一个类来继承它然后改变一些方法实现,这样的话就会有很多重复代码。这也是设计模式所强调的少用继承多用组合。但是MapContext的写法使得子类没法去替换HashMap,哎,心塞 。 
MapContext又提供了如下几个返回值不可修改的方法: 

?


1

2

3

4

5

6

7

8

9

10

11

public Set<String> keySet() {

        return Collections.unmodifiableSet(backingMap.keySet());

    }

 

    public Collection<Object> values() {

        return Collections.unmodifiableCollection(backingMap.values());

    }

 

    public Set<Entry<String, Object>> entrySet() {

        return Collections.unmodifiableSet(backingMap.entrySet());

    }

有点扯远了。继续回到DefaultSecurityManager创建Subject的地方: 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

public Subject createSubject(SubjectContext subjectContext) {

        //create a copy so we don't modify the argument's backing map:

        SubjectContext context = copy(subjectContext);

 

        //ensure that the context has a SecurityManager instance, and if not, add one:

        context = ensureSecurityManager(context);

 

        //Resolve an associated Session (usually based on a referenced session ID), and place it in the context before

        //sending to the SubjectFactory.  The SubjectFactory should not need to know how to acquire sessions as the

        //process is often environment specific - better to shield the SF from these details:

        context = resolveSession(context);

 

        //Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first

        //if possible before handing off to the SubjectFactory:

        context = resolvePrincipals(context);

 

        Subject subject = doCreateSubject(context);

 

        //save this subject for future reference if necessary:

        //(this is needed here in case rememberMe principals were resolved and they need to be stored in the

        //session, so we don't constantly rehydrate the rememberMe PrincipalCollection on every operation).

        //Added in 1.2:

        save(subject);

 

        return subject;

    }

对于context,把能获取到的参数都凑齐,SecurityManager、Session。resolveSession尝试获取context的map中获取Session,若没有则尝试获取context的map中的Subject,如果存在的话,根据此Subject来获取Session,若没有再尝试获取sessionId,若果有了sessionId则构建成一个DefaultSessionKey来获取对应的Session。 
整个过程如下; 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

protected SubjectContext resolveSession(SubjectContext context) {

        if (context.resolveSession() != null) {

            log.debug("Context already contains a session.  Returning.");

            return context;

        }

        try {

            //Context couldn't resolve it directly, let's see if we can since we have direct access to

            //the session manager:

            Session session = resolveContextSession(context);

            if (session != null) {

                context.setSession(session);

            }

        } catch (InvalidSessionException e) {

            log.debug("Resolved SubjectContext context session is invalid.  Ignoring and creating an anonymous " +

                    "(session-less) Subject instance.", e);

        }

        return context;

    }

先看下context.resolveSession(): 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

public Session resolveSession() {

  //这里则是直接从map中取出Session

        Session session = getSession();

        if (session == null) {

            //try the Subject if it exists:

           //若果没有,尝试从map中取出Subject

            Subject existingSubject = getSubject();

            if (existingSubject != null) {

                //这里就是Subject获取session的方法,需要详细看下

                session = existingSubject.getSession(false);

            }

        }

        return session;

    }

existingSubject.getSession(false):通过Subject获取Session如下 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

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;

    }

getSession()的参数表示是否创建session,如果Session为空,并且传递的参数为true,则会创建一个Session。然而这里传递的是false,也就是说不会在创建Subject的时候来创建Session,所以把创建Session过程说完后,再回到此处是要记着不会去创建一个Session。但是我们可以来看下是如何创建Session的,整体三大步骤,先创建一个SessionContext ,然后根据SessionContext 来创建Session,最后是装饰Session,由于创建Session过程内容比较多,先说说装饰Session。 

?


1

2

3

4

5

6

protected Session decorate(Session session) {

        if (session == null) {

            throw new IllegalArgumentException("session cannot be null");

        }

        return new StoppingAwareProxiedSession(session, this);

    }

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

 
和SubjectContext相当雷同。 
看下SessionContext的主要内容: 

?


1

2

3

4

5

6

void setHost(String host);

    String getHost();

 

    Serializable getSessionId();

 

    void setSessionId(Serializable sessionId);

主要两个内容,host和sessionId。 
接下来看下如何由SessionContext来创建Session: 

?


1

2

3

4

5

6

7

8

9

10

11

12

protected Session doCreateSession(SessionContext context) {

        Session s = newSessionInstance(context);

        if (log.isTraceEnabled()) {

            log.trace("Creating session for host {}", s.getHost());

        }

        create(s);

        return s;

    }

 

    protected Session newSessionInstance(SessionContext context) {

        return getSessionFactory().createSession(context);

    }

和Subject一样也是由一个SessionFactory根据SessionContext来创建出一个Session,看下默认的SessionFactory SimpleSessionFactory的创建过程: 

?


1

2

3

4

5

6

7

8

9

public Session createSession(SessionContext initData) {

        if (initData != null) {

            String host = initData.getHost();

            if (host != null) {

                return new SimpleSession(host);

            }

        }

        return new SimpleSession();

    }

如果SessionContext有host信息,就传递给Session,然后就是直接new一个Session接口的实现SimpleSession,先看下Session接口有哪些内容: 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

public interface Session {

    Serializable getId();

    Date getStartTimestamp();

    Date getLastAccessTime();

    long getTimeout() throws InvalidSessionException;

    void setTimeout(long maxIdleTimeInMillis) throws InvalidSessionException;

    String getHost();

    void touch() throws InvalidSessionException;

    void stop() throws InvalidSessionException;

    Collection<Object> getAttributeKeys() throws InvalidSessionException;

    Object getAttribute(Object key) throws InvalidSessionException;

    void setAttribute(Object key, Object value) throws InvalidSessionException;

    Object removeAttribute(Object key) throws InvalidSessionException;

}

id:Session的唯一标识,创建时间、超时时间等内容。 
再看SimpleSession的创建过程: 

?


1

2

3

4

5

6

7

8

9

10

public SimpleSession() {

        this.timeout = DefaultSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT;

        this.startTimestamp = new Date();

        this.lastAccessTime = this.startTimestamp;

    }

 

    public SimpleSession(String host) {

        this();

        this.host = host;

    }

设置下超时时间为DefaultSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT 30分钟,startTimestamp 和lastAccessTime设置为现在开始。就这样构建出了一个Session的实例,然后就是需要将该实例保存起来: 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

protected Session doCreateSession(SessionContext context) {

        Session s = newSessionInstance(context);

        if (log.isTraceEnabled()) {

            log.trace("Creating session for host {}", s.getHost());

        }

        create(s);

        return s;

    }

protected void create(Session session) {

        if (log.isDebugEnabled()) {

            log.debug("Creating new EIS record for new session instance [" + session + "]");

        }

        sessionDAO.create(session);

    }

即该进行create(s)操作了,又和Subject极度的相像,使用sessionDAO来保存刚才创建的Session。再来看下SessionDAO接口: 

?


1

2

3

4

5

6

7

public interface SessionDAO {

    Serializable create(Session session);

    Session readSession(Serializable sessionId) throws UnknownSessionException;

    void update(Session session) throws UnknownSessionException;

    void delete(Session session);

    Collection<Session> getActiveSessions();

}

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

 
AbstractSessionDAO:有一个重要的属性SessionIdGenerator,它负责给Session创建sessionId,SessionIdGenerator接口如下: 

?


1

2

3

public interface SessionIdGenerator {

    Serializable generateId(Session session);

}

很简单,参数为Session,返回sessionId。SessionIdGenerator 的实现有两个JavaUuidSessionIdGenerator、RandomSessionIdGenerator。而AbstractSessionDAO默认采用的是JavaUuidSessionIdGenerator,如下: 

?


1

2

3

public AbstractSessionDAO() {

        this.sessionIdGenerator = new JavaUuidSessionIdGenerator();

    }

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: 

?


1

2

3

protected Subject doCreateSubject(SubjectContext context) {

        return getSubjectFactory().createSubject(context);

    }

就是通过SubjectFactory工厂接口来创建Subject的,而DefaultSecurityManager默认使用的 
SubjectFactory是DefaultSubjectFactory: 

?


1

2

3

4

5

public DefaultSecurityManager() {

        super();

        this.subjectFactory = new DefaultSubjectFactory();

        this.subjectDAO = new DefaultSubjectDAO();

    }

继续看DefaultSubjectFactory是怎么创建Subject的: 

?


1

2

3

4

5

6

7

8

9

10

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,也没什么好说的。创建完成之后,就需要将刚创建的Subject保存起来,仍回到: 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

public Subject createSubject(SubjectContext subjectContext) {

        //create a copy so we don't modify the argument's backing map:

        SubjectContext context = copy(subjectContext);

 

        //ensure that the context has a SecurityManager instance, and if not, add one:

        context = ensureSecurityManager(context);

 

        //Resolve an associated Session (usually based on a referenced session ID), and place it in the context before

        //sending to the SubjectFactory.  The SubjectFactory should not need to know how to acquire sessions as the

        //process is often environment specific - better to shield the SF from these details:

        context = resolveSession(context);

 

        //Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first

        //if possible before handing off to the SubjectFactory:

        context = resolvePrincipals(context);

 

        Subject subject = doCreateSubject(context);

 

        //save this subject for future reference if necessary:

        //(this is needed here in case rememberMe principals were resolved and they need to be stored in the

        //session, so we don't constantly rehydrate the rememberMe PrincipalCollection on every operation).

        //Added in 1.2:

        save(subject);

 

        return subject;

    }

来看下save方法: 

?


1

2

3

protected void save(Subject subject) {

        this.subjectDAO.save(subject);

    }

可以看到又是使用另一个模块来完成的即SubjectDAO,SubjectDAO接口如下: 

?


1

2

3

4

public interface SubjectDAO {

    Subject save(Subject subject);

    void delete(Subject subject);

}

很简单,就是保存和删除一个Subject。我们看下具体的实现类DefaultSubjectDAO是如何来保存的: 

?


1

2

3

4

5

6

7

8

9

10

public Subject save(Subject subject) {

        if (isSessionStorageEnabled(subject)) {

            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;

    }

首先就是判断isSessionStorageEnabled,是否要存储该Subject的session来 
DefaultSubjectDAO:有一个重要属性SessionStorageEvaluator,它是用来决定一个Subject的Session来记录Subject的状态,接口如下 

?


1

2

3

public interface SessionStorageEvaluator {

    boolean isSessionStorageEnabled(Subject subject);

}

其实现为DefaultSessionStorageEvaluator: 

?


1

2

3

4

5

6

7

public class DefaultSessionStorageEvaluator implements SessionStorageEvaluator {

 

    private boolean sessionStorageEnabled = true;

 

    public boolean isSessionStorageEnabled(Subject subject) {

        return (subject != null && subject.getSession(false) != null) || isSessionStorageEnabled();

    }

决定策略就是通过DefaultSessionStorageEvaluator 的sessionStorageEnabled的true或false 和subject是否有Session对象来决定的。如果允许存储Subject的Session的话,下面就说具体的存储过程: 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

protected void saveToSession(Subject subject) {

        //performs merge logic, only updating the Subject's session if it does not match the current state:

        mergePrincipals(subject);

        mergeAuthenticationState(subject);

    }

protected void mergePrincipals(Subject subject) {

        //merge PrincipalCollection state:

 

        PrincipalCollection currentPrincipals = null;

 

        //SHIRO-380: added if/else block - need to retain original (source) principals

        //This technique (reflection) is only temporary - a proper long term solution needs to be found,

        //but this technique allowed an immediate fix that is API point-version forwards and backwards compatible

        //

        //A more comprehensive review / cleaning of runAs should be performed for Shiro 1.3 / 2.0 +

        if (subject.isRunAs() && subject instanceof DelegatingSubject) {

            try {

                Field field = DelegatingSubject.class.getDeclaredField("principals");

                field.setAccessible(true);

                currentPrincipals = (PrincipalCollection)field.get(subject);

            } catch (Exception e) {

                throw new IllegalStateException("Unable to access DelegatingSubject principals property.", e);

            }

        }

        if (currentPrincipals == null || currentPrincipals.isEmpty()) {

            currentPrincipals = subject.getPrincipals();

        }

 

        Session session = subject.getSession(false);

 

        if (session == null) {

           //只有当Session为空,并且currentPrincipals不为空的时候才会去创建Session

           //Subject subject = SecurityUtils.getSubject()此时两者都是为空的,

           //不会去创建Session

            if (!CollectionUtils.isEmpty(currentPrincipals)) {

                session = subject.getSession();

                session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);

            }

            //otherwise no session and no principals - nothing to save

        } else {

            PrincipalCollection existingPrincipals =

                    (PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);

 

            if (CollectionUtils.isEmpty(currentPrincipals)) {

                if (!CollectionUtils.isEmpty(existingPrincipals)) {

                    session.removeAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);

                }

                //otherwise both are null or empty - no need to update the session

            } else {

                if (!currentPrincipals.equals(existingPrincipals)) {

                    session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);

                }

                //otherwise they're the same - no need to update the session

            }

        }

    }

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

?


1

Subject subject = SecurityUtils.getSubject();

虽然Session为空,但此时还没有用户身份信息,也不会去创建Session。案例中的subject.login(token),该过程则会去创建Session,具体看下过程: 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

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

        }

         //在该过程会进行Session的创建

        Subject loggedIn = createSubject(token, info, subject);

 

        onSuccessfulLogin(token, info, loggedIn);

 

        return loggedIn;

    }

对于验证过程上篇文章已经简单说明了,这里不再说明,重点还是在验证通过后,会设置Subject的身份,即用户名: 

?


1

2

3

4

5

6

7

8

9

10

protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) {

        SubjectContext context = createSubjectContext();

        context.setAuthenticated(true);

        context.setAuthenticationToken(token);

        context.setAuthenticationInfo(info);

        if (existing != null) {

            context.setSubject(existing);

        }

        return createSubject(context);

    }

有了认证成功的AuthenticationInfo信息,SubjectContext在resolvePrincipals便可以获取用户信息,即通过AuthenticationInfo的getPrincipals()来获得。 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

public PrincipalCollection resolvePrincipals() {

        PrincipalCollection principals = getPrincipals();

 

        if (CollectionUtils.isEmpty(principals)) {

            //check to see if they were just authenticated:

            AuthenticationInfo info = getAuthenticationInfo();

            if (info != null) {

                principals = info.getPrincipals();

            }

        }

 

        if (CollectionUtils.isEmpty(principals)) {

            Subject subject = getSubject();

            if (subject != null) {

                principals = subject.getPrincipals();

            }

        }

 

        if (CollectionUtils.isEmpty(principals)) {

            //try the session:

            Session session = resolveSession();

            if (session != null) {

                principals = (PrincipalCollection) session.getAttribute(PRINCIPALS_SESSION_KEY);

            }

        }

 

        return principals;

    }

PrincipalCollection不为空了,在save(subject)的时候会得到session为空,同时PrincipalCollection不为空,则会执行Session的创建。也就是说在认证通过后,会执行Session的创建,Session创建完成之后会进行一次装饰,即用StoppingAwareProxiedSession将创建出来的session和subject关联起来,然后又进行如下操作: 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

public void login(AuthenticationToken token) throws AuthenticationException {

        clearRunAsIdentitiesInternal();

        //这里的Subject则是经过认证后创建的并且也含有刚才创建的session,类型为

        //StoppingAwareProxiedSession,即是该subject本身和session的合体。

        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;

        }

    }

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)的简易流程图: 
 
 

时间: 2024-10-25 00:29:28

shiro源码分析(二)Subject和Session的相关文章

shiro源码分析(一)入门

最近闲来无事,准备读个框架源码,经别人推荐shiro,那就准备读读其中的设计.开涛大神已经有了跟我学Shiro系列,那我就跟着这个系列入门然后再深入源代码,所以我的侧重点就是源码分析.  话不多说,上开涛大神的入门案例 地址http://jinnianshilongnian.iteye.com/blog/2019547:  ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Test      public void

shiro源码分析(六)CredentialsMatcher 的案例分析

有了上一篇文章的原理分析,这一篇文章主要结合原理来进行使用.  shiro.ini配置为:  ? 1 2 3 4 5 6 7 8 9 10 11 [main] #realm  dataSource=com.mchange.v2.c3p0.ComboPooledDataSource  dataSource.driverClass=com.mysql.jdbc.Driver  dataSource.jdbcUrl=jdbc:mysql://localhost:3306/shiro  dataSour

shiro源码分析(三)授权、认证、缓存的接口设计

前两篇文章主要说的是认证过程,这一篇来分析下授权的过程.还是开涛大神的案例(http://jinnianshilongnian.iteye.com/blog/2020017),如下:  ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class ShiroTest {       @Test      public void testHelloworld() {        

shiro源码分析(五)CredentialsMatcher

Realm在验证用户身份的时候,要进行密码匹配.最简单的情况就是明文直接匹配,然后就是加密匹配,这里的匹配工作则就是交给CredentialsMatcher来完成的.先看下它的接口方法:  ? 1 2 3 public interface CredentialsMatcher {     boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info); } 根据用户名获取AuthenticationIn

shiro源码分析(四)具体的Realm

首先还是Realm的接口设计图:   这里只来说明SimpleAccountRealm和JdbcRealm的实现.  首先是SimpleAccountRealm不用关心数据的具体来源,只提供了与上层的交互,即实现了AuthenticatingRealm遗留的AuthenticationInfo doGetAuthenticationInfo和AuthorizingRealm遗留的AuthorizationInfo doGetAuthorizationInfo.  如下:  ? 1 2 3 4 5

Hhadoop-2.7.0中HDFS写文件源码分析(二):客户端实现之DFSPacket

一.简介       HDFS在数据传输过程中,针对数据块Block,不是整个block进行传输的,而是将block切分成一个个的数据包进行传输.而DFSPacket就是HDFS数据传输过程中对数据包的抽象. 二.实现       HDFS客户端在往DataNodes节点写数据时,会以数据包packet的形式写入,且每个数据包包含一个包头,n个连续的校验和数据块checksum chunks和n个连续的实际数据块 actual data chunks,每个校验和数据块对应一个实际数据块,被用来做

TOMCAT源码分析——生命周期管理(二)

前言 我在<TOMCAT源码分析--生命周期管理(一)>一文中介绍了TOMCAT生命周期类接口设计.JMX.容器以及基于容器的事件与监听等内容,本文将接着介绍Tomcat7.0中容器生命周期的管理. 容器生命周期 每个容器都会有自身的生命周期,其中也涉及状态的迁移,以及伴随的事件生成,本节详细介绍Tomcat中的容器生命周期实现.所有容器的转态转换(如新疆.初始化.启动.停止等)都是由外到内,由上到下进行,即先执行父容器的状态转换及相关操作,然后再执行子容器的转态转换,这个过程是层层迭代执行的

HBase源码分析之MemStore的flush发起时机、判断条件等详情(二)

        在<HBase源码分析之MemStore的flush发起时机.判断条件等详情>一文中,我们详细介绍了MemStore flush的发起时机.判断条件等详情,主要是两类操作,一是会引起MemStore数据大小变化的Put.Delete.Append.Increment等操作,二是会引起HRegion变化的诸如Regin的分裂.合并以及做快照时的复制拷贝等,同样会触发MemStore的flush流程.同时,在<HBase源码分析之compact请求发起时机.判断条件等详情(一

Hhadoop-2.7.0中HDFS写文件源码分析(二):客户端实现(1)

一.综述       HDFS写文件是整个Hadoop中最为复杂的流程之一,它涉及到HDFS中NameNode.DataNode.DFSClient等众多角色的分工与合作.       首先上一段代码,客户端是如何写文件的: Configuration conf = new Configuration(); FileSystem fs = FileSystem.get(conf); Path file = new Path("demo.txt"); FSDataOutputStream