Shiro登录验证与鉴权核心流程详解

在Web项目的安全架构中,登录验证与权限控制是保障系统安全的核心环节。Shiro作为一款轻量级的安全框架,通过过滤器(Filter)与拦截器(Interceptor)的协同工作,提供了完整且灵活的登录验证与鉴权解决方案。本文将从核心概述、核心组件、登录验证流程、权限鉴权流程、过滤器、拦截器及实践建议七个维度,系统拆解其实现逻辑与应用细节,助力开发者深入理解并灵活运用Shiro构建安全防护体系。

一、核心概述:双层安全防护体系

Shiro登录验证与鉴权的核心设计思想,是构建"过滤器链前置拦截+拦截器方法级增强"的双层管控模式。过滤器负责URL级别的粗粒度安全校验(如是否登录、URL是否允许匿名访问),确保非法请求在进入业务层前被阻断;拦截器基于AOP思想实现方法级别的细粒度权限控制(如方法所需的具体角色、权限),精准管控业务逻辑的访问权限。两者协同配合,形成从请求入口到业务执行的全链路安全校验闭环。

核心流程总览:用户发起请求后,先经过Web容器过滤器链,再进入Shiro核心过滤器链完成基础校验;校验通过后,经自定义或框架拦截器完成方法级权限校验,最终到达Controller层处理业务逻辑。任意一层校验失败,均直接拦截请求并返回对应结果,有效减少无效业务处理开销。

graph TD A[请求] --> B(shiro过滤器链) B --> C(其他web过滤器) C --> E(拦截器) E-->D[controller]

二、核心组件:安全机制的基石

Shiro的安全校验机制依赖一系列核心组件的协同工作,各组件分工明确,共同完成身份验证、权限控制、会话管理等核心功能。

1. 核心组件及功能

组件 功能
Shiro Filter 各种验证流程的入口,实现URL级别的粗粒度校验
Subject 代表登录人,记录session、凭证等信息,定义验证所需基本方法,每次请求创建新实例
SecurityManager 核心管理器,负责管理Subject、session等信息,辅助Subject实现各项功能
Session 类似Servlet Session,每个登录用户对应唯一Session
Realm 需开发者实现,用于获取用户权限、角色、身份等核心信息
AuthenticationInfo 通过Realm获取,用于身份验证的核心信息载体
AuthorizationInfo 通过Realm获取,用于权限验证的核心信息载体
AuthenticationToken 封装请求中的凭证信息(如用户名、密码)
CredentialsMatcher 核心用于密码验证,对比请求凭证与系统存储凭证的一致性
PasswordService 用于密码生成,辅助CredentialsMatcher完成密码验证
AuthenticationStrategy 多Realm场景下的身份验证策略,类似投票机制决定验证是否通过
Authenticator 负责执行身份验证的核心逻辑

2. 组件间核心关系

组件协同逻辑:用户请求触发Subject创建,Subject通过SecurityManager调用Authenticator完成身份验证;Authenticator依托AuthenticationStrategy,从Realm获取AuthenticationInfo并通过CredentialsMatcher校验凭证;权限验证时,SecurityManager调用Authorizer,从Realm获取AuthorizationInfo完成权限匹配;过滤器与拦截器作为入口,串联各组件形成完整校验链路。

三、登录验证流程:身份合法性校验全链路

登录验证流程的核心目标是校验用户身份合法性,核心链路为"请求拦截→凭证获取→凭证校验→会话创建",具体实现依赖Filter与核心组件的协同工作。

1. 登录验证流程总览

graph TD A[用户发起请求] --> B{是否为登录请求} B --> |否| C[跳转至登录页] B --> |是| D[FormAuthenticationFilter拦截请求] D --> E["获取用户名、密码,封装为AuthenticationToken"] E --> F["调用Subject.login()方法"] F --> G[SecurityManager转发验证请求] G --> H[Authenticator执行验证逻辑] H --> I{多Realm场景?} I --> |是| J[按AuthenticationStrategy执行多Realm验证] I --> |否| K[单Realm验证] J --> L[Realm获取AuthenticationInfo] K --> L L --> M{凭证匹配?} M --> |否| N[返回登录失败信息] M --> |是| O[创建Session和身份凭证] O --> P[将登录信息写入Cookie] P --> Q[跳转至目标页面]

2. 核心方法调用链路

graph TD a0[AbstractShiroFilter.doFilterInternal] --> a[FormAuthenticationFilter.onAccessDenied] a --> b[AuthenticatingFilter.executeLogin] b --> c[DelegatingSubject.login] c --> d[DefaultSecurityManager.login] d --> e[ModularRealmAuthenticator.authenticate] e --> f[AuthenticatingRealm.getAuthenticationInfo] f --> g[SimpleCredentialsMatcher.equals]

3. 关键方法解析

(1)AbstractShiroFilter.doFilterInternal:请求拦截入口

java 复制代码
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
        final Subject subject = createSubject(request, response);

        // 将Subject绑定到当前线程,调用FormAuthenticationFilter处理
        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;
    }
}
    

核心作用:预处理请求/响应对象,创建Subject并绑定到当前线程,触发后续登录验证逻辑。

(2)FormAuthenticationFilter.onAccessDenied:登录请求判断

java 复制代码
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.");
            }
            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;
    }
}
    

核心作用:区分登录请求与普通请求,仅对登录提交请求触发验证流程,非登录请求引导至登录页。

(3)ModularRealmAuthenticator.authenticate:验证逻辑分发

java 复制代码
// 验证策略选择
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
    assertRealmsConfigured();
    Collection<Realm> realms = getRealms();
    if (realms.size() == 1) {
        // 单Realm场景:直接执行普通用户名密码验证
        return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
    } else {
        // 多Realm场景:按策略执行验证(全成功/至少一个成功/首个成功)
        return doMultiRealmAuthentication(realms, authenticationToken);
    }
}

// 单Realm用户名密码验证
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
    if (!realm.supports(token)) {
        String msg = "Realm [" + realm + "] does not support authentication token [" +
                token + "].  Please ensure that the appropriate Realm implementation is " +
                "configured correctly or that the realm accepts AuthenticationTokens of this type.";
        throw new UnsupportedTokenException(msg);
    }
    // 调用Realm获取用户信息并验证
    AuthenticationInfo info = realm.getAuthenticationInfo(token);
    if (info == null) {
        String msg = "Realm [" + realm + "] was unable to find account data for the " +
                "submitted AuthenticationToken [" + token + "].";
        throw new UnknownAccountException(msg);
    }
    return info;
}
    

核心作用:根据Realm数量分发验证逻辑,支持单Realm普通验证与多Realm策略化验证,适配不同系统架构。

四、权限鉴权流程:已登录用户的权限校验

权限鉴权流程基于身份验证通过的前提,核心目标是校验已登录用户是否具备访问目标资源的权限,分为URL级(过滤器实现)和方法级(拦截器实现)两类,此处先阐述通用鉴权链路,方法级细节后续展开。

1. 权限鉴权流程总览

graph TD A[已登录用户发起请求] --> B[Shiro过滤器链拦截] B --> C[UserFilter校验用户有效性] C --> |无效| D[跳转至登录页] C --> |有效| E["PermissionsAuthorizationFilter/RolesAuthorizationFilter"] E --> F[获取请求所需权限/角色] F --> G["调用Subject.isPermitted()/hasRole()"] G --> H[SecurityManager转发权限校验请求] H --> I[ModularRealmAuthorizer执行校验逻辑] I --> J[Realm获取AuthorizationInfo] J --> K{权限/角色匹配?} K --> |否| L[返回403无权限信息] K --> |是| M[进入拦截器校验环节] M --> N{方法级权限校验通过?} N --> |否| L N --> |是| O[Controller业务处理]

2. 核心方法调用链路

graph TD a0["AbstractShiroFilter.doFilterInternal"] --> a["UserFilter.isAccessAllowed"] a --> f["PermissionsAuthorizationFilter.isAccessAllowed"] f --> g["DelegatingSubject.isPermitted"] g --> h["DefaultSecurityManager.isPermitted"] h --> i["ModularRealmAuthorizer.isPermitted"] i --> j[AuthorizingRealm.isPermitted]

3. 关键方法解析

(1)UserFilter.isAccessAllowed:用户有效性前置校验

java 复制代码
public class UserFilter extends AccessControlFilter {

    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        // 不拦截登录流程
        if (isLoginRequest(request, response)) {
            return true;
        } else {
            // 校验用户是否已登录(含记住我状态)
            Subject subject = getSubject(request, response);
            return subject.getPrincipal() != null;
        }
    }
    
}
    

核心作用:快速筛选无效用户,避免无效的后续权限校验,仅允许已登录(含记住我)用户进入权限校验环节。

(2)PermissionsAuthorizationFilter.isAccessAllowed:权限精准校验

java 复制代码
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {

    Subject subject = getSubject(request, response);
    // 获取配置的目标资源所需权限
    String[] perms = (String[]) mappedValue;

    boolean isPermitted = true;
    if (perms != null && perms.length > 0) {
        // 单权限/多权限校验(默认"且"关系)
        if (perms.length == 1) {
            if (!subject.isPermitted(perms[0])) {
                isPermitted = false;
            }
        } else {
            if (!subject.isPermittedAll(perms)) {
                isPermitted = false;
            }
        }
    }

    return isPermitted;
}
    

核心作用:校验用户是否具备访问当前URL所需的全部(或指定)权限,实现URL级的权限精准管控。

(3)AuthorizingRealm.isPermitted:权限匹配核心逻辑

java 复制代码
protected boolean isPermitted(Permission permission, AuthorizationInfo info) {
    Collection<Permission> perms = getPermissions(info);
    if (perms != null && !perms.isEmpty()) {
        for (Permission perm : perms) {
            // 对比用户拥有的权限与目标资源所需权限
            if (perm.implies(permission)) {
                return true;
            }
        }
    }
    return false;
}
    

核心作用:通过权限匹配算法,判断用户已拥有的权限是否覆盖目标资源所需权限,是权限校验的核心逻辑实现。

五、过滤器:URL级粗粒度安全管控核心

Shiro过滤器基于Web容器Filter接口扩展,是URL级安全管控的核心组件,通过拦截请求URL,完成登录验证、权限校验、匿名访问控制等功能。其核心优势在于配置灵活,无需修改业务代码即可实现安全管控,覆盖大部分常规安全场景。

1. 过滤器层级结构

Shiro过滤器采用"抽象基类+具体实现"的层级设计,基础类提供通用能力,实现类聚焦业务校验,结构清晰且扩展性强:

  • 基础过滤器:定义核心骨架,提供通用功能。如PathMatchingFilter负责URL模式匹配(支持?、*、**通配符),是所有URL相关过滤器的父类;AdviceFilter提供请求前后增强点,支持日志记录、资源清理等扩展操作。
  • 验证过滤器:基于基础类扩展,实现具体校验逻辑。分为登录状态验证(如FormAuthenticationFilter、UserFilter)和权限验证(如PermissionsAuthorizationFilter、RolesAuthorizationFilter)两类。

2. 内置默认过滤器枚举

Shiro通过DefaultFilter枚举定义常用内置过滤器,可直接通过枚举名称在配置文件中引用,简化配置流程:

java 复制代码
public enum DefaultFilter {
    // 匿名访问过滤器:无需登录即可访问(登录页、公开接口等)
    anon(AnonymousFilter.class),
    // 表单登录过滤器:处理表单登录请求,校验用户名密码
    authc(FormAuthenticationFilter.class),
    // HTTP基本认证过滤器:基于HTTP Basic协议验证(适用于API)
    authcBasic(BasicHttpAuthenticationFilter.class),
    // 登出过滤器:清除会话信息,销毁登录状态
    logout(LogoutFilter.class),
    // 禁止会话创建过滤器:适用于无状态接口
    noSessionCreation(NoSessionCreationFilter.class),
    // 权限校验过滤器:验证用户是否具备指定权限
    perms(PermissionsAuthorizationFilter.class),
    // 端口校验过滤器:验证请求端口是否符合配置
    port(PortFilter.class),
    // REST风格权限过滤器:基于HTTP方法匹配权限(适用于RESTful接口)
    rest(HttpMethodPermissionFilter.class),
    // 角色校验过滤器:验证用户是否具备指定角色
    roles(RolesAuthorizationFilter.class),
    // SSL过滤器:强制HTTPS访问
    ssl(SslFilter.class),
    // 用户状态过滤器:验证用户是否为有效用户(登录/记住我)
    user(UserFilter.class);
}
    

3. 常见配置示例与规则

(1)YML配置示例

yml 复制代码
shiro:
  filter-chain-definitions:
    # 公开接口:允许匿名访问
    - /api/public/**|anon
    # 登录接口:表单登录验证
    - /login|authc
    # 登出接口:登出处理
    - /logout|logout
    # 管理员接口:需admin角色
    - /api/admin/**|roles[admin]
    # 订单接口:需order:operate权限
    - /api/order/**|perms[order:operate]
    # 兜底配置:所有未匹配URL需有效用户
    - /**|user
    

(2)核心配置规则

  • URL模式:支持通配符,/**表示所有路径,/api/*表示/api下一级路径,/api/**表示/api下所有层级路径;
  • 多过滤器组合:多个过滤器用逗号分隔,按配置顺序执行(如/authc,perms[test]表示先验证登录再校验权限);
  • 匹配优先级:精准匹配优先于模糊匹配,兜底配置(/**)需放在最后,避免覆盖精准规则。

4. 核心过滤器源码解析:AccessControlFilter

AccessControlFilter是权限控制过滤器的顶层抽象类,定义了权限校验的核心流程,其onPreHandle方法是校验入口:

java 复制代码
public abstract class AccessControlFilter extends PathMatchingFilter {
    /**
     * 请求处理前的核心校验方法,权限控制入口
     * @param request 请求对象
     * @param response 响应对象
     * @param mappedValue 配置参数(如roles[admin]中的admin)
     * @return true:校验通过;false:校验失败
     * @throws Exception 异常信息
     */
    public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        // 核心逻辑:允许访问则放行,否则执行拒绝处理
        return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
    }

    /**
     * 抽象方法:判断是否允许访问(子类实现具体校验逻辑)
     */
    protected abstract boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception;

    /**
     * 访问被拒绝后的处理逻辑(子类实现跳转/返回错误等)
     */
    protected abstract boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception;
}
    

核心逻辑总结:采用"短路逻辑",先通过isAccessAllowed判断是否允许访问,通过则直接放行;未通过则执行onAccessDenied处理(如跳转登录页、返回403),确保校验流程统一且高效。

六、拦截器:方法级细粒度权限控制

Shiro拦截器基于AOP思想,实现方法级别的细粒度权限控制,弥补了过滤器URL级控制的局限性。其不仅适用于Web环境,也可用于普通Java应用,适用性更广,尤其适合同一URL对应不同方法需不同权限、方法内部权限校验等复杂场景。

1. 实现原理

Shiro拦截器通过动态代理机制实现权限校验,核心流程如下:

  1. 通过注解(如@RequiresRoles、@RequiresPermissions)标记需要权限校验的方法;
  2. 项目启动时,Shiro扫描带有注解的方法,为其创建动态代理对象;
  3. 调用目标方法时,先执行代理对象中的拦截器逻辑,完成权限校验;
  4. 校验通过则执行目标方法,失败则抛出异常并中断执行。

2. 核心注解与使用示例

Shiro提供一系列注解标记方法权限需求,可作用于方法或类(类级注解对所有方法生效):

注解名称 核心作用 使用示例
@RequiresAuthentication 要求用户主动登录(排除记住我状态) @RequiresAuthentication
@RequiresGuest 要求用户为访客(未登录且非记住我) @RequiresGuest
@RequiresPermissions 要求具备指定权限(支持多权限与通配符) // 需同时具备order:add和order:edit @RequiresPermissions({"order:add", "order:edit"}) // 具备其一即可 @RequiresPermissions(value = {"order:delete", "order:query"}, logical = Logical.OR)
@RequiresRoles 要求具备指定角色(支持多角色) // 需同时具备admin和manager @RequiresRoles({"admin", "manager"}) // 具备其一即可 @RequiresRoles(value = {"user", "guest"}, logical = Logical.OR)
@RequiresUser 要求为有效用户(登录/记住我) @RequiresUser public List getUserOrders() { ... }

3. 使用注意事项

  • 注解生效条件:集成Spring时需确保Shiro AOP自动代理开启(默认开启),否则注解无法被扫描;
  • 异常处理:校验失败会抛出UnauthorizedException(无权限)、UnauthenticatedException(未登录)等,需通过全局异常处理器捕获并返回友好响应;
  • 优先级:过滤器校验优先于拦截器,过滤器校验失败时,不会执行到拦截器逻辑;
  • 非Web适用:不依赖Web容器,可在普通Java应用中手动创建代理对象使用。

七、总结与实践建议

Shiro登录验证与鉴权的核心价值,在于通过"过滤器+拦截器"的双层架构,实现了"URL级粗粒度控制+方法级细粒度控制"的全链路安全防护。过滤器负责前置拦截,快速阻断非法请求;拦截器负责后置精准管控,适配复杂业务权限需求,两者协同构建了灵活、高效的安全体系。

实践应用建议:

通过合理运用Shiro的核心组件与流程,可快速构建稳固的系统安全架构,兼顾开发效率与安全可靠性。

相关推荐
小杨同学492 小时前
C 语言实战:枚举类型实现数字转星期(输入 1~7 对应星期几)
前端·后端
码头整点薯条2 小时前
基于Java实现的简易规则引擎(日常开发难点记录)
java·后端
Codelinghu2 小时前
「 LLM实战 - 企业 」构建企业级RAG系统:基于Milvus向量数据库的高效检索实践
人工智能·后端·llm
d***81722 小时前
springboot 修复 Spring Framework 特定条件下目录遍历漏洞(CVE-2024-38819)
spring boot·后端·spring
2***d8852 小时前
Spring Boot中的404错误:原因、影响及处理策略
java·spring boot·后端
c***69302 小时前
Springboot项目:使用MockMvc测试get和post接口(含单个和多个请求参数场景)
java·spring boot·后端
6***A6632 小时前
Springboot中SLF4J详解
java·spring boot·后端
tonydf2 小时前
在Blazor Server中集成docx-preview.js实现高保真Word预览
后端
用户948357016512 小时前
告别乱七八糟的返回格式:手把手带你封装生产级 Result 实体
后端