学习 准备 尝试 谨慎小心

0%

Shiro源码解析之SecurityManager

了解 SecurityManger

继承图

SecurityManager接口名称上按 Ctrl+H,显示它的继承层次
1571642320953

DefaultWebSecurityManager类名称上按 Shift+Ctrl+Alt+U,显示它的继承图
1571642933389

SecurityManager

SecurityManger结构

CachingSecurityManager

1571707959571

RealmSecurityManager

1571708033085

AuthenticatingSecurityManager

1571708079963

AuthorizingSecurityManager

1571708160461

SessionSecurityManager

1571708232562

DefaultSecurityManager

1571708376374

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

DefaultSecurityManager

1571708790577

小结

SecurityManager的继承结构大量使用了 装饰模式

理解 SecurityManager

从顶层接口 SecurityManger 暴露出的三个方法来入手,分别解析它们的逻辑流程

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()

这个方法在 DefaultSubjectFactoryDefaultWebSubjectFactory 分别有一个实现,如下:

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 对象里包含哪些属性

image-20191025104928852

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

image-20191025110723800

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 重写了父类 RealmSecurityMangerafterRealmsSet() 方法,在里面给 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
}