shiro source learning record

Some of the pictures can't be seen clearly. There are: https://github.com/nice01qc/s...
GitHub resources have been uploaded

shiro Introduction (personal understanding)

shiro is a framework for managing user rights. Here is my reading process, and some small summary, and the source code can be run. If shiro is compared to a samurai, the security manager is the samurai's knife, the shiro Filter is the samurai, a tool and a user. It can be seen that these two aspects are very important. This article provides the explanation of these two aspects and a source reading process, which is a bit rough. But if you want to read shiro source code, GitHub has configured source code, ready-made springboot examples and a single sign on model.

Project Overview:

Clone the whole project from shiro Official warehouse Only 2 small projects have been added to the subproject sales for debug ging.

  • Shiro? Learn (project root path)

    • samples

      • Shiro ﹣ cas ﹣ service uses springboot to simply simulate cas service (you can fully understand cas service request process through debug ging)
      • At the beginning of source code analysis, most of shiro's configurations have been configured

This paper will analyze from three aspects:

  1. shiro configuration (very complete)
  2. Shiro web filter loading process
  3. Complete analysis of a shiro cas request

shiro configuration

The figure above shows all the functions of shiro. From the figure above, it can be seen that it is mainly divided into two parts,

One is Security Manager. This module and authorization verification are integrated (the corresponding class is the subclass of SecurityManager), and external services are provided through the subject interface, such as login, authorization and so on. It is good to use the subject (for specific use, detailed analysis will be made later);

The other is the user, that is, the SecurityManager has been configured. How should the third party use it? This paper describes the third party, Web MVC, which blocks all user requests through the Filter filter, and then distributes them to the corresponding shiro Filter for corresponding processing (for example, check whether the request has login or permission), Use the Subject interface provided by the SecurityManager in the Filter to perform corresponding operations.

SecurityManager configuration

From the above figure, you can see which attributes can be configured by DefaultSecurityManager. The following table explains one by one:

attribute Is there a default configuration Default configuration class Function description Common configuration
subjectFactory yes DefaultSubjectFactory Specific implementation class of Subject (external service interface, if it is a cas service, it is recommended to use CasSubjectFactory override) no
subjectDAO yes DefaultSubjectDAO It is mainly used to save the latest information in the subject to the session no
rememberMeManager Only in DefaultWebSecurityManager CookieRememberMeManager It is used to manage the rememberMe cookie. Generally, it is not used no
sessionManager yes Defaultsessionmanager (defaultwebsecurity manager is DefaultWebSessionManager) All operations related to session will be delegated to him (he can also configure it himself, as shown in the following table) yes
authorizer yes ModularRealmAuthorizer Authorization policy (when there are multiple realm s, you can set your own policy) no
authenticator yes ModularRealmAuthenticator Authentication policy (you can set your own policy when there are multiple realm s) yes
realm no CasRealm,JdbcRealm ...... To implement the authentication and authorization operations, you need to configure it yourself (I will give you an example later) yes
cacheManager no It is better for users to inherit AbstractCacheManager (support shiro default cycle management) It is used in realm authentication and authorization (equivalent to adding a layer of cache, cas authentication is not needed. If it is a user name and password, it can be used to increase the speed of authentication and authorization) No (cas doesn't need it)

DefaultSecurityManager's SessionManager property configuration:

attribute Is there a default configuration Default configuration class Function description Common configuration
sessionFactory yes SimpleSessionFactory Used to create Session, generally not configured no
sessionDAO yes MemorySessionDAO The interface used to save the Session. Generally, you can inherit the AbstractSessionDAO class and use redis to reconfigure one to save the Session in redis (this abstract class can configure its own sessionidgenerator = > to generate the session id) yes
casheManager no It can be used in combination with the sessionDAO that implements the CashManagerAware class. Generally, the sessionDAO configuration is complete no
sessionIdCookie yes new SimpleCookie("JSESSIONID"); This exists in the subclass DefaultWebSessionManager of this class, which is generally reconfigured so that the cookie name can be changed to its own (generally, the underlying operations of the cookie are entrusted to him, such as reading the value of the cookie, configuring the cookie...) yes

Note: these properties can be set through the set method of the object

Web MVC filter configuration

DefaultWebSecurityManager is the same as DefaultSecurityManager configuration. The following securityManager refers to DefaultWebSecurityManager.

Now that the defaultwebsecurity manager has been configured, where should I put it? It must be applied to shiro filter.

shiro filter related configurations are stored in the ShiroFilterFactoryBean class, and then the DelegatingFilterProxy is used to inject ShiroFilterFactoryBean into the web container.

  • If it's springmvc, just configure the DelegatingFilterProxy directly in web.xml. The filter name is shirofilterfactory bean;
  • If it's a springboot, it's good to use the filter registration bean of springboot to register (servlet3.0 can use ServletContext to register, and you can register servlets and filters...) = > I'll talk about it in detail later

ShiroFilterFactoryBean. It can be seen from its name that it is not a real Filter. It is used to configure and manage shirofilter.

As you can see from the above figure, the SpringShiroFilter object is returned through FactoryBean. ShiroFilterFactoryBean configuration description is as follows:

attribute Function description
securityManager shiro core
filters (Map<String, Filter>) Put the custom shiro Filter in this map, which is generally used in combination with the following definition
filterChainDefinitionMap (Map<String, String>) Map < request url expression, filter name used (multiple separated by commas) >
loginUrl Login URL
successUrl The URL to jump after successful login (this jump will not be used in general, but the URL saved during the first access will be used)
unauthorizedUrl URL for unsuccessful authorization jump

All the above configuration examples are placed in the class Shiro ﹣ learn / samples / Shiro ﹣ client / SRC / main / Java / COM / nice01qc / config / Shiro / shirocasconfig.java, which can be viewed correspondingly.

Shiro web filter loading process

This loading process is actually the ShiroFilterFactoryBean loading process

Now that the security manager has been manually configured, there is nothing to say. let's coding......, all source codes, only point out the key nodes, and implement the specific details by yourself.

ShiroFilterFactoryBean.java loading process

public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor {
    
    // That's where you start (if you don't know, search for the role of FactoryBean)
    public Object getObject() throws Exception {
        if (instance == null) {
            instance = createInstance();  //Just look at this
        }
        return instance;
    }
    
 
    protected AbstractShiroFilter createInstance() throws Exception {
        SecurityManager securityManager = getSecurityManager();
        // Encapsulate Filter call management, continue to deepen
        FilterChainManager manager = createFilterChainManager();
        
        PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
        chainResolver.setFilterChainManager(manager);
        
        //Create a real shiro filter with a global overview here
        return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
    }
    
    protected FilterChainManager createFilterChainManager() {
        DefaultFilterChainManager manager = new DefaultFilterChainManager();
        // Get the default Filter from the DefaultFilter enumeration class (12 in total)
        Map<String, Filter> defaultFilters = manager.getFilters(); 
        // Fill loginUrl, successUrl, unauthorizedUrl into a qualified filter
        for (Filter filter : defaultFilters.values()) {
            applyGlobalPropertiesIfNecessary(filter);
        }

        //This is your own Filter
        Map<String, Filter> filters = getFilters();
        if (!CollectionUtils.isEmpty(filters)) {
            for (Map.Entry<String, Filter> entry : filters.entrySet()) {
                String name = entry.getKey();
                Filter filter = entry.getValue();
                applyGlobalPropertiesIfNecessary(filter); // Fill a wave
                if (filter instanceof Nameable) {
                    ((Nameable) filter).setName(name);
                }
                //'init' argument is false, since Spring-configured filters should be initialized
                //in Spring (i.e. 'init-method=blah') or implement InitializingBean:
                manager.addFilter(name, filter, false);
            }
        }

        //build up the chains:
        Map<String, String> chains = getFilterChainDefinitionMap(); // filterChainDefinitionMap
        if (!CollectionUtils.isEmpty(chains)) {
            for (Map.Entry<String, String> entry : chains.entrySet()) {
                // For example, filterChainDefinitionMap.put("/index", "authc[config1,config2]");
                String url = entry.getKey();    // url is "/ index"
                String chainDefinition = entry.getValue(); // "authc[config1,config2]"
                // The "[config1,config2]" of chainDefinition will be parsed and encapsulated in the corresponding filter of authc, which will be used later
                manager.createChain(url, chainDefinition);
            }
        }
        return manager;
    }
    
    // Fill in the three values
    private void applyGlobalPropertiesIfNecessary(Filter filter) {
        applyLoginUrlIfNecessary(filter);
        applySuccessUrlIfNecessary(filter);
        applyUnauthorizedUrlIfNecessary(filter);
    }
    private void applyLoginUrlIfNecessary(Filter filter) {
        String loginUrl = getLoginUrl();
        if (StringUtils.hasText(loginUrl) && (filter instanceof AccessControlFilter)) {
            AccessControlFilter acFilter = (AccessControlFilter) filter;
            String existingLoginUrl = acFilter.getLoginUrl();
            if (AccessControlFilter.DEFAULT_LOGIN_URL.equals(existingLoginUrl)) {
                acFilter.setLoginUrl(loginUrl);
            }
        }
    }
}

The above is the initialization process of ShiroFilterFactoryBean class. Subsequently, DelegatingFilterProxy obtains the bean through getBean("ShiroFilterFactoryBean's bean name"), and delegates the request to him.

shiro Filter inheritance system

Before going further, I'll introduce the features of shiro filter, that is, you want to implement filters with different functions by inheriting the filter provided by shiro, and then make changes on this basis.

Each of the above abstract classes has different functions. The division of labor is clear. The next one is to analyze (the loginUrl above was injected when it was initialized): = > this is very important

AbstractFilter.java

// It implements the init interface of Filter and exposes the onFilterConfigSet interface
public abstract class AbstractFilter extends ServletContextSupport implements Filter {
    
    // The init interface of Filter is implemented, and the onFilterConfigSet interface is exposed. This interface covers this method in AbstractShiroFilter, where AbstractShiroFilter is the parent class of SpringShiroFilter, the parent class of SpringShiroFilter, and the parent class of SpringShiroFilter
    public final void init(FilterConfig filterConfig) throws ServletException {
        setFilterConfig(filterConfig);
        try {
            onFilterConfigSet();
        } catch (Exception e) {
        }
    }
   
    public void destroy() {
    }
    
}

NameableFilter.java

public abstract class NameableFilter extends AbstractFilter implements Nameable { 
    // For setting filter name
    public void setName(String name) {
        this.name = name;
    }
}

OncePerRequestFilter.java

public abstract class OncePerRequestFilter extends NameableFilter {
    // Ensure only one access per request
    public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // See if you have visited it once
        String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
        // If it is accessed once, skip this filter and continue to the next one
        if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
            filterChain.doFilter(request, response);
        } else if (!isEnabled(request, response) || shouldNotFilter(request) ) {
            filterChain.doFilter(request, response);
        } else {
            // No access, mark now
            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
            try {
                   // Execute the content of this filter
                doFilterInternal(request, response, filterChain);
            } finally {
                request.removeAttribute(alreadyFilteredAttributeName);
            }
        }
    }
    
    // The sub class needs to implement the interface. It does not need to implement the doFilter method. By implementing this method, you can do more
    protected abstract void doFilterInternal(ServletRequest request, ServletResponse response,         FilterChain chain) throws ServletException, IOException;
    
}

AdviceFilter.java

public abstract class AdviceFilter extends OncePerRequestFilter {
    
    // This method implements OncePerRequestFilter, which is a bit like aop style
    public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        try {
           // Execute the preHandle method before executing
            boolean continueChain = preHandle(request, response);
           // If the preHandle fails, the execution will not continue
            if (continueChain) {
                executeChain(request, response, chain);
            }
            // Method to execute after execution
            postHandle(request, response);
        } catch (Exception e) {
            exception = e;
        } finally {
            cleanup(request, response, exception);
        }
    }
    // Expose this method
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        return true;
    }
    // Expose this method
    protected void postHandle(ServletRequest request, ServletResponse response) throws Exception {
    }      
}

PathMatchingFilter.java

// It is used to determine whether the request conforms to the Filter. Only the request url matches the url matching rule corresponding to the Filter
public abstract class PathMatchingFilter extends AdviceFilter implements PathConfigProcessor {
    // ShiroFilterFactoryBean is populated when it is initialized,
    // In it, value is the array of [config1,config2] in the author [config1,config2]
    protected Map<String, Object> appliedPaths = new LinkedHashMap<String, Object>();
        // It covers the preHandle method of the parent class. First, it determines whether the request url matches the appliedPaths of the Filter
        protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        // Returns true if the Filter itself does not match the url
        if (this.appliedPaths == null || this.appliedPaths.isEmpty()) {
            return true;
        }

        for (String path : this.appliedPaths.keySet()) {
            //(first match 'wins'):
            if (pathsMatch(path, request)) {
                Object config = this.appliedPaths.get(path);
                // If the match is up, execute this method, which will be further passed to the onPreHandle method and exposed to the outside world
                return isFilterChainContinued(request, response, path, config);
            }
        }

        //no path matched, allow the request to go through:
        return true;
    }

    private boolean isFilterChainContinued(ServletRequest request, ServletResponse response,
                                           String path, Object pathConfig) throws Exception {

        if (isEnabled(request, response, path, pathConfig)) { //isEnabled check added in 1.2

            return onPreHandle(request, response, pathConfig);
        }
        return true;
    }
    
   // Expose this interface externally
    protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        return true;
    }    
}

AccessControlFilter.java(If you want to verify the authorization, this class is still critical.)

// This interface is used to judge whether the request can pass, skip to log in if it doesn't pass, use subject to log in if it meets the requirements, etc
public abstract class AccessControlFilter extends PathMatchingFilter {
    // Override the method exposed by the parent class PathMatchingFilter, and add two methods to the method
    // One is isaccessassallowed, which is used to determine whether it has been verified. For example, the user has logged in to a session
    // The other is the onAccessDenied verification failed. What do you want to do after the failure? I don't know. How do you see it implemented
     public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
    }
    
    // Directly expose it to the outside world, and watch the implementation. Don't do the work of onAccessDenied
    protected abstract boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception;
    
    // This elder brother provides two ways for external coverage, that is, method overloading. It depends on whether you want that parameter
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        return onAccessDenied(request, response);
    }
    // .......
    protected abstract boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception;
    
    // Determine whether it is a login request
    protected boolean isLoginRequest(ServletRequest request, ServletResponse response) {
        return pathsMatch(getLoginUrl(), request);
    }
    // Save request and redirect to login page
    protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
        saveRequest(request);
        redirectToLogin(request, response);
    }
    
    protected void saveRequest(ServletRequest request) {
        WebUtils.saveRequest(request);
    }
    
    protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
        String loginUrl = getLoginUrl();
        WebUtils.issueRedirect(request, response, loginUrl);
    }
        
}
Here is a summary:
  1. If you want to write a simple Filter, please implement the Filter interface directly
  2. If you want to give Filter a name, please inherit the NameableFilter abstract class
  3. If you want to ensure that your filter is called only once, please inherit the OncePerRequestFilter abstract class
  4. If you want your filter before and after the dofilter method (similar to the aop of this method), please inherit the AdviceFilter (see if this advice is particularly familiar - spring aop also has the concept of advice)
  5. If you want to write an authentication and authorization Filter, please continue to read, because there are two classes that implement AccessControlFilter (the wheel has been built, get on the car)

One is authentication

AuthenticationFilter.java (a word class that usually ends with ion and generally provides very basic services)

public abstract class AuthenticationFilter extends AccessControlFilter {
        // Provide basic validation
        protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        Subject subject = getSubject(request, response);
        return subject.isAuthenticated() && subject.getPrincipal() != null;
    }
    
        protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws Exception {
            // After verification, it will be redirected to the url set by itself, which can be changed
        WebUtils.redirectToSavedRequest(request, response, getSuccessUrl());
    }
    
}

AuthenticatingFilter.java

public abstract class AuthenticatingFilter extends AuthenticationFilter {
    
    // Override the method of the parent class, and add the vip function inside the method (you can use isPermissive to walk the vip channel)
        @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return super.isAccessAllowed(request, response, mappedValue) ||
                (!isLoginRequest(request, response) && isPermissive(mappedValue));
    }
    
    // This is the vip template. Just vip, ordinary passengers don't leave
    protected boolean isPermissive(Object mappedValue) {
        if(mappedValue != null) {
            String[] values = (String[]) mappedValue;
            return Arrays.binarySearch(values, PERMISSIVE) >= 0;
        }
        return false;
    }
    
    
    // This method is generally provided to onAccessDenied method, that is, you fail to pass the authentication, and you should log in for authentication
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        AuthenticationToken token = createToken(request, response);
        if (token == null) {
            throw new IllegalStateException(msg);
        }
        try {
            Subject subject = getSubject(request, response);
            subject.login(token);
            return onLoginSuccess(token, subject, request, response);
        } catch (AuthenticationException e) {
            return onLoginFailure(token, e, request, response);
        }
    }
    
    // The external exposure interface is used to generate tokens, because the login must take the token to log in. By default, there are two methods to generate tokens, which are just under this method
    protected abstract AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception;
}

Here's a final example:

An example of shiro Filter

CasFilter.java (let's see what cas did)

public class CasFilter extends AuthenticatingFilter {
    // The method of directly covering the parent class means that you have met me, and you have no authentication (need to be arranged)
        @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return false;
    }
    
    // Let me log in. I'm ready
        @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String ticket = httpRequest.getParameter(TICKET_PARAMETER);
        return new CasToken(ticket);
    }
    
    // Go, Picasso. I want you to log in
        @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        // So casFilter directly performs this step. None of the previous methods work
        return executeLogin(request, response);
    }
    
}

Let's not talk about the authorization class (as above). Here are some default implementations of the above Filter (you can see in the DefaultFilter enumeration class) (familiar or not)

filter name Corresponding Filter
anon AnonymousFilter.class
authc FormAuthenticationFilter.class
authcBasic BasicHttpAuthenticationFilter.class
authcBearer BearerHttpAuthenticationFilter.class
logout LogoutFilter.class
noSessionCreation NoSessionCreationFilter.class
perms PermissionsAuthorizationFilter.class
port PortFilter.class
rest HttpMethodPermissionFilter.class
roles RolesAuthorizationFilter.class
ssl SslFilter.class
user UserFilter.class

Through the understanding of the above Filter, do you know shiro is a Filter? If you don't understand, please read it again (this is a dead cycle judgment, unless you understand it, it won't break;). After the container initialization, it's time to make a request.

Complete analysis of a shiro request

Let's start from the place where the request is intercepted by the Filter. It's the DelegatingFilterProxy class. OK, just make a breakpoint on the doFilter method of this class (let's start before the breakpoint)

DelegatingFilterProxy.java (where the request starts)

public class DelegatingFilterProxy extends GenericFilterBean {
        
    // This method is called when Filter init is started. It will be called when the service is started. It will initialize the delegate, which is ShiroFilterFactoryBean
    @Override
    protected void initFilterBean() throws ServletException {
        synchronized (this.delegateMonitor) {
            if (this.delegate == null) {
                // If no target bean name specified, use filter name.
                // What is targetBeanName? See DelegatingFilterProxy constructor
                if (this.targetBeanName == null) {
                    this.targetBeanName = getFilterName();
                }

                WebApplicationContext wac = findWebApplicationContext();
                if (wac != null) {
                    this.delegate = initDelegate(wac); // Look directly at this
                }
            }
        }
    }
    
    // Just look at this
    protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
        String targetBeanName = getTargetBeanName();
        // Get bean directly through ApplicationContex
        Filter delegate = wac.getBean(targetBeanName, Filter.class);
        if (isTargetFilterLifecycle()) {
            delegate.init(getFilterConfig());
        }
        return delegate;
    }
   
   // The origin of targetBeanName
   public DelegatingFilterProxy(String targetBeanName, @Nullable WebApplicationContext wac) {
        this.setTargetBeanName(targetBeanName);
        this.webApplicationContext = wac;
        if (wac != null) {
            this.setEnvironment(wac.getEnvironment());
        }
    }
    
    
    // doFilter is here, come on!!! Here, come on!!! Here, come on!!!
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        // Lazily initialize the delegate if necessary.
        Filter delegateToUse = this.delegate;
        if (delegateToUse == null) {
            synchronized (this.delegateMonitor) {
                delegateToUse = this.delegate;
                if (delegateToUse == null) {
                    WebApplicationContext wac = findWebApplicationContext();
                    delegateToUse = initDelegate(wac);
                }
                this.delegate = delegateToUse;
            }
        }

        // Let the delegate perform the actual doFilter operation.
        // Go straight to this method
        invokeDelegate(delegateToUse, request, response, filterChain);
    }
    
    // Don't do anything, just leave it to the internal class of SpringShiroFilter of ShiroFilterFactoryBean
    protected void invokeDelegate(
            Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        delegate.doFilter(request, response, filterChain);
    }
    
}

SpringShiroFilter

ShiroFilterFactoryBean$SpringShiroFilterParent classOncePerRequestFilter.java(The following isSpringShiroFilterMission)

public abstract class OncePerRequestFilter extends NameableFilter {
    // Jump here
    public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) {
        String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
        if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
            filterChain.doFilter(request, response);
        } else if (!isEnabled(request, response) || shouldNotFilter(request) ) {
            filterChain.doFilter(request, response);
        } else {
                
            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

            try {
                // Just look at the classes that cover this method
                doFilterInternal(request, response, filterChain);
            } finally {
                request.removeAttribute(alreadyFilteredAttributeName);
            }
        }
    }
}

The parent class of SpringShiroFilter AbstractShiroFilter.java

public abstract class AbstractShiroFilter extends OncePerRequestFilter {
    // Here's the real implementation
    protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)throws ServletException, IOException {

        try {
            final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
            final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
            // Create the subject here, remember!!!
            final Subject subject = createSubject(request, response);

            //noinspection unchecked
            // After the subject is created, the following two methods are encapsulated in Callable(),
            // Before executing the call, bind the subject to the current thread. After executing, clear the binding of the current thread
            // Why do we have to make a Callable method? Just put two methods before and after these two methods. Maybe it's because they are more extensible
            // It's also convenient to encapsulate another layer outside in the future. Maybe you can do asynchronous???
            subject.execute(new Callable() {
                public Object call() throws Exception {
                    // Update session time
                    updateSessionLastAccessTime(request, response);
                    //************************************************
                    // Perform shiro filter chain (let's talk about the subject creation process first, and then later)
                    //************************************************
                    executeChain(request, response, chain);
                    return null;
                }
            });
        } catch (ExecutionException ex) {
            t = ex.getCause();
        }
    }
    //*****************************************
    // Temporarily record this creation process as the subject creation process
    //*****************************************
    // The creation process of subjcet is handed over to the parent class Subject$Builder, and a security manager is sent to him
    protected WebSubject createSubject(ServletRequest request, ServletResponse response){
        return new WebSubject.Builder(getSecurityManager(), request,  response).buildWebSubject();
    }
    
    
}
//==============================================================>

// Show it simply and debug it
public class SubjectCallable<V> implements Callable<V> {
    public V call() throws Exception {
        try {
            // Bind to current thread
            threadState.bind();
            // Execute the call method you implemented
            return doCall(this.callable);
        } finally {
            // Clear data
            threadState.restore();
        }
    }
}

subject creation process

Subject$Builder(SubjectInternal static classBuild)

public interface Subject {
    public static class Builder {
        // As you can see from here, the security manager is finally entrusted to do this
        public Subject buildSubject() {
            return this.securityManager.createSubject(this.subjectContext);
        }
    }
}

Defaultsecuritymanager.java

public class DefaultSecurityManager extends SessionsSecurityManager {
    // Create a new one based on subjcetContext without affecting the previous subjectContext
    public Subject createSubject(SubjectContext subjectContext) {
        //create a copy so we don't modify the argument's backing map:
        // It's a Map, which stores a lot of things about the verification process, such as whether to authenticate, whether to remember, etc,
        // In the DefaultSubjectContext class, you can see: securityManager,sessionId,authenticationToken,authenticationInfo,subject,principals,session,authenticated,host,sessionCreationEnabled,principalsSessionKey,authenticatedSessionKey
        // It is a collection of temporary status and tools. If it is defaultwebsecurity manager, create a
        // DefaultWebSubjectContext instance, in fact, this article discusses the web
        SubjectContext context = copy(subjectContext);

        //Make sure that the SecurityManager has been put into the context
        context = ensureSecurityManager(context);
        
        // This is the most critical and complex one, and it is not complex. It goes into several categories, which is also the focus of the following discussion
        // step0.........
        context = resolveSession(context);
        
        // Well, let's talk later
        context = resolvePrincipals(context);

        // The foreplay is ready. Start to create the Subject instead
        Subject subject = doCreateSubject(context);

        // This will synchronize the latest information in the current Subject to the session. There are other functions. You can have a further look at them later
        save(subject);

        return subject;
    }
    
    // Step 1, to solve the session
    protected SubjectContext resolveSession(SubjectContext context) {
        if (context.resolveSession() != null) {
            return context;
        }
        try {
            // Look directly at resolveContextSession
            Session session = resolveContextSession(context);
            if (session != null) {
                context.setSession(session);
            }
        } catch (InvalidSessionException e) {
        }
        return context;
    }
    // step2, here we go
    protected Session resolveContextSession(SubjectContext context) throws InvalidSessionException {
        // First, check whether there is a context. If there is one, you don't need to continue to find it. Save time. If not, package the request and response into the SessionKey
        SessionKey key = getSessionKey(context);
        if (key != null) {
            // Now it's really started, but it's a division of labor. It's directly handed over to the parent class, SessionsSecurityManager, who specializes in managing sessions
            return getSession(key);
        }
        return null;
    }
}

SessionsSecurityManager.java (all session related operations are managed by him)

public abstract class SessionsSecurityManager extends AuthorizingSecurityManager {
    // Step 3. Seeing this, I found that the security manager as a whole would not work, so I managed the whole process and distributed it
    public Session getSession(SessionKey key) throws SessionException {
        // If you don't overwrite the sessionManger, the default is DefaultSessionManager. Let's start from this
        return this.sessionManager.getSession(key);
    }
}

AbstractNativeSessionManager.java (DefaultSessionManager parent class)

public abstract class AbstractNativeSessionManager extends AbstractSessionManager implements NativeSessionManager, EventBusAware {
    // Don't worry, it's starting
    public Session getSession(SessionKey key) throws SessionException {
        Session session = lookupSession(key);
        return session != null ? createExposedSession(session, key) : null;
    }
    
    // Step 5
    private Session lookupSession(SessionKey key) throws SessionException {
        if (key == null) {
            throw new NullPointerException("SessionKey argument cannot be null.");
        }
        // Seeing the beginning of do, I know that I'm starting to work
        return doGetSession(key);
    }
    
}

AbstractValidatingSessionManager.java (DefaultSessionManager parent class)

public abstract class AbstractValidatingSessionManager extends AbstractNativeSessionManager        implements ValidatingSessionManager, Destroyable {
    
    // Step 6
    @Override
    protected final Session doGetSession(final SessionKey key) throws InvalidSessionException {
        // To verify the validity of the session, generally do not start this method (it is felt that redis can set the validity of time control, and do not start the verification)
        enableSessionValidationIfNecessary();
        // Take a look at this directly. This directly jumps to the subclass DefaultSessionManager, go
        Session s = retrieveSession(key);
        if (s != null) {
            validate(s, key);
        }
        return s;
    }
    
}

DefaultSessionManager.java

public class DefaultSessionManager extends AbstractValidatingSessionManager implements CacheManagerAware {
       // step7. Continue
    protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
        // Take sessionId first
        Serializable sessionId = getSessionId(sessionKey);
        if (sessionId == null) {
            return null;
        }
        // Get session by sessionId
        Session s = retrieveSessionFromDataSource(sessionId);
        if (s == null) {
            throw new UnknownSessionException(msg);
        }
        return s;
    }
    
    // step8. Start to find sessionId
    @Override
    public Serializable getSessionId(SessionKey key) {
        // Check whether the sessionId is stored in the key (shiro does reference caching everywhere, how many rounds)
        Serializable id = super.getSessionId(key);
        if (id == null && WebUtils.isWeb(key)) {
            ServletRequest request = WebUtils.getRequest(key);
            ServletResponse response = WebUtils.getResponse(key);
            // Continue from here
            id = getSessionId(request, response);
        }
        return id;
    }
    // It's here. Go on
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        return getReferencedSessionId(request, response);
    }
    // Step 9. This is the end of the search. If you can't find it, you can't find it. Start back to step 7. Assume you have found it. ok, continue
    private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) {
        // Read this directly from the cookie. In this process, I suggest you debug it. I think it's very important and simple. I won't write it
        String id = getSessionIdCookieValue(request, response);
        if (id != null) {
            // Save to request
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
                    ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
        } else {

            // If you can't find it, you can find it in the request parameters. The request rules are as follows: http: localhost: 8001?; shirohttpsession.default_session_id_name = sessionid (known as shiro's little tail, it's really ugly)
            //try the URI path segment parameters first:
            id = getUriPathSegmentParamValue(request, ShiroHttpSession.DEFAULT_SESSION_ID_NAME);

            if (id == null) {
                //not a URI path segment parameter, try the query parameters:
                String name = getSessionIdName();
                id = request.getParameter(name);
                if (id == null) {
                    //try lowercase:
                    // If you haven't found it yet, you can find it in the request parameter (so even if the browser can't save the cookie, you can only save it in the request parameter)
                    id = request.getParameter(name.toLowerCase());
                }
            }
            if (id != null) {
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
                        ShiroHttpServletRequest.URL_SESSION_ID_SOURCE);
            }
        }
        // Put the newly obtained results into the request and cache them
        if (id != null) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
        }

        // always set rewrite flag - SHIRO-361
        request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, isSessionIdUrlRewritingEnabled());

        return id;
    }
    
    // Step 10. Get the session from the storage you set. If it's redis, get it from redis. That's it. Let's see the rest
    protected Session retrieveSessionFromDataSource(Serializable sessionId) throws UnknownSessionException {
        return sessionDAO.readSession(sessionId);
    }
}

At this stage, the resolveSession(context) method has been completed, leaving only doc create subject (context) and save(subject)

public class DefaultSecurityManager extends SessionsSecurityManager {

    public Subject createSubject(SubjectContext subjectContext) {
        SubjectContext context = copy(subjectContext);
        context = ensureSecurityManager(context);
        context = resolveSession(context);
        context = resolvePrincipals(context);
        // It's easy to come here
        Subject subject = doCreateSubject(context);

        save(subject);

        return subject;
    }
    
    // It can be seen from the method that the Subject is finally created with a special subjectFactory. This article talks about the web
    // So DefaultWebSubjectFactory is the factory method by default
    protected Subject doCreateSubject(SubjectContext context) {
        return getSubjectFactory().createSubject(context);
    }
    
}

DefaultWebSubjectFactory.java

public class DefaultWebSubjectFactory extends DefaultSubjectFactory {
    
    public Subject createSubject(SubjectContext context) {

        boolean isNotBasedOnWebSubject = context.getSubject() != null && !(context.getSubject() instanceof WebSubject);
        if (!(context instanceof WebSubjectContext) || isNotBasedOnWebSubject) {
            return super.createSubject(context);
        }
        // The value is taken from the context store
        WebSubjectContext wsc = (WebSubjectContext) context;
        SecurityManager securityManager = wsc.resolveSecurityManager();
        Session session = wsc.resolveSession();
        boolean sessionEnabled = wsc.isSessionCreationEnabled();
        PrincipalCollection principals = wsc.resolvePrincipals(); // 
        boolean authenticated = wsc.resolveAuthenticated(); // Is it certified
        String host = wsc.resolveHost();
        // There are also request and response. Do you have a lot of subject saved, but you haven't used it basically? Don't mess with it
        ServletRequest request = wsc.resolveServletRequest();    
        ServletResponse response = wsc.resolveServletResponse();
        // Create a real Subject
        return new WebDelegatingSubject(principals, authenticated, host, session, sessionEnabled, request, response, securityManager);
    }
}

The above is the Subject creation process. If there is a session, it will be filled in. If there is no session, it will not be filled in, but the Subject must be created. Now let's go back to the AbstractShiroFilter class and continue to see the doFilterInternal method. The foreplay is enough. We need to execute shiro filter chain instead. come on... It's almost over

AbstractShiroFilter.java (shiro filter started to distribute and execute)

public abstract class AbstractShiroFilter extends OncePerRequestFilter {
    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);

            // Already completed
            final Subject subject = createSubject(request, response);

            //noinspection unchecked
            subject.execute(new Callable() {
                public Object call() throws Exception {
                    updateSessionLastAccessTime(request, response);
                    // come on
                    executeChain(request, response, chain);
                    return null;
                }
            });
        } catch (ExecutionException ex) {
            t = ex.getCause();
        }

        if (t != null) {
            throw new ServletException(msg, t);
        }
    }
    
    // Ad locum
    protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
            throws IOException, ServletException {
        // This function is based on the URL of the request,
        // Find the first matching value and corresponding filters from the map < string, string > map. The map looks like this
        // filterChainDefinitionMap.put("/login", "casFilter");
        // filterChainDefinitionMap.put("/favicon.ico", "anon");
        // filterChainDefinitionMap.put("/**/*.html", "anon");
        // filterChainDefinitionMap.put("/**", "authc,anon"); there are two filter s in the chain
        FilterChain chain = getExecutionChain(request, response, origChain);
        // This chain is ProxiedFilterChain (this is under the web). Go to this class and have a look. The function is very simple
        chain.doFilter(request, response);
    }
}

ProxiedFilterChain.java (Multiple filter When, execute policy)

public class ProxiedFilterChain implements FilterChain {
    // To execute the filter chain strategy, you should take the current chain as the chain of all filters, and use the local index variable to determine the next filter to be executed
    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        if (this.filters == null || this.filters.size() == this.index) {

            this.orig.doFilter(request, response);
        } else {

            this.filters.get(this.index++).doFilter(request, response, this);
        }
    }
}

So far, shiro has talked about more than half of it. There is still a filter process to run. Let me take casFilter as an example.

shiro cas request sequence diagram

It can be seen from the figure that the whole authentication process, I will directly take casfilter.java, which is the token that the user gets, and then make a request to the server. Now the doFilter of casfilter.java intercepts him, and it's time to make a break (the following Filter abstract class, very familiar with it, mentioned earlier):

Onceperrequestfilter.java (parent of casfilter)

public abstract class OncePerRequestFilter extends NameableFilter {
    
    public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain){
        String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
        if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
            filterChain.doFilter(request, response);
        } else
            if (!isEnabled(request, response) || shouldNotFilter(request) ) {

            filterChain.doFilter(request, response);
        } else {
            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
            try {
                // Look at this directly. I'll jump to CasFilter's onAccessDenied method,
                // Why = > the previous one is really clear
                // I don't know. Please jump to the shiro filter
                doFilterInternal(request, response, filterChain);
            } finally {
                request.removeAttribute(alreadyFilteredAttributeName);
            }
        }
    }
}

CasFilter.java

public class CasFilter extends AuthenticatingFilter {
    // I didn't do any of the previous steps until I started working here
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        // This method belongs to the parent class AuthenticatingFilter. Go here
        return executeLogin(request, response);
    }
}

AuthenticatingFilter.java

public abstract class AuthenticatingFilter extends AuthenticationFilter {
    
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        // Take out the token in the request parameter and wrap it as a casToken
        AuthenticationToken token = createToken(request, response);
        try {
            // See here. Use the interface provided by SecurityManager to start the verification
            Subject subject = getSubject(request, response);
            // Log in, get up
            subject.login(token);
            return onLoginSuccess(token, subject, request, response);
        } catch (AuthenticationException e) {
            return onLoginFailure(token, e, request, response);
        }
    }
}

DelegatingSubject.java

public class DelegatingSubject implements Subject {

    public void login(AuthenticationToken token) throws AuthenticationException {
        clearRunAsIdentitiesInternal();
        // Look at this directly. The login operation must be handed to the security manager
        // step1. Where to start
        Subject subject = securityManager.login(this, token);

        PrincipalCollection principals;

        String host = null;

        if (subject instanceof DelegatingSubject) {
            DelegatingSubject delegating = (DelegatingSubject) subject;
            principals = delegating.principals;
            host = delegating.host;
        } else {
            principals = subject.getPrincipals();
        }

        if (principals == null || principals.isEmpty()) {
            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;
        }
    }
    
}

DefaultSecurityManager.java

public class DefaultSecurityManager extends SessionsSecurityManager {

    public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
        AuthenticationInfo info;
        try {
            // Step 2. The parent class AuthenticatingSecurityManager is entrusted to
            info = authenticate(token);
        } catch (AuthenticationException ae) {
            try {
                // After the failure, I guess I will jump to login
                onFailedLogin(token, ae, subject);
            } catch (Exception e) {
            }
            throw ae; //propagate
        }
        // Successful authentication, reseal subject
        Subject loggedIn = createSubject(token, info, subject);
        
        // This is about rememberMe
        onSuccessfulLogin(token, info, loggedIn);

        return loggedIn;
    } 
}

AuthenticatingSecurityManager.java

public abstract class AuthenticatingSecurityManager extends RealmSecurityManager {
    // Step 3. Continue to delegate to the Authenticator. If you do not configure it, the default is modularealmauthenticator
    // This article has also reconfigured (it is recommended to reconfigure this in case of multiple realm s,
    // Unless your authentication policy is the same as modular real authenticator)
    public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
        // Direct access
        return this.authenticator.authenticate(token);
    }
}

AbstractAuthenticator.java

public abstract class AbstractAuthenticator implements Authenticator, LogoutAware {
    
    public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {

        if (token == null) {
            throw new IllegalArgumentException("Method argument (authentication token) cannot be null.");
        }
        AuthenticationInfo info;
        try {
            // Step 4. Do begins to show that it really starts,
            // This article is my modular real Authenticator. Go here
            info = doAuthenticate(token);
            if (info == null) {
                throw new AuthenticationException(msg);
            }
        } catch (Throwable t) {
            AuthenticationException ae = null;
            if (t instanceof AuthenticationException) {
                ae = (AuthenticationException) t;
            }
            if (ae == null) {
                ae = new AuthenticationException(msg, t);
            }
            try {
                notifyFailure(token, ae);
            } catch (Throwable t2) {
            }


            throw ae;
        }

        notifySuccess(token, info);

        return info;
    }
}

MyModularRealmAuthenticator.java

public class MyModularRealmAuthenticator extends ModularRealmAuthenticator { 
    
    // When there are multiple realms, how to use them? The strategy of this paper is: if it's a castoken, let it go to casRealm, and other serious ways
    @Override
    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        // All Realm
        Collection<Realm> realms = getRealms();
        HashMap<String, Realm> realmHashMap = new HashMap<>(realms.size());
        for (Realm realm : realms) {
            realmHashMap.put(realm.getName(), realm);
        }
        
        if (authenticationToken instanceof CasToken) {
            // Step 5. Go directly to this method
            return doSingleRealmAuthentication(realmHashMap.get("casRealm"), authenticationToken);
        } else {
            return doSingleRealmAuthentication(realmHashMap.get("tokenRealm"), authenticationToken);
        }
    }
}

ModularRealmAuthenticator.java

public class ModularRealmAuthenticator extends AbstractAuthenticator {
    
    protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
        if (!realm.supports(token)) {
            throw new UnsupportedTokenException(msg);
        }
         // Step 6. Enter the corresponding realm directly. This article is casrealm. Go to casrealm and have a look
        AuthenticationInfo info = realm.getAuthenticationInfo(token);
        if (info == null) {
            throw new UnknownAccountException(msg);
        }
        return info;
    }  
}

Authenticatingream.java (CasRealm parent class)

public abstract class AuthenticatingRealm extends CachingRealm implements Initializable {
    
    public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // First, check whether there is a cache configured for this realm. If so, take it directly from the cache
        // If you configure CacheManager, casrealm1.setauthorizationcacheenabled (true), the cache will be used. Log in with the user name and password, and add a cache here to speed up the authentication. cas does not need (not need, not use)
        AuthenticationInfo info = getCachedAuthenticationInfo(token);
        if (info == null) {
            //otherwise not cached, perform the lookup:
            // Step 7. Come here, usually write a realm by yourself, and overwrite the method at the beginning of do (because coverage is for work)
            info = doGetAuthenticationInfo(token);
            if (token != null && info != null) {
                cacheAuthenticationInfoIfPossible(token, info);
            }
        }

        if (info != null) {
            assertCredentialsMatch(token, info);
        } 

        return info;
    }
}

CasRealm.java

public class CasRealm extends AuthorizingRealm {

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        CasToken casToken = (CasToken) token;
        if (token == null) {
            return null;
        }
        
        String ticket = (String)casToken.getCredentials();
        if (!StringUtils.hasText(ticket)) {
            return null;
        }
        // By default, Cas20ServiceTicketValidator is used for communication, just like the front-end and back-end interfaces
        TicketValidator ticketValidator = ensureTicketValidator();

        try {
            // Step 8. Come here, start to communicate with cas server, and verify the correctness of token
            // Let's not talk about this process. I suggest you debug it to see how it communicates. I wrote this simulation in the project. You can have a look at it
            Assertion casAssertion = ticketValidator.validate(ticket, getCasService());
            
            AttributePrincipal casPrincipal = casAssertion.getPrincipal();
            String userId = casPrincipal.getName();


            Map<String, Object> attributes = casPrincipal.getAttributes();
            // refresh authentication token (user id + remember me)
            casToken.setUserId(userId);
            String rememberMeAttributeName = getRememberMeAttributeName();
            String rememberMeStringValue = (String)attributes.get(rememberMeAttributeName);
            boolean isRemembered = rememberMeStringValue != null && Boolean.parseBoolean(rememberMeStringValue);
            if (isRemembered) {
                casToken.setRememberMe(true);
            }

            List<Object> principals = CollectionUtils.asList(userId, attributes);
            PrincipalCollection principalCollection = new SimplePrincipalCollection(principals, getName());
            
            // It doesn't say much about the settings
            return new SimpleAuthenticationInfo(principalCollection, ticket);
        } catch (TicketValidationException e) { 
            throw new CasAuthenticationException("Unable to validate ticket [" + ticket + "]", e);
        }
    }
    // The method doGetAuthorizationInfo must be noted that this method will be called to verify roles and role permissions,
    // Therefore, please rewrite it. Note: 1 is the role, 2 is the permission permission of the role
    // PS: you can encapsulate the AuthorizationInfo and put it in the session, so that every time you call this method, you can get it from the session
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // retrieve user information
        SimplePrincipalCollection principalCollection = (SimplePrincipalCollection) principals;
        List<Object> listPrincipals = principalCollection.asList();
        Map<String, String> attributes = (Map<String, String>) listPrincipals.get(1);
        // create simple authorization info
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        // add default roles
        addRoles(simpleAuthorizationInfo, split(defaultRoles));
        // add default permissions
        addPermissions(simpleAuthorizationInfo, split(defaultPermissions));
        // get roles from attributes
        List<String> attributeNames = split(roleAttributeNames);
        for (String attributeName : attributeNames) {
            String value = attributes.get(attributeName);
            addRoles(simpleAuthorizationInfo, split(value));
        }
        // get permissions from attributes
        attributeNames = split(permissionAttributeNames);
        for (String attributeName : attributeNames) {
            String value = attributes.get(attributeName);
            addPermissions(simpleAuthorizationInfo, split(value));
        }
        return simpleAuthorizationInfo;
    }
}

It's almost over here. For specific use, please refer to the online usage methods. You can also use annotations in the code. shiro has its own aop implementation. He will proxy the annotated classes and methods.

Finally, the following points can be optimized and noted:

  1. WebSessionManager will read from the SessionDAO every time it gets a session. If the cache is redis, it will consume performance. It is better to rewrite the retrieveSession method to store the first session in the request, and then retrieve it from here every time.
  2. When using Redis to serialize and store the Session, the fields in SimpleSession are all modified by transient. Please note when selecting the serialization scheme. Either rewrite SimpleSession yourself or choose a serialization method that does not ignore transient.
  3. Those resources that do not need to be authenticated will get a Session from the SessionDAO just like those that need to be authenticated. In fact, this is not necessary at all. Yes, it can also be optimized in the WebSessionManager.
  4. No matter what request is sent to the server, the server will first generate a session to save the request to the session store. If someone keeps requesting, it will cause the session store to run full and eventually cause a denial of service attack. (the solution is to put the session without authentication and the session without authentication in different places, or not save the session without serious authentication, but not save will cause the user to log in for the first time for authentication, and will not navigate to the address that the user first accesses, but make the original set address, which will affect the user experience.)

License

Apache License, Version 2.0

Tags: Java Session Shiro Redis

Posted on Tue, 03 Dec 2019 04:02:53 -0800 by Syphon