在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. 组件间核心关系

组件协同逻辑:用户请求触发Subject创建,Subject通过SecurityManager调用Authenticator完成身份验证;Authenticator依托AuthenticationStrategy,从Realm获取AuthenticationInfo并通过CredentialsMatcher校验凭证;权限验证时,SecurityManager调用Authorizer,从Realm获取AuthorizationInfo完成权限匹配;过滤器与拦截器作为入口,串联各组件形成完整校验链路。
三、登录验证流程:身份合法性校验全链路
登录验证流程的核心目标是校验用户身份合法性,核心链路为"请求拦截→凭证获取→凭证校验→会话创建",具体实现依赖Filter与核心组件的协同工作。
1. 登录验证流程总览
2. 核心方法调用链路
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. 权限鉴权流程总览
2. 核心方法调用链路
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拦截器通过动态代理机制实现权限校验,核心流程如下:
- 通过注解(如@RequiresRoles、@RequiresPermissions)标记需要权限校验的方法;
- 项目启动时,Shiro扫描带有注解的方法,为其创建动态代理对象;
- 调用目标方法时,先执行代理对象中的拦截器逻辑,完成权限校验;
- 校验通过则执行目标方法,失败则抛出异常并中断执行。
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的核心组件与流程,可快速构建稳固的系统安全架构,兼顾开发效率与安全可靠性。