了解 SecurityManger
继承图
在SecurityManager接口名称上按 Ctrl+H,显示它的继承层次
在DefaultWebSecurityManager类名称上按 Shift+Ctrl+Alt+U,显示它的继承图
SecurityManager

CachingSecurityManager

RealmSecurityManager

AuthenticatingSecurityManager

AuthorizingSecurityManager

SessionSecurityManager

DefaultSecurityManager

方法看上去很多,但我们只需关注其对顶层接口 SecurityManager 方法的实现
DefaultSecurityManager

小结
SecurityManager的继承结构大量使用了 装饰模式
理解 SecurityManager
从顶层接口 SecurityManger 暴露出的三个方法来入手,分别解析它们的逻辑流程

login(Subject, AuthenticationToken): Subject
1. login(subject, token) | DefaultSecurityManager
1 | // line 259 |
2 | /** |
3 | * First authenticates the {@code AuthenticationToken} argument, and if successful, constructs a |
4 | * {@code Subject} instance representing the authenticated account's identity. |
5 | * <p/> |
6 | * Once constructed, the {@code Subject} instance is then {@link #bind bound} to the application for |
7 | * subsequent access before being returned to the caller. |
8 | * |
9 | * @param token the authenticationToken to process for the login attempt. |
10 | * @return a Subject representing the authenticated user. |
11 | * @throws AuthenticationException if there is a problem authenticating the specified {@code token}. |
12 | */ |
13 | public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException { |
14 | AuthenticationInfo info; |
15 | try { |
16 | info = authenticate(token); |
17 | } catch (AuthenticationException ae) { |
18 | try { |
19 | onFailedLogin(token, ae, subject); |
20 | } catch (Exception e) { |
21 | if (log.isInfoEnabled()) { |
22 | log.info("onFailedLogin method threw an " + |
23 | "exception. Logging and propagating original AuthenticationException.", e); |
24 | } |
25 | } |
26 | throw ae; //propagate |
27 | } |
28 | |
29 | Subject loggedIn = createSubject(token, info, subject); |
30 | |
31 | onSuccessfulLogin(token, info, loggedIn); |
32 | |
33 | return loggedIn; |
34 | } |
2. authencate(token) | AuthenticatingSecurityManager
1 | // line 50 |
2 | /** |
3 | * Default no-arg constructor that initializes its internal |
4 | * <code>authenticator</code> instance to a |
5 | * {@link org.apache.shiro.authc.pam.ModularRealmAuthenticator ModularRealmAuthenticator}. |
6 | */ |
7 | public AuthenticatingSecurityManager() { |
8 | super(); |
9 | // 默认 Authenticator |
10 | this.authenticator = new ModularRealmAuthenticator(); |
11 | } |
12 | ...... |
13 | // line 101 |
14 | /** |
15 | * Delegates to the wrapped {@link org.apache.shiro.authc.Authenticator Authenticator} for authentication. |
16 | */ |
17 | public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException { |
18 | return this.authenticator.authenticate(token); |
19 | } |
authenticator.authenticate(token) 的解析请参考 Shiro源码解析之Authenticator
3. createSubject(token, info, subject) | DefaultSecurityManager
1 | // line 166 |
2 | protected SubjectContext createSubjectContext() { |
3 | return new DefaultSubjectContext(); |
4 | } |
5 | /** |
6 | * Creates a {@code Subject} instance for the user represented by the given method arguments. |
7 | * |
8 | * @param token the {@code AuthenticationToken} submitted for the successful authentication. |
9 | * @param info the {@code AuthenticationInfo} of a newly authenticated user. |
10 | * @param existing the existing {@code Subject} instance that initiated the authentication attempt |
11 | * @return the {@code Subject} instance that represents the context and session data for the newly |
12 | * authenticated subject. |
13 | */ |
14 | protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) { |
15 | SubjectContext context = createSubjectContext(); |
16 | context.setAuthenticated(true); |
17 | // 将 token、authenticationInfo、subject 存入 subjectContext 的 backingMap 中 |
18 | context.setAuthenticationToken(token); |
19 | context.setAuthenticationInfo(info); |
20 | if (existing != null) { |
21 | context.setSubject(existing); |
22 | } |
23 | return createSubject(context); |
24 | } |
关于createSubject(context) 解析参考 createSubject(subjectContext): Subject: Subject)
createSubject(subjectContext): Subject
1. createSubject(subjectContext) | DefaultSecurityManager
1 | /** |
2 | * This implementation functions as follows: |
3 | * <p/> |
4 | * <ol> |
5 | * <li>Ensures the {@code SubjectContext} is as populated as it can be, using heuristics to acquire |
6 | * data that may not have already been available to it (such as a referenced session or remembered principals).</li> |
7 | * <li>Calls {@link #doCreateSubject(org.apache.shiro.subject.SubjectContext)} to actually perform the |
8 | * {@code Subject} instance creation.</li> |
9 | * <li>calls {@link #save(org.apache.shiro.subject.Subject) save(subject)} to ensure the constructed |
10 | * {@code Subject}'s state is accessible for future requests/invocations if necessary.</li> |
11 | * <li>returns the constructed {@code Subject} instance.</li> |
12 | * </ol> |
13 | * |
14 | * @param subjectContext any data needed to direct how the Subject should be constructed. |
15 | * @return the {@code Subject} instance reflecting the specified contextual data. |
16 | * @see #ensureSecurityManager(org.apache.shiro.subject.SubjectContext) |
17 | * @see #resolveSession(org.apache.shiro.subject.SubjectContext) |
18 | * @see #resolvePrincipals(org.apache.shiro.subject.SubjectContext) |
19 | * @see #doCreateSubject(org.apache.shiro.subject.SubjectContext) |
20 | * @see #save(org.apache.shiro.subject.Subject) |
21 | * @since 1.0 |
22 | */ |
23 | public Subject createSubject(SubjectContext subjectContext) { |
24 | //create a copy so we don't modify the argument's backing map: |
25 | SubjectContext context = copy(subjectContext); |
26 | |
27 | //ensure that the context has a SecurityManager instance, and if not, add one: |
28 | context = ensureSecurityManager(context); |
29 | |
30 | //Resolve an associated Session (usually based on a referenced session ID), and place it in the context before |
31 | //sending to the SubjectFactory. The SubjectFactory should not need to know how to acquire sessions as the |
32 | //process is often environment specific - better to shield the SF from these details: |
33 | context = resolveSession(context); |
34 | |
35 | //Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first |
36 | //if possible before handing off to the SubjectFactory: |
37 | context = resolvePrincipals(context); |
38 | |
39 | Subject subject = doCreateSubject(context); |
40 | |
41 | //save this subject for future reference if necessary: |
42 | //(this is needed here in case rememberMe principals were resolved and they need to be stored in the |
43 | //session, so we don't constantly rehydrate the rememberMe PrincipalCollection on every operation). |
44 | //Added in 1.2: |
45 | save(subject); |
46 | |
47 | return subject; |
48 | } |
49 | |
50 | /** |
51 | * Actually creates a {@code Subject} instance by delegating to the internal |
52 | * {@link #getSubjectFactory() subjectFactory}. By the time this method is invoked, all possible |
53 | * {@code SubjectContext} data (session, principals, et. al.) has been made accessible using all known heuristics |
54 | * and will be accessible to the {@code subjectFactory} via the {@code subjectContext.resolve*} methods. |
55 | * |
56 | * @param context the populated context (data map) to be used by the {@code SubjectFactory} when creating a |
57 | * {@code Subject} instance. |
58 | * @return a {@code Subject} instance reflecting the data in the specified {@code SubjectContext} data map. |
59 | * @see #getSubjectFactory() |
60 | * @see SubjectFactory#createSubject(org.apache.shiro.subject.SubjectContext) |
61 | * @since 1.2 |
62 | */ |
63 | protected Subject doCreateSubject(SubjectContext context) { |
64 | return getSubjectFactory().createSubject(context); |
65 | } |
66 | |
67 | /** |
68 | * Saves the subject's state to a persistent location for future reference if necessary. |
69 | * <p/> |
70 | * This implementation merely delegates to the internal {@link #setSubjectDAO(SubjectDAO) subjectDAO} and calls |
71 | * {@link SubjectDAO#save(org.apache.shiro.subject.Subject) subjectDAO.save(subject)}. |
72 | * |
73 | * @param subject the subject for which state will potentially be persisted |
74 | * @see SubjectDAO#save(org.apache.shiro.subject.Subject) |
75 | * @since 1.2 |
76 | */ |
77 | protected void save(Subject subject) { |
78 | this.subjectDAO.save(subject); |
79 | } |
createSubject(subjectContext) 中那几个 resolveXxx 方法的解析,请看 Shiro源码解析之Subject和SubjectContext
2. getSubjectFacotry().createSubject()
这个方法在 DefaultSubjectFactory、DefaultWebSubjectFactory 分别有一个实现,如下:
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 | } |
1 | public Subject createSubject(SubjectContext context) { |
2 | if (!(context instanceof WebSubjectContext)) { |
3 | return super.createSubject(context); |
4 | } |
5 | WebSubjectContext wsc = (WebSubjectContext) context; |
6 | SecurityManager securityManager = wsc.resolveSecurityManager(); |
7 | Session session = wsc.resolveSession(); |
8 | boolean sessionEnabled = wsc.isSessionCreationEnabled(); |
9 | PrincipalCollection principals = wsc.resolvePrincipals(); |
10 | boolean authenticated = wsc.resolveAuthenticated(); |
11 | String host = wsc.resolveHost(); |
12 | // web 应用特有的 |
13 | ServletRequest request = wsc.resolveServletRequest(); |
14 | ServletResponse response = wsc.resolveServletResponse(); |
15 | |
16 | return new WebDelegatingSubject(principals, authenticated, host, session, sessionEnabled, |
17 | request, response, securityManager); |
18 | } |
通过 debug 我们来看一下,第一次 getSubject() 得到的 subject 对象里包含哪些属性

顺便看一下 SecurityManager 中的属性方法

3. save(subject): void | DefaultSecurityManager
这个方法调用了 this.subjectDAO.save(subject)
1 | // line 133 |
2 | /** |
3 | * Saves the subject's state to the subject's {@link org.apache.shiro.subject.Subject#getSession() session} only |
4 | * if {@link #isSessionStorageEnabled(Subject) sessionStorageEnabled(subject)}. If session storage is not enabled |
5 | * for the specific {@code Subject}, this method does nothing. |
6 | * <p/> |
7 | * In either case, the argument {@code Subject} is returned directly (a new Subject instance is not created). |
8 | * |
9 | * @param subject the Subject instance for which its state will be created or updated. |
10 | * @return the same {@code Subject} passed in (a new Subject instance is not created). |
11 | */ |
12 | public Subject save(Subject subject) { |
13 | if (isSessionStorageEnabled(subject)) { |
14 | saveToSession(subject); |
15 | } else { |
16 | log.trace("Session storage of subject state for Subject [{}] has been disabled: identity and " + |
17 | "authentication state are expected to be initialized on every request or invocation.", subject); |
18 | } |
19 | |
20 | return subject; |
21 | } |
22 | |
23 | /** |
24 | * Saves the subject's state (it's principals and authentication state) to its |
25 | * {@link org.apache.shiro.subject.Subject#getSession() session}. The session can be retrieved at a later time |
26 | * (typically from a {@link org.apache.shiro.session.mgt.SessionManager SessionManager} to be used to recreate |
27 | * the {@code Subject} instance. |
28 | * |
29 | * @param subject the subject for which state will be persisted to its session. |
30 | */ |
31 | protected void saveToSession(Subject subject) { |
32 | //performs merge logic, only updating the Subject's session if it does not match the current state: |
33 | mergePrincipals(subject); |
34 | mergeAuthenticationState(subject); |
35 | } |
36 | |
37 | // line 172 |
38 | /** |
39 | * Merges the Subject's current {@link org.apache.shiro.subject.Subject#getPrincipals()} with whatever may be in |
40 | * any available session. Only updates the Subject's session if the session does not match the current principals |
41 | * state. |
42 | * |
43 | * @param subject the Subject for which principals will potentially be merged into the Subject's session. |
44 | */ |
45 | protected void mergePrincipals(Subject subject) { |
46 | //merge PrincipalCollection state: |
47 | |
48 | PrincipalCollection currentPrincipals = null; |
49 | |
50 | //SHIRO-380: added if/else block - need to retain original (source) principals |
51 | //This technique (reflection) is only temporary - a proper long term solution needs to be found, |
52 | //but this technique allowed an immediate fix that is API point-version forwards and backwards compatible |
53 | // |
54 | //A more comprehensive review / cleaning of runAs should be performed for Shiro 1.3 / 2.0 + |
55 | if (subject.isRunAs() && subject instanceof DelegatingSubject) { |
56 | try { |
57 | Field field = DelegatingSubject.class.getDeclaredField("principals"); |
58 | field.setAccessible(true); |
59 | currentPrincipals = (PrincipalCollection)field.get(subject); |
60 | } catch (Exception e) { |
61 | throw new IllegalStateException("Unable to access DelegatingSubject principals property.", e); |
62 | } |
63 | } |
64 | if (currentPrincipals == null || currentPrincipals.isEmpty()) { |
65 | currentPrincipals = subject.getPrincipals(); |
66 | } |
67 | |
68 | Session session = subject.getSession(false); |
69 | |
70 | if (session == null) { |
71 | if (!isEmpty(currentPrincipals)) { |
72 | session = subject.getSession(); |
73 | session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals); |
74 | } |
75 | // otherwise no session and no principals - nothing to save |
76 | } else { |
77 | PrincipalCollection existingPrincipals = |
78 | (PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY); |
79 | |
80 | if (isEmpty(currentPrincipals)) { |
81 | if (!isEmpty(existingPrincipals)) { |
82 | session.removeAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY); |
83 | } |
84 | // otherwise both are null or empty - no need to update the session |
85 | } else { |
86 | if (!currentPrincipals.equals(existingPrincipals)) { |
87 | session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals); |
88 | } |
89 | // otherwise they're the same - no need to update the session |
90 | } |
91 | } |
92 | } |
93 | |
94 | /** |
95 | * Merges the Subject's current authentication state with whatever may be in |
96 | * any available session. Only updates the Subject's session if the session does not match the current |
97 | * authentication state. |
98 | * |
99 | * @param subject the Subject for which principals will potentially be merged into the Subject's session. |
100 | */ |
101 | protected void mergeAuthenticationState(Subject subject) { |
102 | |
103 | Session session = subject.getSession(false); |
104 | |
105 | if (session == null) { |
106 | if (subject.isAuthenticated()) { |
107 | session = subject.getSession(); |
108 | session.setAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY, Boolean.TRUE); |
109 | } |
110 | //otherwise no session and not authenticated - nothing to save |
111 | } else { |
112 | Boolean existingAuthc = (Boolean) session.getAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY); |
113 | |
114 | if (subject.isAuthenticated()) { |
115 | if (existingAuthc == null || !existingAuthc) { |
116 | session.setAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY, Boolean.TRUE); |
117 | } |
118 | //otherwise authc state matches - no need to update the session |
119 | } else { |
120 | if (existingAuthc != null) { |
121 | //existing doesn't match the current state - remove it: |
122 | session.removeAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY); |
123 | } |
124 | //otherwise not in the session and not authenticated - no need to update the session |
125 | } |
126 | } |
127 | } |
理解 subject.login()
从众多 SecurityManager 的方法图中可知,只有 DefaultSecurityManager 实现了 login() 方法,所以从它的源码开始解析。
*1. DefaultSecurityManager | login(token) *
1 | // line 259 |
2 | /** |
3 | * First authenticates the {@code AuthenticationToken} argument, and if successful, constructs a |
4 | * {@code Subject} instance representing the authenticated account's identity. |
5 | * <p/> |
6 | * Once constructed, the {@code Subject} instance is then {@link #bind bound} to the application for |
7 | * subsequent access before being returned to the caller. |
8 | * |
9 | * @param token the authenticationToken to process for the login attempt. |
10 | * @return a Subject representing the authenticated user. |
11 | * @throws AuthenticationException if there is a problem authenticating the specified {@code token}. |
12 | */ |
13 | public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException { |
14 | AuthenticationInfo info; |
15 | try { |
16 | info = authenticate(token); |
17 | } catch (AuthenticationException ae) { |
18 | try { |
19 | onFailedLogin(token, ae, subject); |
20 | } catch (Exception e) { |
21 | if (log.isInfoEnabled()) { |
22 | log.info("onFailedLogin method threw an " + |
23 | "exception. Logging and propagating original AuthenticationException.", e); |
24 | } |
25 | } |
26 | throw ae; //propagate |
27 | } |
28 | |
29 | Subject loggedIn = createSubject(token, info, subject); |
30 | |
31 | onSuccessfulLogin(token, info, loggedIn); |
32 | |
33 | return loggedIn; |
34 | } |
** 2. AuthenticatingSecurityManager | authenticate(token) **
** 3. ModularRealmAuthenticator | authenticate(token)
查看源码的时候,一直弄不懂 ModularRealmAuthenticator 对象中的 Collection<Realm> realms 是什么时候赋值的。
因为我们只是 securityManager.setRealm(userRealm) 这样的方式来填充 realm,但是是什么时候给你 Authenticator 填充 realm 的呢?
通过debug才发现,AuthenticatingSecurityManager 重写了父类 RealmSecurityManger 的 afterRealmsSet() 方法,在里面给 authenticator 填充了 realms
1 | // line 90 |
2 | /** |
3 | * Passes on the {@link #getRealms() realms} to the internal delegate <code>Authenticator</code> instance so |
4 | * that it may use them during authentication attempts. |
5 | */ |
6 | protected void afterRealmsSet() { |
7 | super.afterRealmsSet(); |
8 | if (this.authenticator instanceof ModularRealmAuthenticator) { |
9 | ((ModularRealmAuthenticator) this.authenticator).setRealms(getRealms()); |
10 | } |
11 | } |