Spring Shiro流程简析 基于过滤器的权限管理

相关阅读

简介

Shiro框架通过拦截器实现用户鉴权,其核心为AbstractShiroFilter
在SpringBoot框架中,可以借助FactoryBean机制来配置AbstractShiroFilter

配置代码

@Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

    // 设置SecurityManager
    shiroFilterFactoryBean.setSecurityManager(securityManager);

    // 自定义URL过滤器链规则
    Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
    filterChainDefinitionMap.put("/**", "authc");
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

    // 自定义过滤器实现
    LinkedHashMap<String, Filter> filterMap = new LinkedHashMap<>();
    filterMap.put("authc", new ShiroFormAuthenticationFilter());
    shiroFilterFactoryBean.setFilters(filterMap);

    return shiroFilterFactoryBean;
}

过滤器链创建

ShiroFilterFactoryBean实现了FactoryBean<AbstractShiroFilter>接口,故可以通过其getObject方法获取AbstractShiroFilter实例,代码如下:

public AbstractShiroFilter getObject() throws Exception {
    if (instance == null) {
        // Spring会保证创建Bean时的线程安全
        // 还未创建实例,则创建实例
        instance = createInstance();
    }
    return instance;
}

protected AbstractShiroFilter createInstance() throws Exception {

    log.debug("Creating Shiro Filter instance.");

    // 校验SecurityManager
    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);
    }

    // 创建过滤器链管理器
    FilterChainManager manager = createFilterChainManager();

    // 创建过滤器链解析器,内部持有过滤器链管理器
    PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
    chainResolver.setFilterChainManager(manager);

    // 创建SpringShiroFilter,并传入SecurityManager和PathMatchingFilterChainResolver
    // SecurityManager用于用户鉴权
    // PathMatchingFilterChainResolver 用于为URL创建过滤器链
    return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
}

在创建FilterChainManager实例时,就会根据配置信息构建URL对应的过滤器链,代码如下:

protected FilterChainManager createFilterChainManager() {

    DefaultFilterChainManager manager = new DefaultFilterChainManager();
    // 应用全局配置到默认的过滤器
    // 默认的过滤器见DefaultFilter
    Map<String, Filter> defaultFilters = manager.getFilters();
    for (Filter filter : defaultFilters.values()) {
        applyGlobalPropertiesIfNecessary(filter);
    }

    //Apply the acquired and/or configured filters:
    // 应用全局配置到配置的过滤器
    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);
            }
            manager.addFilter(name, filter, false);
        }
    }

    // 设置全局过滤器
    manager.setGlobalFilters(this.globalFilters);

    // 根据配置构建URL对应的过滤器链
    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();
            manager.createChain(url, chainDefinition);
        }
    }

    // 构建任意URL默认的过滤器链
    manager.createDefaultChain("/**");

    return manager;
}

根据配置信息为URL构建对应的过滤器链的实现代码如下:

public void createChain(String chainName, String chainDefinition) {
    // 校验配置信息
    if (!StringUtils.hasText(chainName)) {
        throw new NullPointerException("chainName cannot be null or empty.");
    }
    if (!StringUtils.hasText(chainDefinition)) {
        throw new NullPointerException("chainDefinition cannot be null or empty.");
    }

    if (log.isDebugEnabled()) {
        log.debug("Creating chain [" + chainName + "] with global filters " + globalFilterNames + " and from String definition [" + chainDefinition + "]");
    }

    // 若存在全局过滤器,则先加入到过滤器链中
    if (!CollectionUtils.isEmpty(globalFilterNames)) {
        globalFilterNames.stream().forEach(filterName -> addToChain(chainName, filterName));
    }

    // 分解过滤器定义
    // "authc, roles[admin,user], perms[file:edit]" ->
    // { "authc", "roles[admin,user]", "perms[file:edit]" }
    String[] filterTokens = splitChainDefinition(chainDefinition);

    // 遍历每个过滤器定义
    for (String token : filterTokens) {
        // 分解过滤器名称和配置参数
        // "authc" -> {"authc", null}
        // "roles[admin,user]" -> {"roles", "admin,user"}
        String[] nameConfigPair = toNameConfigPair(token);

        // 加入过滤器链
        addToChain(chainName, nameConfigPair[0], nameConfigPair[1]);
    }
}

加入过滤器链的实现代码如下:

public void addToChain(String chainName, String filterName) {
    addToChain(chainName, filterName, null);
}

public void addToChain(String chainName, String filterName, String chainSpecificFilterConfig) {
    if (!StringUtils.hasText(chainName)) {
        throw new IllegalArgumentException("chainName cannot be null or empty.");
    }
    // 根据过滤器名查找对应的过滤器
    // 默认的对应关系在DefaultFilter中定义
    // 可以通过调用ShiroFilterFactoryBean.setFilters自定义对应关系
    Filter filter = getFilter(filterName);
    if (filter == null) {
        throw new IllegalArgumentException("There is no filter with name '" + filterName +
                "' to apply to chain [" + chainName + "] in the pool of available Filters.  Ensure a " +
                "filter with that name/path has first been registered with the addFilter method(s).");
    }

    // 应用过滤器配置
    // chainName就是访问过滤器时的URL
    // chainSpecificFilterConfig就是过滤器的配置信息
    applyChainConfig(chainName, filter, chainSpecificFilterConfig);

    // 确保过滤器链存在,没有就创建
    NamedFilterList chain = ensureChain(chainName);
    // 添加过滤器到过滤器链
    chain.add(filter);
}

至此,需要拦截的URL的对应过滤器链就创建完成;
接下来就是如何执行过滤器链;

过滤器链执行

在上文中,配置的ShiroFilterFactoryBean实例会在Spring IOC容器中引入AbstractShiroFilter,那么处理请求的过滤器链中就会存在过滤器AbstractShiroFilterAbstractShiroFilter在执行过滤逻辑时,会将原过滤器链进行代理,在代理过滤器链ProxiedFilterChain中加入了Shiro配置的过滤器链,且优先将Shiro配置的过滤器链执行完,再执行原过滤器链,从而在请求被真正处理之前执行了对请求进行权限管理相关操作;
AbstractShiroFilter过滤逻辑如下:

protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
        throws ServletException, IOException {

    Throwable t = null;

    try {
        // 包装ServletRequest,支持Subject相关操作
        final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
        // 包装ServletResponse,基于ShiroHttpSession
        final ServletResponse response = prepareServletResponse(request, servletResponse, chain);

        // 创建新Subject
        // 虽然是新创建,但是会通过Session设置上次登录信息
        final Subject subject = createSubject(request, response);

        // 同步执行
        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;
        }
        String msg = "Filtered request failed.";
        throw new ServletException(msg, t);
    }
}

主要分为两步:

  1. 创建Subject;
  2. 创建代理过滤器链并执行;

创建Subject

AbstractShiroFilter会为每次请求都创建一个新Subject,代码如下:

protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
    return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
}

借助WebSubject.Builder实现Subject创建,代码如下:

public Builder(SecurityManager securityManager, ServletRequest request, ServletResponse response) {
    // 调用父类Subject.Builder构造函数
    super(securityManager);
    if (request == null) {
        throw new IllegalArgumentException("ServletRequest argument cannot be null.");
    }
    if (response == null) {
        throw new IllegalArgumentException("ServletResponse argument cannot be null.");
    }
    setRequest(request);
    setResponse(response);
}

protected SubjectContext newSubjectContextInstance() {
    return new DefaultWebSubjectContext();
}

父类Subject.Builder构造方法代码如下:

public Builder(SecurityManager securityManager) {
    if (securityManager == null) {
        throw new NullPointerException("SecurityManager method argument cannot be null.");
    }
    this.securityManager = securityManager;
    // WebSubject.Builder会创建DefaultWebSubjectContext
    this.subjectContext = newSubjectContextInstance();
    if (this.subjectContext == null) {
        throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' " +
                "cannot be null.");
    }
    this.subjectContext.setSecurityManager(securityManager);
}

WebSubject.Builder.buildSubject的实现内部也是调用父类Subject.Builder.buildSubject,代码如下:

public Subject buildSubject() {
    return this.securityManager.createSubject(this.subjectContext);
}

借助SecurityManager实现构建Subject,代码如下:

public Subject createSubject(SubjectContext subjectContext) {
    // 创建拷贝,后续基于拷贝操作
    SubjectContext context = copy(subjectContext);

    // SecurityManager不存在就设置自身
    context = ensureSecurityManager(context);

    // 查找Session如果存在的话
    context = resolveSession(context);

    // 查找PrincipalCollection如果存在的话
    context = resolvePrincipals(context);

    // 创建Subject
    Subject subject = doCreateSubject(context);

    // 保存Subject
    save(subject);

    return subject;
}

其中查找Session的过程比较重要,接下来重点分析;

查找Session

resolveSession方法实现代码如下:

protected SubjectContext resolveSession(SubjectContext context) {
    if (context.resolveSession() != null) {
        // 若已存在则直接返回
        log.debug("Context already contains a session.  Returning.");
        return context;
    }
    try {
        // 尝试查找Session
        Session session = resolveContextSession(context);
        if (session != null) {
            // 找到则保存到SubjectContext中
            context.setSession(session);
        }
    } catch (InvalidSessionException e) {
        log.debug("Resolved SubjectContext context session is invalid.  Ignoring and creating an anonymous " +
                "(session-less) Subject instance.", e);
    }
    return context;
}

protected Session resolveContextSession(SubjectContext context) throws InvalidSessionException {
    // 根据SubjectContext构建SessionKey
    // DefaultWebSecurityManager重写了该方法实现
    SessionKey key = getSessionKey(context);
    if (key != null) {
        // 存在SessionKey则尝试查找Session
        return getSession(key);
    }
    return null;
}

// DefaultWebSecurityManager.java
protected SessionKey getSessionKey(SubjectContext context) {
    if (WebUtils.isWeb(context)) {
        // 如果是Web请求则构建WebSessionKey
        // 即使SubjectContext不存在SessionId也会构建SessionKey
        Serializable sessionId = context.getSessionId();
        ServletRequest request = WebUtils.getRequest(context);
        ServletResponse response = WebUtils.getResponse(context);
        return new WebSessionKey(sessionId, request, response);
    } else {
        return super.getSessionKey(context);

    }
}

public Session getSession(SessionKey key) throws SessionException {
    // 使用SessionManager查找Session
    return this.sessionManager.getSession(key);
}

在Spring环境下,默认使用DefaultWebSessionManager,用户可自定义SessionManager实现,用于获取SessionIdDefaultWebSessionManager.getSession(SessionKey)方法继承自父类AbstractNativeSessionManager,代码如下:

public Session getSession(SessionKey key) throws SessionException {
    Session session = lookupSession(key);
    return session != null ? createExposedSession(session, key) : null;
}

private Session lookupSession(SessionKey key) throws SessionException {
    if (key == null) {
        throw new NullPointerException("SessionKey argument cannot be null.");
    }
    return doGetSession(key);
}

// AbstractValidatingSessionManager.java
protected final Session doGetSession(final SessionKey key) throws InvalidSessionException {
    // 确保开启Session校验如果需要的话
    enableSessionValidationIfNecessary();

    log.trace("Attempting to retrieve session with key {}", key);

    // 查找Session
    Session s = retrieveSession(key);
    if (s != null) {
        // 校验Session
        // 每次获取到Session后进行校验,确保Session有效
        validate(s, key);
    }
    return s;
}

// DefaultSessionManager.java
protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
    // 从SessionKey中解析SessionId
    // Web请求从根据Request和Response生成ID
    Serializable sessionId = getSessionId(sessionKey);
    if (sessionId == null) {
        log.debug("Unable to resolve session ID from SessionKey [{}].  Returning null to indicate a " +
                "session could not be found.", sessionKey);
        return null;
    }
    // 根据SessionId查找
    Session s = retrieveSessionFromDataSource(sessionId);
    if (s == null) {
        //session ID was provided, meaning one is expected to be found, but we couldn't find one:
        String msg = "Could not find session with ID [" + sessionId + "]";
        throw new UnknownSessionException(msg);
    }
    return s;
}

// DefaultWebSessionManager.java
public Serializable getSessionId(SessionKey key) {
    Serializable id = super.getSessionId(key);
    if (id == null && WebUtils.isWeb(key)) {
        // 如果是Web请求,则从ServletRequest和ServletResponse中获取SessionId
        ServletRequest request = WebUtils.getRequest(key);
        ServletResponse response = WebUtils.getResponse(key);
        id = getSessionId(request, response);
    }
    return id;
}

// DefaultWebSessionManager.java
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
    return getReferencedSessionId(request, response);
}

// DefaultWebSessionManager.java
private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) {

    // 尝试从Cookie中获取
    String id = getSessionIdCookieValue(request, response);
    if (id != null) {
        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
                ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
    } else {
        // 尝试从请求URL路径中获取JSESSIONID
        id = getUriPathSegmentParamValue(request, ShiroHttpSession.DEFAULT_SESSION_ID_NAME);

        if (id == null && request instanceof HttpServletRequest) {
            // 尝试从请求参数中获取JSESSIONID
            String name = getSessionIdName();
            HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
            String queryString = httpServletRequest.getQueryString();
            if (queryString != null && queryString.contains(name)) {
                id = request.getParameter(name);
            }
            if (id == null && queryString != null && queryString.contains(name.toLowerCase())) {
                //try lowercase:
                id = request.getParameter(name.toLowerCase());
            }
        }
        if (id != null) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
                    ShiroHttpServletRequest.URL_SESSION_ID_SOURCE);
        }
    }
    if (id != null) {
        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
    }
    request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, isSessionIdUrlRewritingEnabled());

    return id;
}

// DefaultSessionManager.java
protected Session retrieveSessionFromDataSource(Serializable sessionId) throws UnknownSessionException {
    // 从SessionDao中根据SessionId查找
    // 默认使用MemorySessionDAO
    return sessionDAO.readSession(sessionId);
}

如果之前已存在Session,那么这时候就能找到对应的Session,并将其放入SubjectContext中;

继续回到SecurityManager构建Subject的方法中,代码如下:

public Subject createSubject(SubjectContext subjectContext) {
    // 创建拷贝,后续基于拷贝操作
    SubjectContext context = copy(subjectContext);

    // SecurityManager不存在就设置自身
    context = ensureSecurityManager(context);

    // 查找Session如果存在的话
    context = resolveSession(context);

    // 查找PrincipalCollection如果存在的话
    context = resolvePrincipals(context);

    // 创建Subject
    Subject subject = doCreateSubject(context);

    // 保存Subject
    save(subject);

    return subject;
}

resolvePrincipals实现代码如下:

protected SubjectContext resolvePrincipals(SubjectContext context) {

    PrincipalCollection principals = context.resolvePrincipals();
    // 当前Subject还不存在PrincipalCollection
    if (isEmpty(principals)) {
        log.trace("No identity (PrincipalCollection) found in the context.  Looking for a remembered identity.");

        // 从RememberMeManager中查找
        principals = getRememberedIdentity(context);

        if (!isEmpty(principals)) {
            log.debug("Found remembered PrincipalCollection.  Adding to the context to be used " +
                    "for subject construction by the SubjectFactory.");

            // 找到则保存到SubjectContext
            context.setPrincipals(principals);
        } else {
            log.trace("No remembered identity found.  Returning original context.");
        }
    }

    return context;
}

解析完SubjectContext后,就可以创建Subject了,代码如下:

protected Subject doCreateSubject(SubjectContext context) {
    return getSubjectFactory().createSubject(context);
}

// DefaultWebSubjectFactory.java
public Subject createSubject(SubjectContext context) {
    boolean isNotBasedOnWebSubject = context.getSubject() != null && !(context.getSubject() instanceof WebSubject);
    if (!(context instanceof WebSubjectContext) || isNotBasedOnWebSubject) {
        return super.createSubject(context);
    }
    WebSubjectContext wsc = (WebSubjectContext) context;
    SecurityManager securityManager = wsc.resolveSecurityManager();
    Session session = wsc.resolveSession();
    boolean sessionEnabled = wsc.isSessionCreationEnabled();
    // 获取PrincipalCollection
    PrincipalCollection principals = wsc.resolvePrincipals();
    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);
}

其中wsc.resolvePrincipals()的过程比较重要,接下来重点分析;

查找Principals

wsc.resolvePrincipals()的实现代码如下:

public PrincipalCollection resolvePrincipals() {
    // 查找PrincipalCollection
    PrincipalCollection principals = getPrincipals();

    if (isEmpty(principals)) {
        // 从鉴权信息中获取如果存在的话
        AuthenticationInfo info = getAuthenticationInfo();
        if (info != null) {
            principals = info.getPrincipals();
        }
    }

    if (isEmpty(principals)) {
        // 从已存在的Subject中获取如果存在的话
        Subject subject = getSubject();
        if (subject != null) {
            principals = subject.getPrincipals();
        }
    }

    if (isEmpty(principals)) {
        // 从Session中获取如果存在的话
        // 用户登录成功后,Shiro会将PrincipalCollection信息保存到Session中,详见mergePrincipals
        Session session = resolveSession();
        if (session != null) {
            principals = (PrincipalCollection) session.getAttribute(PRINCIPALS_SESSION_KEY);
        }
    }

    return principals;
}

最后就是保存Subject,代码如下:

protected void save(Subject subject) {
    // 借助SubjectDao保存Subject
    this.subjectDAO.save(subject);
}

默认使用DefaultSubjectDAO,代码如下:

public Subject save(Subject subject) {
    if (isSessionStorageEnabled(subject)) {
        // 开启Session保存则保存到Session中
        saveToSession(subject);
    } else {
        log.trace("Session storage of subject state for Subject [{}] has been disabled: identity and " +
                "authentication state are expected to be initialized on every request or invocation.", subject);
    }

    return subject;
}

protected void saveToSession(Subject subject) {
    // 合并当前Subject中的Principals
    mergePrincipals(subject);
    // 合并当前Subject中的鉴权标志
    mergeAuthenticationState(subject);
}

创建出Subject后就开始构建代理过滤器链并执行;

创建代理过滤器链并执行

代码如下:

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;
    }

    // 根据请求和原过滤器链构建代理过滤器链
    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;
}

由上文过滤器链创建可知,过滤器链解析器默认使用PathMatchingFilterChainResolver,其getChain方法代码如下:

public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
    FilterChainManager filterChainManager = getFilterChainManager();
    if (!filterChainManager.hasChains()) {
        return null;
    }

    final String requestURI = getPathWithinApplication(request);
    final String requestURINoTrailingSlash = removeTrailingSlash(requestURI);

    // 遍历配置的所有的过滤器链
    for (String pathPattern : filterChainManager.getChainNames()) {
        // 请求URL匹配当前过滤器链路径,则存在过滤器链需要代理
        if (pathMatches(pathPattern, requestURI)) {
            if (log.isTraceEnabled()) {
                log.trace("Matched path pattern [{}] for requestURI [{}].  " +
                        "Utilizing corresponding filter chain...", pathPattern, Encode.forHtml(requestURI));
            }
            return filterChainManager.proxy(originalChain, pathPattern);
        } else {
            // 两者去除'/'后,再次匹配
            pathPattern = removeTrailingSlash(pathPattern);
            if (pathMatches(pathPattern, requestURINoTrailingSlash)) {
                if (log.isTraceEnabled()) {
                    log.trace("Matched path pattern [{}] for requestURI [{}].  " +
                              "Utilizing corresponding filter chain...", pathPattern, Encode.forHtml(requestURINoTrailingSlash));
                }
                return filterChainManager.proxy(originalChain, pathPattern);
            }
        }
    }

    return null;
}

代理过滤器链构建出之后,就开始执行过滤器链

chain.doFilter(request, response);

在分析执行过滤器链之前,先分析创建代理过滤器链并执行这一步是如何执行的,代码如下:

subject.execute(new Callable() {
    public Object call() throws Exception {
        updateSessionLastAccessTime(request, response);
        executeChain(request, response, chain);
        return null;
    }
});

// DelegatingSubject.java
public <V> V execute(Callable<V> callable) throws ExecutionException {
    // 创建SubjectCallable
    Callable<V> associated = associateWith(callable);
    try {
        return associated.call();
    } catch (Throwable t) {
        throw new ExecutionException(t);
    }
}

// SubjectCallable.java
public V call() throws Exception {
    try {
        // 保存Subject到本地线程
        // 这样在任务执行过程中就可以访问到Subject
        threadState.bind();
        // 真正执行任务
        return doCall(this.callable);
    } finally {
        // 清除Subject信息
        threadState.restore();
    }
}

// SubjectThreadState.java
public void bind() {
    SecurityManager securityManager = this.securityManager;
    if ( securityManager == null ) {
        //try just in case the constructor didn't find one at the time:
        securityManager = ThreadContext.getSecurityManager();
    }
    // 备份,会在restore中恢复
    this.originalResources = ThreadContext.getResources();
    ThreadContext.remove();

    // 保存Subject到本地线程
    // 这样就可以通过SecurityUtils.getSubject()获取到该Subject
    ThreadContext.bind(this.subject);
    if (securityManager != null) {
        // 保存SecurityManager到本地线程
        ThreadContext.bind(securityManager);
    }
}

由上文可知,在过滤器链执行过程中,可以通过SecurityUtils.getSubject()获取到为本次请求创建的Subject,Shiro过滤器中,我们简单以FormAuthenticationFilter为例,分析其过滤逻辑;
FormAuthenticationFilter继承自AuthenticationFilterAuthenticationFilter实现了isAccessAllowed方法,代码如下:

protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
    // 根据Request和Response获取Subject
    // 其实就是获取本地线程中的Subject
    Subject subject = getSubject(request, response);
    // 已经完成鉴权且Principal存在,则允许通过
    return subject.isAuthenticated() && subject.getPrincipal() != null;
}

protected Subject getSubject(ServletRequest request, ServletResponse response) {
    // 获取本地线程中的Subject
    return SecurityUtils.getSubject();
}

FormAuthenticationFilter实现了onAccessDenied算法细节,代码如下:

protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
    if (isLoginRequest(request, response)) {
        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;
    }
}

executeLogin由父类AuthenticatingFilter实现,代码如下:

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 = getSubject(request, response);
        // 进行登录
        // 未抛出异常则登录成功
        subject.login(token);
        return onLoginSuccess(token, subject, request, response);
    } catch (AuthenticationException e) {
        return onLoginFailure(token, e, request, response);
    }
}

FormAuthenticationFilter实现了createToken算法细节,代码如下;

protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
    // 获取请求参数中的用户名
    String username = getUsername(request);
    // 获取请求参数中的密码
    String password = getPassword(request);
    // 调用AuthenticatingFilter.createToken创建AuthenticationToken
    return createToken(username, password, request, response);
}

protected String getUsername(ServletRequest request) {
    return WebUtils.getCleanParam(request, getUsernameParam());
}

protected String getPassword(ServletRequest request) {
    return WebUtils.getCleanParam(request, getPasswordParam());
}

至此,过滤器链执行的分析结束。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在 Spring Boot 中使用 shiro 配置自定义过滤器需要以下几个步骤: 1. 引入 shiro-spring-boot-starter 依赖: ``` <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-starter</artifactId> <version>1.7.1</version> </dependency> ``` 2. 创建自定义过滤器: ``` public class CustomFilter extends AccessControlFilter { @Override protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception { // 在这里实现自定义的过滤逻辑,返回 true 表示通过过滤器,返回 false 表示未通过过滤器 return true; } @Override protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception { // 如果 isAccessAllowed 返回 false,则会进入到这里,可以在这里处理未通过过滤器的情况 return false; } } ``` 3. 配置 shiro 的 FilterChainDefinition: ``` @Bean public ShiroFilterChainDefinition shiroFilterChainDefinition() { DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition(); // 添加自定义过滤器,其中 key 是过滤器名称,value 是该过滤器对应的路径 chainDefinition.addPathDefinition("/custom/**", "custom"); return chainDefinition; } ``` 4. 配置自定义过滤器: ``` @Bean("custom") public CustomFilter customFilter() { return new CustomFilter(); } ``` 5. 配置 shiro 的注解支持: ``` @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } ``` 完成以上步骤后,就可以在 Spring Boot 中使用 shiro 配置自定义过滤器了。 ### 回答2: 在 Spring Boot 中使用 Shiro 配置自定义过滤器分为三个步骤。 第一步,创建自定义过滤器类。可以通过实现 Shiro 的 Filter 接口来创建自定义过滤器。在自定义过滤器中需要实现过滤规则,并对请求进行相应的处理。 第二步,配置 Shiro 过滤器链。在 Spring Boot 的配置类中,通过创建 ShiroFilterFactoryBean 对象来配置 Shiro过滤器链。可以使用 Shiro 的 FilterChainDefinitionMap 对象来配置过滤器链,然后将该对象设置给 ShiroFilterFactoryBean。 第三步,启用 Shiro 过滤器。在 Spring Boot 的配置类中,通过创建 DefaultFilterChainManager 对象,并将该对象设置给 ShiroFilterFactoryBean,启用自定义过滤器。 有了以上三步,就可以在 Spring Boot 中使用 Shiro 配置自定义过滤器了。可以通过在自定义过滤器中实现过滤规则来对请求进行拦截或处理,然后在 Shiro 过滤器链中配置该过滤器,最后启用该过滤器。这样就可以实现对请求的自定义过滤器处理。 值得注意的是,在使用 Shiro 进行自定义过滤器配置时,需要保证 Shiro 的配置文件中已经进行了相应的配置,包括认证和授权等相关配置。只有在正确配置的前提下,才能正确使用 Shiro 进行自定义过滤器的配置。 ### 回答3: 在Spring Boot中使用Shiro配置自定义过滤器通常需要以下几个步骤: 1. 引入ShiroSpring Boot依赖。在pom.xml文件中添加ShiroSpring Boot Starter依赖: ``` <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-starter</artifactId> <version>1.7.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> ``` 2. 创建自定义过滤器类。可以通过实现`javax.servlet.Filter`接口或者继承`org.apache.shiro.web.servlet.OncePerRequestFilter`类来创建自定义过滤器。例如,创建一个名为`CustomFilter`的自定义过滤器类: ``` public class CustomFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 过滤器逻辑处理 // ... filterChain.doFilter(request, response); } } ``` 3. 在Shiro配置类中注册自定义过滤器。创建一个Shiro配置类,并使用`@Configuration`注解标记为配置类。通过`@Bean`注解将自定义过滤器注册到Shiro过滤器链中。例如,在配置类`ShiroConfig`中注册`CustomFilter`: ``` @Configuration public class ShiroConfig { @Bean public FilterRegistrationBean<CustomFilter> customFilterRegistrationBean() { FilterRegistrationBean<CustomFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new CustomFilter()); registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); // 过滤器执行顺序 registrationBean.addUrlPatterns("/*"); // 过滤器路径 return registrationBean; } } ``` 4. 配置Shiro的过滤规则。在Shiro配置文件中,可以设置自定义过滤器的拦截规则。例如,在`shiro.ini`配置文件中,设置自定义过滤器的拦截规则: ``` [urls] /** = customFilter // 对所有请求都使用自定义过滤器 ``` 通过以上步骤,在Spring Boot中使用Shiro配置自定义过滤器就可以实现对特定请求的拦截和处理。在`CustomFilter`类的`doFilterInternal`方法中编写自定义的过滤器逻辑,例如鉴权、权限验证等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值