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

64 阅读12分钟

在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:请求拦截入口



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:登录请求判断



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:验证逻辑分发



// 验证策略选择
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:用户有效性前置校验



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:权限精准校验



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:权限匹配核心逻辑



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枚举定义常用内置过滤器,可直接通过枚举名称在配置文件中引用,简化配置流程:



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配置示例



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方法是校验入口:



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的核心组件与流程,可快速构建稳固的系统安全架构,兼顾开发效率与安全可靠性。