• java auth 随笔 0-shiro


    0. 感觉算是源码最简单的框架了吧,也可能是之前接触过了的原因…

    借助源码走读的方法,理一下shiro几个重要过程的脉络即可,还是想把重心放在spring-security


    渐进式的中文教程

    1. 简单过一下shiro的重要组件

    这张图都传烂了…

    请添加图片描述

    1.1 从Subject开始顺藤摸瓜

    	// org.apache.shiro.SecurityUtils#getSubject
    	public static Subject getSubject() {
            Subject subject = ThreadContext.getSubject();
            if (subject == null) {
                subject = (new Subject.Builder()).buildSubject();
                ThreadContext.bind(subject);
            }
            return subject;
        }
    	
    	// org.apache.shiro.util.ThreadContext#bind(org.apache.shiro.subject.Subject)
    	public static void bind(Subject subject) {
            if (subject != null) {
                put(SUBJECT_KEY, subject);
            }
        }
    	
    	// org.apache.shiro.util.ThreadContext#put
    	private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();
    	public static void put(Object key, Object value) {
            if (key == null) {
                throw new IllegalArgumentException("key cannot be null");
            }
    
            if (value == null) {
                remove(key);
                return;
            }
    
            resources.get().put(key, value);
    
            if (log.isTraceEnabled()) {
                String msg = "Bound value of type [" + value.getClass().getName() + "] for key [" +
                        key + "] to thread " + "[" + Thread.currentThread().getName() + "]";
                log.trace(msg);
            }
        }
    	
    
    • 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

    1.2 借助简单的登陆过程,过眼一下其他的几个组件

    	// org.apache.shiro.subject.support.DelegatingSubject#login
    	public void login(AuthenticationToken token) throws AuthenticationException {
            clearRunAsIdentitiesInternal();
            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;
            }
        }
    	
    	// org.apache.shiro.mgt.DefaultSecurityManager#login
    	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
            }
    
            Subject loggedIn = createSubject(token, info, subject);
    
            onSuccessfulLogin(token, info, loggedIn);
    
            return loggedIn;
        }
    	
    	// org.apache.shiro.authc.AbstractAuthenticator#authenticate
    	public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
    
            if (token == null) {
                throw new IllegalArgumentException("Method argumet (authentication token) cannot be null.");
            }
    
            log.trace("Authentication attempt received for token [{}]", token);
    
            AuthenticationInfo info;
            try {
                info = doAuthenticate(token);
                if (info == null) {
                    String msg = "No account information found for authentication token [" + token + "] by this " +
                            "Authenticator instance.  Please check that it is configured correctly.";
                    throw new AuthenticationException(msg);
                }
            } catch (Throwable t) {
                AuthenticationException ae = null;
                if (t instanceof AuthenticationException) {
                    ae = (AuthenticationException) t;
                }
                if (ae == null) {
                    //Exception thrown was not an expected AuthenticationException.  Therefore it is probably a little more
                    //severe or unexpected.  So, wrap in an AuthenticationException, log to warn, and propagate:
                    String msg = "Authentication failed for token submission [" + token + "].  Possible unexpected " +
                            "error? (Typical or expected login exceptions should extend from AuthenticationException).";
                    ae = new AuthenticationException(msg, t);
                }
                try {
                    notifyFailure(token, ae);
                } catch (Throwable t2) {
                    if (log.isWarnEnabled()) {
                        String msg = "Unable to send notification for failed authentication attempt - listener error?.  " +
                                "Please check your AuthenticationListener implementation(s).  Logging sending exception " +
                                "and propagating original AuthenticationException instead...";
                        log.warn(msg, t2);
                    }
                }
    
    
                throw ae;
            }
    
            log.debug("Authentication successful for token [{}].  Returned account [{}]", token, info);
    
            notifySuccess(token, info);
    
            return info;
        }
    	
    	// org.apache.shiro.authc.pam.ModularRealmAuthenticator#doAuthenticate
    	protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
            assertRealmsConfigured();
            Collection<Realm> realms = getRealms();
            if (realms.size() == 1) {
                return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
            } else {
                return doMultiRealmAuthentication(realms, authenticationToken);
            }
        }
    	
    	// org.apache.shiro.authc.pam.ModularRealmAuthenticator#doMultiRealmAuthentication
    	protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
    
            AuthenticationStrategy strategy = getAuthenticationStrategy();
    
            AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);
    
            if (log.isTraceEnabled()) {
                log.trace("Iterating through {} realms for PAM authentication", realms.size());
            }
    
            for (Realm realm : realms) {
    
                aggregate = strategy.beforeAttempt(realm, token, aggregate);
    
                if (realm.supports(token)) {
    
                    log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm);
    
                    AuthenticationInfo info = null;
                    Throwable t = null;
                    try {
                        info = realm.getAuthenticationInfo(token);
                    } catch (Throwable throwable) {
                        t = throwable;
                        if (log.isDebugEnabled()) {
                            String msg = "Realm [" + realm + "] threw an exception during a multi-realm authentication attempt:";
                            log.debug(msg, t);
                        }
                    }
    
                    aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);
    
                } else {
                    log.debug("Realm [{}] does not support token {}.  Skipping realm.", realm, token);
                }
            }
    
            aggregate = strategy.afterAllAttempts(token, aggregate);
    
            return aggregate;
        }
    	
    	// org.apache.shiro.realm.AuthenticatingRealm#getAuthenticationInfo
    	public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    
            AuthenticationInfo info = getCachedAuthenticationInfo(token);
            if (info == null) {
                //otherwise not cached, perform the lookup:
                info = doGetAuthenticationInfo(token);
                log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
                if (token != null && info != null) {
                    cacheAuthenticationInfoIfPossible(token, info);
                }
            } else {
                log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
            }
    
            if (info != null) {
                assertCredentialsMatch(token, info);
            } else {
                log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}].  Returning null.", token);
            }
    
            return info;
        }
    	
    -------------------------
    	
    	// org.apache.shiro.realm.SimpleAccountRealm#doGetAuthenticationInfo
    	// IniRealm extends TextConfigurationRealm
    	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            UsernamePasswordToken upToken = (UsernamePasswordToken) token;
            SimpleAccount account = getUser(upToken.getUsername());
    
            if (account != null) {
    
                if (account.isLocked()) {
                    throw new LockedAccountException("Account [" + account + "] is locked.");
                }
                if (account.isCredentialsExpired()) {
                    String msg = "The credentials for account [" + account + "] are expired";
                    throw new ExpiredCredentialsException(msg);
                }
    
            }
    
            return account;
        }
    	
    -------------------
    
    	// org.apache.shiro.realm.AuthenticatingRealm#assertCredentialsMatch
    	protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
            CredentialsMatcher cm = getCredentialsMatcher();
            if (cm != null) {
                if (!cm.doCredentialsMatch(token, info)) {
                    //not successful - throw an exception to indicate this:
                    String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
                    throw new IncorrectCredentialsException(msg);
                }
            } else {
                throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " +
                        "credentials during authentication.  If you do not wish for credentials to be examined, you " +
                        "can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
            }
        }
    	
    	// org.apache.shiro.authc.credential.PasswordMatcher#doCredentialsMatch
    	public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
    
            PasswordService service = ensurePasswordService();
    
            Object submittedPassword = getSubmittedPassword(token);
            Object storedCredentials = getStoredPassword(info);
            assertStoredCredentialsType(storedCredentials);
    
            if (storedCredentials instanceof Hash) {
                Hash hashedPassword = (Hash)storedCredentials;
                HashingPasswordService hashingService = assertHashingPasswordService(service);
                return hashingService.passwordsMatch(submittedPassword, hashedPassword);
            }
            //otherwise they are a String (asserted in the 'assertStoredCredentialsType' method call above):
            String formatted = (String)storedCredentials;
            return passwordService.passwordsMatch(submittedPassword, formatted);
        }
    	
    	// org.apache.shiro.authc.credential.DefaultPasswordService#passwordsMatch(java.lang.Object, java.lang.String)
    	public boolean passwordsMatch(Object submittedPlaintext, String saved) {
            ByteSource plaintextBytes = createByteSource(submittedPlaintext);
    
            if (saved == null || saved.length() == 0) {
                return plaintextBytes == null || plaintextBytes.isEmpty();
            } else {
                if (plaintextBytes == null || plaintextBytes.isEmpty()) {
                    return false;
                }
            }
    
            //First check to see if we can reconstitute the original hash - this allows us to
            //perform password hash comparisons even for previously saved passwords that don't
            //match the current HashService configuration values.  This is a very nice feature
            //for password comparisons because it ensures backwards compatibility even after
            //configuration changes.
            HashFormat discoveredFormat = this.hashFormatFactory.getInstance(saved);
    
            if (discoveredFormat != null && discoveredFormat instanceof ParsableHashFormat) {
    
                ParsableHashFormat parsableHashFormat = (ParsableHashFormat)discoveredFormat;
                Hash savedHash = parsableHashFormat.parse(saved);
    
                return passwordsMatch(submittedPlaintext, savedHash);
            }
    
            //If we're at this point in the method's execution, We couldn't reconstitute the original hash.
            //So, we need to hash the submittedPlaintext using current HashService configuration and then
            //compare the formatted output with the saved string.  This will correctly compare passwords,
            //but does not allow changing the HashService configuration without breaking previously saved
            //passwords:
    
            //The saved text value can't be reconstituted into a Hash instance.  We need to format the
            //submittedPlaintext and then compare this formatted value with the saved value:
            HashRequest request = createHashRequest(plaintextBytes);
            Hash computed = this.hashService.computeHash(request);
            String formatted = this.hashFormat.format(computed);
    
            return saved.equals(formatted);
        }
    	
    -------------------
    
    	// org.apache.shiro.authc.credential.SimpleCredentialsMatcher#doCredentialsMatch
    	public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
            Object tokenCredentials = getCredentials(token);
            Object accountCredentials = getCredentials(info);
            return equals(tokenCredentials, accountCredentials);
        }
    	
    	// org.apache.shiro.authc.credential.SimpleCredentialsMatcher#equals
    	protected boolean equals(Object tokenCredentials, Object accountCredentials) {
            if (log.isDebugEnabled()) {
                log.debug("Performing credentials equality check for tokenCredentials of type [" +
                        tokenCredentials.getClass().getName() + " and accountCredentials of type [" +
                        accountCredentials.getClass().getName() + "]");
            }
            if (isByteSource(tokenCredentials) && isByteSource(accountCredentials)) {
                if (log.isDebugEnabled()) {
                    log.debug("Both credentials arguments can be easily converted to byte arrays.  Performing " +
                            "array equals comparison");
                }
                byte[] tokenBytes = toBytes(tokenCredentials);
                byte[] accountBytes = toBytes(accountCredentials);
                return Arrays.equals(tokenBytes, accountBytes);
            } else {
                return accountCredentials.equals(tokenCredentials);
            }
        }
    
    • 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
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322

    2. 集成到web环境

    springMVC作为web框架,也有自己的逻辑,但这并不会与shiro集成冲突,shiro集成到web的做法是:接管webFilterChain

    请添加图片描述


    Shiro的Filter有两类同构(OncePerRequestFilter)的实现:

    • ShiroFilter:接管Web的FilterChain,并插入一段 shiro 的FilerChain
    • AdviceFilter:执行shiro认证逻辑

    请添加图片描述

    2.1 OncePerRequestFilter:shiro拦截器都是从这个入口开始

    // org.apache.shiro.web.servlet.OncePerRequestFilter
    public abstract class OncePerRequestFilter extends NameableFilter {
    
        public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
                throws ServletException, IOException {
            String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
    		
    		// 该filter已经被调用,则继续调用servletFilter(此时所有shiro的adviceFilter的FilterChain均执行完毕)
            if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
                log.trace("Filter '{}' already executed.  Proceeding without invoking this filter.", getName());
                filterChain.doFilter(request, response);
    			
    		// 不需要使用shiro-web权限控制
            } else //noinspection deprecation
                if (/* added in 1.2: */ !isEnabled(request, response) ||
                    /* retain backwards compatibility: */ shouldNotFilter(request) ) {
                log.debug("Filter '{}' is not enabled for the current request.  Proceeding without invoking this filter.",
                        getName());
                filterChain.doFilter(request, response);
    			
    		// 开始调度执行shiro的FilterChain
            } else {
                // Do invoke this filter...
                log.trace("Filter '{}' not yet executed.  Executing now.", getName());
                request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
    
                try {
            		// 由此可见 doFilter() -> doFilterInternal()
    				// 该方法由其子类AbstractShiroFilter实现
    				// 入参中的filterChain属于是servletFilterChain
                    doFilterInternal(request, response, filterChain);
                } finally {
                    // Once the request has finished, we're done and we don't
                    // need to mark as 'already filtered' any more.
                    request.removeAttribute(alreadyFilteredAttributeName);
                }
            }
        }
    
        protected abstract void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
                throws ServletException, IOException;
    }
    
    • 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

    2.1.1 ShiroFilter:插入一段Shiro的FilterChain

    // org.apache.shiro.web.servlet.AbstractShiroFilter
    public abstract class AbstractShiroFilter extends OncePerRequestFilter {
    
        // Reference to the security manager used by this filter
        private WebSecurityManager securityManager;
    
        // Used to determine which chain should handle an incoming request/response
        private FilterChainResolver filterChainResolver;
    
        protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
                throws ServletException, IOException {
    
            Throwable t = null;
    
            try {
                final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
                final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
    
    			// return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
                final Subject subject = createSubject(request, response);
    
                //noinspection unchecked
                subject.execute(new Callable() {
                    public Object call() throws Exception {
                        updateSessionLastAccessTime(request, response);
    					
                        executeChain(request, response, chain);
                        return null;
                    }
                });
            } catch (ExecutionException ex) {
                t = ex.getCause();
            } catch (Throwable throwable) {
                t = throwable;
            }
    
            if (t != null) {
                if (t instanceof ServletException) {
                    throw (ServletException) t;
                }
                if (t instanceof IOException) {
                    throw (IOException) t;
                }
                //otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one:
                String msg = "Filtered request failed.";
                throw new ServletException(msg, t);
            }
        }
    
        protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
                throws IOException, ServletException {
            FilterChain chain = getExecutionChain(request, response, origChain);
            chain.doFilter(request, response);
        }
    
        protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
            FilterChain chain = origChain;
    
            FilterChainResolver resolver = getFilterChainResolver();
            if (resolver == null) {
                log.debug("No FilterChainResolver configured.  Returning original FilterChain.");
                return origChain;
            }
    
    		// step into ...
            FilterChain resolved = resolver.getChain(request, response, origChain);
            if (resolved != null) {
                log.trace("Resolved a configured FilterChain for the current request.");
                chain = resolved;
            } else {
                log.trace("No FilterChain configured for the current request.  Using the default.");
            }
    
            return chain;
        }
    }
    
    • 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
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    2.1.1.1 PathMatchingFilterChainResolver:获取请求URL对应的校验AdviceFilterChain
    // package org.apache.shiro.web.filter.mgt;
    public class PathMatchingFilterChainResolver implements FilterChainResolver {
    
    	// shiroFilterChain的manager
        private FilterChainManager filterChainManager;
    
        private PatternMatcher pathMatcher;
    
        private static final String DEFAULT_PATH_SEPARATOR = "/";
    	
        public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
            FilterChainManager filterChainManager = getFilterChainManager();
            if (!filterChainManager.hasChains()) {
                return null;
            }
    
            String requestURI = getPathWithinApplication(request);
    
            // in spring web, the requestURI "/resource/menus" ---- "resource/menus/" bose can access the resource
            // but the pathPattern match "/resource/menus" can not match "resource/menus/"
            // user can use requestURI + "/" to simply bypassed chain filter, to bypassed shiro protect
            if(requestURI != null && !DEFAULT_PATH_SEPARATOR.equals(requestURI)
                    && requestURI.endsWith(DEFAULT_PATH_SEPARATOR)) {
                requestURI = requestURI.substring(0, requestURI.length() - 1);
            }
    
    
            //the 'chain names' in this implementation are actually path patterns defined by the user.  We just use them
            //as the chain name for the FilterChainManager's requirements
            for (String pathPattern : filterChainManager.getChainNames()) {
                if (pathPattern != null && !DEFAULT_PATH_SEPARATOR.equals(pathPattern)
                        && pathPattern.endsWith(DEFAULT_PATH_SEPARATOR)) {
                    pathPattern = pathPattern.substring(0, pathPattern.length() - 1);
                }
    
                // If the path does match, then pass on to the subclass implementation for specific checks:
                if (pathMatches(pathPattern, requestURI)) {
                    if (log.isTraceEnabled()) {
                        log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + Encode.forHtml(requestURI) + "].  " +
                                "Utilizing corresponding filter chain...");
                    }
    				
    				// step into ...
    				// url命中之后,返回其shiro认证的filter代理
                    return filterChainManager.proxy(originalChain, pathPattern);
                }
            }
    
            return null;
        }
    
        protected boolean pathMatches(String pattern, String path) {
            PatternMatcher pathMatcher = getPathMatcher();
            return pathMatcher.matches(pattern, path);
        }
    }
    
    • 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
    2.1.1.2 FilterChainManager:返回请求URL对应的Shiro的FilterChain的代理
    // package org.apache.shiro.web.filter.mgt;
    public class DefaultFilterChainManager implements FilterChainManager {
    
        private FilterConfig filterConfig;
    
        private Map<String, Filter> filters; //pool of filters available for creating chains
    
        private Map<String, NamedFilterList> filterChains; //key: chain name, value: chain
    
        public FilterChain proxy(FilterChain original, String chainName) {
            NamedFilterList configured = getChain(chainName);
            if (configured == null) {
                String msg = "There is no configured chain under the name/key [" + chainName + "].";
                throw new IllegalArgumentException(msg);
            }
            return configured.proxy(original);
        }
        
        public NamedFilterList getChain(String chainName) {
            return this.filterChains.get(chainName);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    2.1.1.3 NamedFilterList:Shiro的FilterChain底层使用的List
    // org.apache.shiro.web.filter.mgt.SimpleNamedFilterList
    public class SimpleNamedFilterList implements NamedFilterList {
    
        private String name;
        private List<Filter> backingList;
    
        public FilterChain proxy(FilterChain orig) {
            return new ProxiedFilterChain(orig, this);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    2.1.1.4 ProxiedFilterChain:Shiro的FilterChain代理类
    // package org.apache.shiro.web.servlet;
    public class ProxiedFilterChain implements FilterChain {
    
    	// servletFilterChain
        private FilterChain orig;
    	
        private List<Filter> filters;
    	
    	// 拦截器执行链的索引
        private int index = 0;
    
        public ProxiedFilterChain(FilterChain orig, List<Filter> filters) {
            if (orig == null) {
                throw new NullPointerException("original FilterChain cannot be null.");
            }
            this.orig = orig;
            this.filters = filters;
            this.index = 0;
        }
    
        public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
            if (this.filters == null || this.filters.size() == this.index) {
                //we've reached the end of the wrapped chain, so invoke the original one:
                if (log.isTraceEnabled()) {
                    log.trace("Invoking original filter chain.");
                }
    			// 调用servletFilter
                this.orig.doFilter(request, response);
            } else {
                if (log.isTraceEnabled()) {
                    log.trace("Invoking wrapped filter at index [" + this.index + "]");
                }
    			// 调用shiroFilter
                this.filters.get(this.index++).doFilter(request, response, this);
            }
        }
    }
    
    • 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

    2.1.2 AdviceFilter:Shiro校验逻辑的Filter

    这个类命名应该也是想传递源码中的AOP风格

    // package org.apache.shiro.web.servlet;
    public abstract class AdviceFilter extends OncePerRequestFilter {
    
        protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
            return true;
        }
    
        protected void postHandle(ServletRequest request, ServletResponse response) throws Exception {
        }
    
        public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception {
        }
    
    	// 两种类型的OncePerRequestFilter的子类均实现该方法(废话):
    	// doFilter() -> doFilterInternal()
        public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
                throws ServletException, IOException {
    
            Exception exception = null;
    
            try {
    
                boolean continueChain = preHandle(request, response);
                if (log.isTraceEnabled()) {
                    log.trace("Invoked preHandle method.  Continuing chain?: [" + continueChain + "]");
                }
    
                if (continueChain) {
    				// shiroFilterChain的演进方法
    				// 调用下一个拦截器:doFilter() -> doFilterInternal() ...
                    executeChain(request, response, chain);
                }
    
                postHandle(request, response);
                if (log.isTraceEnabled()) {
                    log.trace("Successfully invoked postHandle method");
                }
    
            } catch (Exception e) {
                exception = e;
            } finally {
    			// afterCompletion回调方法在这里被调用
                cleanup(request, response, exception);
            }
        }
    
    	protected void executeChain(ServletRequest request, ServletResponse response, FilterChain chain) throws Exception {
            chain.doFilter(request, response);
        }
    
        protected void cleanup(ServletRequest request, ServletResponse response, Exception existing)
                throws ServletException, IOException {
            Exception exception = existing;
            try {
                afterCompletion(request, response, exception);
                if (log.isTraceEnabled()) {
                    log.trace("Successfully invoked afterCompletion method.");
                }
            } catch (Exception e) {
                if (exception == null) {
                    exception = e;
                } else {
                    log.debug("afterCompletion implementation threw an exception.  This will be ignored to " +
                            "allow the original source exception to be propagated.", e);
                }
            }
            if (exception != null) {
                if (exception instanceof ServletException) {
                    throw (ServletException) exception;
                } else if (exception instanceof IOException) {
                    throw (IOException) exception;
                } else {
                    if (log.isDebugEnabled()) {
                        String msg = "Filter execution resulted in an unexpected Exception " +
                                "(not IOException or ServletException as the Filter API recommends).  " +
                                "Wrapping in ServletException and propagating.";
                        log.debug(msg);
                    }
                    throw new ServletException(exception);
                }
            }
        }
    }
    
    • 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
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    3. 集成到Spring-ioc容器

    springMvc,其实是 WebApplicationContext

    // org.apache.shiro.spring.web.ShiroFilterFactoryBean
    public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor {
    	public Object getObject() throws Exception {
            if (instance == null) {
    		
    			// step into ...
                instance = createInstance();
            }
            return instance;
        }
    	
    	protected AbstractShiroFilter createInstance() throws Exception {
    
            log.debug("Creating Shiro Filter instance.");
    
    		// 这个manager是webSecurityManager,管理权限认证的
            SecurityManager securityManager = getSecurityManager();
            if (securityManager == null) {
                String msg = "SecurityManager property must be set.";
                throw new BeanInitializationException(msg);
            }
    
            if (!(securityManager instanceof WebSecurityManager)) {
                String msg = "The security manager does not implement the WebSecurityManager interface.";
                throw new BeanInitializationException(msg);
            }
    
    		// 这个manager的作用是:SpringShiroFilter作为OncePerRequestFilter
    		// 作为所有请求的入口时,接管FilterChain的调度
    		// 换了方式说:servlet的默认FilterChain将委托给SpringShrioFilter来调度                  
            FilterChainManager manager = createFilterChainManager();
    
            //Expose the constructed FilterChainManager by first wrapping it in a
            // FilterChainResolver implementation. The AbstractShiroFilter implementations
            // do not know about FilterChainManagers - only resolvers:
            PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
            chainResolver.setFilterChainManager(manager);
    
            //Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built
            //FilterChainResolver.  It doesn't matter that the instance is an anonymous inner class
            //here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts
    		
    		// step into ...
            //injection of the SecurityManager and FilterChainResolver:
            return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
        }
    	
    	private static final class SpringShiroFilter extends AbstractShiroFilter {
            protected SpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {
                super();
                if (webSecurityManager == null) {
                    throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
                }
                setSecurityManager(webSecurityManager);
                if (resolver != null) {
                    setFilterChainResolver(resolver);
                }
            }
        }
    }
    
    • 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
    • 57
    • 58
    • 59
    • 60
  • 相关阅读:
    机器学习基础之《分类算法(8)—随机森林》
    用户眼中的 tensor、TensorFlow 系统中的 tensor、tensor 高阶用法 DLPack
    光敏传感器模块(YH-LDR)
    没有废话-MySQL,MyBatis 动态参数添加序号值(默认递增或根据内容进行递增)
    华为云云耀云服务器L实例评测|部署在线图表和流程图绘制工具drawio
    扩展的多曝光图像合成算法及其在单幅图像增强中的应用。
    小工具使用——瑞士军刀(nc.exe)实现远程控制终端操作
    Java8(JDK1.8)新特性
    一口气说完网络安全设备的功能和作用
    力扣(145.)补9.5
  • 原文地址:https://blog.csdn.net/weixin_43638238/article/details/127710115