美文网首页
spring-shiro的源码理解(1.认证)

spring-shiro的源码理解(1.认证)

作者: 半只笔芯 | 来源:发表于2018-07-17 14:07 被阅读0次

    Apache shiro 作为一个优秀的权限框架,其中最重要的两项工作,其一是认证,解决用户登陆的认证问题,其二是权限控制,看登陆用户有什么样的权限
    首先在web.xml 里面配置过滤器 /*表示拦截所有url请求

      <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <async-supported>true</async-supported>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    

    由于spring 的DelegatingFilterProxy 只是起一个委托的作用,执行的流程委托给spring中名为shiroFilter的过滤器,所以需要配置这个过滤器


    image.png

    里面有详细解释

    <!-- Shiro的Web过滤器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/login.jsp"/>
        <property name="unauthorizedUrl" value="/unauthorized.jsp"/>
        <property name="filters">
            <util:map>
                <entry key="authc" value-ref="formAuthenticationFilter"/>
            </util:map>
        </property>
    //过滤器的执行链
    anon表示不进行权限控制
        <property name="filterChainDefinitions">
            <value>
                /index.jsp = anon
                /unauthorized.jsp = anon
                /login.jsp = authc
                /logout = logout
                /authenticated.jsp = authc 
                /** = user
            </value>
        </property>
    </bean>
    

    ShiroFactoryBean实现了spring的factorybean的接口,所以shirofilter这个对象是通过getObject进行返回

    public Object getObject() throws Exception {
            if (instance == null) {
                instance = createInstance();
            }
            return instance;
     }
    protected AbstractShiroFilter createInstance() throws Exception {
    
            log.debug("Creating Shiro Filter instance.");
            //获取配置文件的安全管理器
            SecurityManager securityManager = getSecurityManager();
            if (securityManager == null) {
                String msg = "SecurityManager property must be set.";
                throw new BeanInitializationException(msg);
            }
            必须是web环境下的安全管理器
            if (!(securityManager instanceof WebSecurityManager)) {
                String msg = "The security manager does not implement the WebSecurityManager interface.";
                throw new BeanInitializationException(msg);
            }
      创建过滤器的链管理器
            FilterChainManager manager = createFilterChainManager();
    
            //Expose the constructed FilterChainManager by first wrapping it in a
            // FilterChainResolver implementation. The AbstractShiroFilter implementations
            // 创建给予路径匹配的过滤器链的解析器
            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
            //返回spring shirofilter这个对象
            return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
        }
    

    我们最终返回了一个springshirofilter对象

    如果在前面设置了filterChainDefinitions属性,会调用set方法进行注入,可以手动写入,也可以通过导入配置文件来传参

    public void setFilterChainDefinitions(String definitions) {
        Ini ini = new Ini();
        ini.load(definitions);
        //did they explicitly state a 'urls' section?  Not necessary, but just in case:
        Ini.Section section = ini.getSection(IniFilterChainResolverFactory.URLS);
        if (CollectionUtils.isEmpty(section)) {
            //no urls section.  Since this _is_ a urls chain definition property, just assume the
            //default section contains only the definitions:
            section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
        }
        /** 获取默认section,也就是加载
                /index.jsp = anon
                /unauthorized.jsp = anon
                /login.jsp = authc
                /logout = logout
                /authenticated.jsp = authc 
                /** = user
                这段配置,从这段配置中可以知道哪种URL需要应用上哪些Filter,像anon、authc、logout就是Filter的名称,之后可以通过名称动态的添加filter进行拼接
                Ini.Section实现了Map接口,其key为URL匹配符,value为Filter名称
        **/
        // 设置filterChainDefinitionMap
        setFilterChainDefinitionMap(section);
    }
    

    通过配置文件的方式传入chailn

    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
            <property name="securityManager" ref="securityManager" />
            <property name="loginUrl" value="/admin" />
            <property name="filters">  
                    <map>  
                        <entry key="sysAuth">  
                            <bean  
                                class="com.enation.app.base.security.AuthFilter" />  
                        </entry>  
                    </map>  
                </property>  
            <property name="filterChainDefinitions" ref="extensibleFilterChainDefinitions"/>
        </bean>
        
        <bean id="extensibleFilterChainDefinitions" class="com.enation.app.base.security.domain.ExtensibleFilterChainDefinitions">
            <property name="locations" value="classpath*:spring_cfg/*filter-chain-definitions.properties"/>
        </bean>
    

    FilterChainManager是管理当前的shiro的所有filter,有shiro默认使用的,也有自己定义的filter,比如anon等等,首先分析源码查看FilterChainManager是如何创建的


    image.png
    protected FilterChainManager createFilterChainManager() {
        // 创建DefaultFilterChainManager
        DefaultFilterChainManager manager = new DefaultFilterChainManager();
        // 创建Shiro默认Filter,根据org.apache.shiro.web.filter.mgt.DefaultFilter创建
        Map<String, Filter> defaultFilters = manager.getFilters();
        //apply global settings if necessary:
        for (Filter filter : defaultFilters.values()) {
            // 设置相关Filter的loginUrl、successUrl、unauthorizedUrl属性
            applyGlobalPropertiesIfNecessary(filter);
        }
     
        // 获取在Spring配置文件中配置的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);
                if (filter instanceof Nameable) {
                    ((Nameable) filter).setName(name);
                }
                // 将配置的Filter添加至链中,如果同名Filter已存在则覆盖默认Filter
                manager.addFilter(name, filter, false);
            }
        }
     
        //build up the chains:
        Map<String, String> chains = getFilterChainDefinitionMap();
        if (!CollectionUtils.isEmpty(chains)) {
            for (Map.Entry<String, String> entry : chains.entrySet()) {
                String url = entry.getKey();
                String chainDefinition = entry.getValue();
                // 为配置的每一个URL匹配创建FilterChain定义,
                // 这样当访问一个URL的时候,一旦该URL配置上则就知道该URL需要应用上哪些Filter
                // 由于URL配置符会配置多个,所以以第一个匹配上的为准,所以越具体的匹配符应该配置在前面,越宽泛的匹配符配置在后面
                manager.createChain(url, chainDefinition);
            }
        }
     
        return manager;
    }
    

    PathMatchingFilterChainResolver对象职责很简单,就是使用ant路径匹配方法匹配访问的URL,由于pathMatchingFilterChainResolver拥有FilterChainManager对象,所以URL匹配上后可以获取该URL需要应用的FilterChain了。比如ano随影的所有url匹配ano配置的所有filter
    通过上述分析可以知道,Shiro就是通过一系列的URL匹配符配置URL应该应用上的Filter,然后在Filter中完成相应的任务,所以Shiro的所有功能都是通过Filter完成的。当然认证功能也不例外,在上述配置中认证功能是由com.enation.app.base.security AuthFilter完成的。

    package com.enation.app.base.security;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.apache.shiro.web.filter.authc.UserFilter;
    import org.springframework.beans.factory.annotation.Autowired;
    
    import com.enation.app.base.core.model.AuthAction;
    import com.enation.eop.processor.core.HttpHeaderConstants;
    import com.enation.eop.resource.IMenuManager;
    import com.enation.eop.resource.model.AdminUser;
    import com.enation.eop.resource.model.Menu;
    import com.enation.eop.sdk.context.EopSetting;
    import com.enation.eop.sdk.context.UserConext;
    import com.enation.framework.context.webcontext.ThreadContextHolder;
    
    /**
     * 自定义权限拦截器:拦截菜单表中定义的所有url并且没有授权给当前用户的
     * 
     * @author tito
     *
     */
    public class AuthFilter extends UserFilter {
    
        public static final String CURRENT_ADMINUSER_MENU_KEY = "CURRENT_ADMINUSER_MENU_KEY";
    
        @Autowired
        private IMenuManager menuManager;
    
        @Override
        public boolean isAccessAllowed(ServletRequest request,
                ServletResponse response, Object mappedValue) {
    
            HttpServletRequest httpRequest = (HttpServletRequest) request;
    
            // String[] mapArr = (String[]) mappedValue;
            // if (mapArr == null || mapArr.length == 0) {
            // return true;
            // }
    
            if ("yes".equals(EopSetting.INSTALL_LOCK)) {
    
                AdminUser user = UserConext.getCurrentAdminUser();
                if (user != null && user.getFounder() == 1) {
                    return true;
                }
    
                List<Menu> allMenus = menuManager.getMenuList();
                Map<String, Menu> map = new HashMap<String, Menu>();
                if (CollectionUtils.isNotEmpty(allMenus)) {
                    for (Menu menu : allMenus) {
                        if (Menu.MENU_TYPE_SYS != menu.getMenutype()
                                && StringUtils.isNoneBlank(menu.getUrl())) {
                            map.put(menu.getUrl().trim(), menu);
                        }
                    }
                }
    
                if ("/core/admin/themeUri/list.do".equalsIgnoreCase(httpRequest
                        .getServletPath())) {
                    System.out.println();
                }
                if (map.containsKey(httpRequest.getServletPath())) {
                    Menu m = map.get(httpRequest.getServletPath());
                    List<AuthAction> authActions = user.getAuthList();
                    if (CollectionUtils.isNotEmpty(authActions)) {
                        for (AuthAction authAction : authActions) {
                            String arth[] = authAction.getObjvalue().split(",");
    
                            // authAction 的objectvalue中怕偶有空格。。。
                            for (String authStr : arth) {
    
                                if (authStr.trim().equals(m.getUrl())) {
                                    return true;
                                }
                            }
                        }
                    }
                    return false;
                }
            }
            // 通过原有的登录(包括Cookie)或者Shiro登录的允许
            return hasOriginalLogedIn()
                    || super.isAccessAllowed(httpRequest, response, mappedValue);
        }
    
        protected boolean hasOriginalLogedIn() {
            return UserConext.getCurrentMember() != null;
        }
    
        @Override
        protected boolean onAccessDenied(ServletRequest request,
                ServletResponse response) throws Exception {
    
            // TODO 以下需要进一步优化,现在只是简单返回401
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            HttpServletResponse httpResponse = ThreadContextHolder
                    .getHttpResponse();
            if (httpResponse != null) {
                httpResponse.setStatus(HttpHeaderConstants.status_401);
            }
    
            // 是否异步请求
            if (isAjaxRequest(httpRequest)) {
                try {
                    PrintWriter writer = response.getWriter();
                    writer.write("ajax 401 没有访问权限");
                    writer.flush();
                } catch (IOException e) {
                }
                return false;
            } else {
                // 非异步请求时,判断访问的是前台还是后台,前台跳转到首页
                String uri = httpRequest.getServletPath();
                String ctx = httpRequest.getContextPath();
                if (uri.startsWith("/admin")) {
                    httpResponse.sendRedirect(ctx + "/admin/401.do");
                    return false;
                }
                httpResponse.sendRedirect(ctx + "/");
                return false;
            }
        }
    
        protected boolean isAjaxRequest(HttpServletRequest httpRequest) {
            String accept = httpRequest.getHeader("accept");
            String contentType = httpRequest.getHeader("Content-Type");
            String xReqWith = httpRequest.getHeader("X-Requested-With");
    
            return (accept != null && accept.contains("application/json"))
                    || (contentType != null && contentType
                            .contains("application/json"))
                    || (xReqWith != null && xReqWith.contains("XMLHttpRequest"));
        }
    }
    

    下面我们就看看入口过滤器SpringShiroFilter的执行流程,是如何执行到AuthFilter的。既然是Filter,那么最重要的就是doFilter方法了,由于SpringShiroFilter继承自OncePerRequestFilter,doFilter方法也是在OncePerRequestFilter中定义的:

    public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        // 用于保证链中同一类型的Filter只会被执行一次
        String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
        if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
            log.trace("Filter '{}' already executed.  Proceeding without invoking this filter.", getName());
            filterChain.doFilter(request, response);
        } 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);
        } else {
            // Do invoke this filter...
            log.trace("Filter '{}' not yet executed.  Executing now.", getName());
            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
     
            try {
                // 执行真正的功能代码
                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);
            }
        }
    }
    

    doFilterInternal方法在AbstractShiroFilter中

    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);
            
            // 创建Subject对象,由此可见,每一个请求到来,都会调用createSubject方法
            final Subject subject = createSubject(request, response);
     
            // 通过Subject对象执行过滤器链,
            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);
            }
    }
    

    首先进入subject的创建方法

    protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
        return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
    }
    
    DefaultSecurityManager类
    public Subject createSubject(SubjectContext subjectContext) {
            //create a copy so we don't modify the argument's backing map:
            SubjectContext context = copy(subjectContext);
    
            //ensure that the context has a SecurityManager instance, and if not, add one:
            context = ensureSecurityManager(context);
    
            //Resolve an associated Session (usually based on a referenced session ID), and place it in the context before
            //sending to the SubjectFactory.  The SubjectFactory should not need to know how to acquire sessions as the
            //process is often environment specific - better to shield the SF from these details:
            context = resolveSession(context);
    
            //Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first
            //if possible before handing off to the SubjectFactory:
            context = resolvePrincipals(context);
    
            Subject subject = doCreateSubject(context);
    
            //save this subject for future reference if necessary:
            //(this is needed here in case rememberMe principals were resolved and they need to be stored in the
            //session, so we don't constantly rehydrate the rememberMe PrincipalCollection on every operation).
            //Added in 1.2:
            save(subject);
    
            return subject;
        }
    
    最后到DefaultWebSubjectFactory类
     protected Subject doCreateSubject(SubjectContext context) {
            return getSubjectFactory().createSubject(context);
        }
    
    public Subject createSubject(SubjectContext context) {
        if (!(context instanceof WebSubjectContext)) {
            return super.createSubject(context);
        }
        WebSubjectContext wsc = (WebSubjectContext) context;
        SecurityManager securityManager = wsc.resolveSecurityManager();
        Session session = wsc.resolveSession();
        boolean sessionEnabled = wsc.isSessionCreationEnabled();
        PrincipalCollection principals = wsc.resolvePrincipals();
        // 判断是已经认证,如果是在没有登录之前,明显返回是false
        boolean authenticated = wsc.resolveAuthenticated();
        String host = wsc.resolveHost();
        ServletRequest request = wsc.resolveServletRequest();
        ServletResponse response = wsc.resolveServletResponse();
     
        return new WebDelegatingSubject(principals, authenticated, host, session, sessionEnabled,
                request, response, securityManager);
    }
    
    

    创建完subject之后,之后在上面的代码中会执行过滤器的链

    protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
            throws IOException, ServletException {
        // 获取当前URL匹配的过滤器链
        FilterChain chain = getExecutionChain(request, response, origChain);
        // 执行过滤器链中的过滤器
        chain.doFilter(request, response);
    }
     
    protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
        FilterChain chain = origChain;
        // 获取过滤器链解析器,即上面创建的PathMatchingFilterChainResolver对象
        FilterChainResolver resolver = getFilterChainResolver();
        if (resolver == null) {
            log.debug("No FilterChainResolver configured.  Returning original FilterChain.");
            return origChain;
        }
        
        // 调用其getChain方法,根据URL匹配相应的过滤器链
        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;
    }
    
    

    根据上述Spring配置,假设现在第一次访问URL: "/authenticated.jsp",则会应用上名为authc的Filter,即FormAuthenticationFilter,根据FormAuthenticationFilter的继承体系,先执行AdviceFilter.doFilterInternal方法:

    public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
                throws ServletException, IOException {
        Exception exception = null;
     
        try {
            // 执行preHandle
            boolean continueChain = preHandle(request, response);
            if (log.isTraceEnabled()) {
                log.trace("Invoked preHandle method.  Continuing chain?: [" + continueChain + "]");
            }
            // 如果preHandle返回false则过滤器链不再执行
            if (continueChain) {
                executeChain(request, response, chain);
            }
     
            postHandle(request, response);
            if (log.isTraceEnabled()) {
                log.trace("Successfully invoked postHandle method");
            }
     
        } catch (Exception e) {
            exception = e;
        } finally {
            cleanup(request, response, exception);
        }
    }
    接下来执行:PathMatchingFilter.preHandle方法:
    appliedPath中存入的是配置文件中所有的配置
    key是路径,value是filter的类型,所以request和path的匹配程度,若匹配,则返回
    
    
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
     
        if (this.appliedPaths == null || this.appliedPaths.isEmpty()) {
            if (log.isTraceEnabled()) {
                log.trace("appliedPaths property is null or empty.  This Filter will passthrough immediately.");
            }
            return true;
        }
     
        for (String path : this.appliedPaths.keySet()) {
            // 根据配置,访问URL:"/authenticated.jsp"时,会匹配上FormAuthenticationFilter,
            // 而FormAuthenticationFilter继承自PathMatchingFilter,所以返回true
            if (pathsMatch(path, request)) {
                log.trace("Current requestURI matches pattern '{}'.  Determining filter chain execution...", path);
                Object config = this.appliedPaths.get(path);
                // 执行isFilterChainContinued方法,该方法调用onPreHandle方法
                return isFilterChainContinued(request, response, path, config);
            }
        }
     
        //no path matched, allow the request to go through:
        return true;
    }
    最后到AccessControlFilter类
    
     public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
            return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
        }
    
    

    执行AuthenticatingFilter类的isAccessAllowed方法(重载)

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return super.isAccessAllowed(request, response, mappedValue) ||
                (!isLoginRequest(request, response) && isPermissive(mappedValue));
    }
    super.isAccessAllowed方法,即AuthenticationFilter.isAccessAllowed方法:
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        Subject subject = getSubject(request, response);
        return subject.isAuthenticated();
    
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        // 第一次访问自然不是登录请求
        if (isLoginRequest(request, response)) {
            // 判断是否是POST请求
    /*
    代码如下
     @SuppressWarnings({"UnusedDeclaration"})
        protected boolean isLoginSubmission(ServletRequest request, ServletResponse response) {
            return (request instanceof HttpServletRequest) && WebUtils.toHttp(request).getMethod().equalsIgnoreCase(POST_METHOD);
        }
    */
            if (isLoginSubmission(request, response)) {
                if (log.isTraceEnabled()) {
                    log.trace("Login submission detected.  Attempting to execute login.");
                }
                return executeLogin(request, response);
            } else {
                if (log.isTraceEnabled()) {
                    log.trace("Login page view.");
                }
                //allow them to see the login page ;)
                return true;
            }
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Attempting to access a path which requires authentication.  Forwarding to the " +
                        "Authentication url [" + getLoginUrl() + "]");
            }
            // 所以执行该方法
            saveRequestAndRedirectToLogin(request, response);
            return false;
        }
    }
    如果是第一次访问,那么肯定返回false
     FormAuthenticationFilter的onAccessDenied方法
    protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
        // 将request对象保存在session中,以便登录成功后重新转至上次访问的URL,提升用户体验
        saveRequest(request);
        // 重定向至登录页面,即:"/login.jsp"
        redirectToLogin(request, response);
    }
    

    根据配置,访问URL:"/login.jsp"时也会应用上FormAuthenticationFilter,由于是重定向所以发起的是GET请求,所以isLoginSubmission()返回false,所以没有执行executeLogin方法,所以能够访问/login.jsp页面。在登录表单中应该设置action="",这样登录请求会提交至/login.jsp,这时为POST请求,所以会执行executeLogin方法:

    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        // 根据表单填写的用户名密码创建AuthenticationToken
        AuthenticationToken token = createToken(request, response);
        if (token == null) {
            String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +
                    "must be created in order to execute a login attempt.";
            throw new IllegalStateException(msg);
        }
        try {
            // 获取Subject对象
            Subject subject = getSubject(request, response);
            // 执行Subject.login方法进行登录
            subject.login(token);
            // 如果登录成功,重定向至上次访问的URL
            return onLoginSuccess(token, subject, request, response);
        } catch (AuthenticationException e) {
            // 如果登录失败,则设置错误信息至request,并重新返回登录页面
            return onLoginFailure(token, e, request, response);
        }
    }
     
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
                                         ServletRequest request, ServletResponse response) throws Exception {
        // 重定向至上次访问的URL
        issueSuccessRedirect(request, response);
        // 由于返回false,所以过滤器链不再执行
        return false;
    }
     
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,
                                     ServletRequest request, ServletResponse response) {
        // 设置错误信息至request
        setFailureAttribute(request, e);
        // 由于返回true,所以过滤器链继续执行,所以又返回了登录页面
        return true;
    }
    

    至此认证流程走通

    相关文章

      网友评论

          本文标题:spring-shiro的源码理解(1.认证)

          本文链接:https://www.haomeiwen.com/subject/fwjipftx.html