在Web项目的安全架构中,登录验证与权限控制是保障系统安全的核心环节。Shiro作为一款轻量级的安全框架,通过过滤器(Filter)与拦截器(Interceptor)的协同工作,提供了完整且灵活的登录验证与鉴权解决方案。本文将从核心概述、核心组件、登录验证流程、权限鉴权流程、过滤器、拦截器及实践建议七个维度,系统拆解其实现逻辑与应用细节,助力开发者深入理解并灵活运用Shiro构建安全防护体系。
一、核心概述:双层安全防护体系
Shiro登录验证与鉴权的核心设计思想,是构建“过滤器链前置拦截+拦截器方法级增强”的双层管控模式。过滤器负责URL级别的粗粒度安全校验(如是否登录、URL是否允许匿名访问),确保非法请求在进入业务层前被阻断;拦截器基于AOP思想实现方法级别的细粒度权限控制(如方法所需的具体角色、权限),精准管控业务逻辑的访问权限。两者协同配合,形成从请求入口到业务执行的全链路安全校验闭环。
核心流程总览:用户发起请求后,先经过Web容器过滤器链,再进入Shiro核心过滤器链完成基础校验;校验通过后,经自定义或框架拦截器完成方法级权限校验,最终到达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. 组件间核心关系
nerror="javascript:errorimg.call(this);">组件协同逻辑:用户请求触发Subject创建,Subject通过SecurityManager调用Authenticator完成身份验证;Authenticator依托AuthenticationStrategy,从Realm获取AuthenticationInfo并通过CredentialsMatcher校验凭证;权限验证时,SecurityManager调用Authorizer,从Realm获取AuthorizationInfo完成权限匹配;过滤器与拦截器作为入口,串联各组件形成完整校验链路。
三、登录验证流程:身份合法性校验全链路
登录验证流程的核心目标是校验用户身份合法性,核心链路为“请求拦截→凭证获取→凭证校验→会话创建”,具体实现依赖Filter与核心组件的协同工作。
1. 登录验证流程总览
2. 核心方法调用链路
nerror="javascript:errorimg.call(this);">3. 关键方法解析
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); returnnull; } }); } catch (ExecutionException ex) { t = ex.getCause(); } catch (Throwable throwable) { t = throwable; }} 核心作用:预处理请求/响应对象,创建Subject并绑定到当前线程,触发后续登录验证逻辑。
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."); } returntrue; } } else { if (log.isTraceEnabled()) { log.trace("Attempting to access a path which requires authentication. Forwarding to the " + "Authentication url [" + getLoginUrl() + "]"); } // 非登录请求跳转至登录页 saveRequestAndRedirectToLogin(request, response); returnfalse; }} 核心作用:区分登录请求与普通请求,仅对登录提交请求触发验证流程,非登录请求引导至登录页。
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."; thrownew 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 + "]."; thrownew UnknownAccountException(msg); } return info;} 核心作用:根据Realm数量分发验证逻辑,支持单Realm普通验证与多Realm策略化验证,适配不同系统架构。
四、权限鉴权流程:已登录用户的权限校验
权限鉴权流程基于身份验证通过的前提,核心目标是校验已登录用户是否具备访问目标资源的权限,分为URL级(过滤器实现)和方法级(拦截器实现)两类,此处先阐述通用鉴权链路,方法级细节后续展开。
1. 权限鉴权流程总览
2. 核心方法调用链路
3. 关键方法解析
UserFilter.isAccessAllowed:用户有效性前置校验
publicclass UserFilter extends AccessControlFilter { protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { // 不拦截登录流程 if (isLoginRequest(request, response)) { returntrue; } else { // 校验用户是否已登录(含记住我状态) Subject subject = getSubject(request, response); return subject.getPrincipal() != null; } } } 核心作用:快速筛选无效用户,避免无效的后续权限校验,仅允许已登录(含记住我)用户进入权限校验环节。
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级的权限精准管控。
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)) { returntrue; } } } returnfalse;} 核心作用:通过权限匹配算法,判断用户已拥有的权限是否覆盖目标资源所需权限,是权限校验的核心逻辑实现。
五、过滤器: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基本认证过滤器:基于HTTPBasic协议验证(适用于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 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拦截器通过动态代理机制实现权限校验,核心流程如下:
- 通过注解(如@RequiresRoles、@RequiresPermissions)标记需要权限校验的方法;
- 项目启动时,Shiro扫描带有注解的方法,为其创建动态代理对象;
- 调用目标方法时,先执行代理对象中的拦截器逻辑,完成权限校验;
- 校验通过则执行目标方法,失败则抛出异常并中断执行。
2. 核心注解与使用示例
Shiro提供一系列注解标记方法权限需求,可作用于方法或类(类级注解对所有方法生效):
注解名称 | 核心作用 | 使用示例 |
@RequiresAuthentication | 要求用户主动登录(排除记住我状态) | @RequiresAuthentication |
@RequiresGuest | 要求用户为访客(未登录且非记住我) | @RequiresGuest |
@RequiresPermissions | 要求具备指定权限(支持多权限与通配符) | // 需同时具备order:add和order:edit |
@RequiresRoles | 要求具备指定角色(支持多角色) | // 需同时具备admin和manager |
@RequiresUser | 要求为有效用户(登录/记住我) | @RequiresUser public ListgetUserOrders() { ... } |
3. 使用注意事项
- 注解生效条件:集成Spring时需确保Shiro AOP自动代理开启(默认开启),否则注解无法被扫描;
- 异常处理:校验失败会抛出UnauthorizedException(无权限)、UnauthenticatedException(未登录)等,需通过全局异常处理器捕获并返回友好响应;
- 优先级:过滤器校验优先于拦截器,过滤器校验失败时,不会执行到拦截器逻辑;
- 非Web适用:不依赖Web容器,可在普通Java应用中手动创建代理对象使用。
七、总结与实践建议
Shiro登录验证与鉴权的核心价值,在于通过“过滤器+拦截器”的双层架构,实现了“URL级粗粒度控制+方法级细粒度控制”的全链路安全防护。过滤器负责前置拦截,快速阻断非法请求;拦截器负责后置精准管控,适配复杂业务权限需求,两者协同构建了灵活、高效的安全体系。
实践应用建议:
通过合理运用Shiro的核心组件与流程,可快速构建稳固的系统安全架构,兼顾开发效率与安全可靠性。

