【注解跳过机制】Spring Boot 登录权限校验拦截器教程

🧰 Spring Boot 登录权限校验拦截器教程(含注解跳过机制)

📌 目录

  1. 功能概述

  2. 拦截器工作流程

  3. 注解使用说明

  4. 核心逻辑分解

  5. Redis 缓存策略说明

  6. 企业状态校验逻辑

  7. 常见扩展与建议

  8. 总结


1️⃣ 功能概述

本教程讲解一个基于 Spring Boot 实现的登录权限拦截器 LoginInterceptor,其作用为:

  • 校验用户是否已登录

  • 校验 token 是否过期

  • 校验用户企业信息是否合法

  • 支持使用自定义注解 @IgnoreAuthentication 来跳过拦截校验(白名单机制)

  • 登录信息通过 Redis 缓存,提高性能与并发能力


2️⃣ 拦截器工作流程

流程如下图:

        ┌────────────────────────┐
        │   HTTP 请求进入系统     │
        └──────────┬─────────────┘
                   │
                   ▼
        是否是静态资源(.do/.jsp/.ico)?
                   │
         是 ──────► 放行
                   │
         否        ▼
         是否使用 @IgnoreAuthentication 注解?
                   │
         是 ──────► 放行
                   │
         否        ▼
           读取 token 是否存在?(Redis + DB 校验)
                   │
         否 ──────► 返回 401,非法 token
                   │
         是        ▼
     token 是否超时?(与最后操作时间比较)
                   │
         是 ──────► 删除 token,返回 401
                   │
         否        ▼
     是否绑定企业?企业是否被禁用?
                   │
         是 ──────► 删除 token,返回 401
                   │
         否        ▼
                放行


3️⃣ 注解使用说明:@IgnoreAuthentication

该自定义注解可用于方法级别类级别,用于标记当前接口跳过登录验证。

示例:

@IgnoreAuthentication
@GetMapping("/public/test")
public AjaxResult publicApi() {
    return AjaxResult.success("无需登录即可访问");
}

注解源码:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreAuthentication {}

判断逻辑(代码中):

IgnoreAuthentication ignoreAuthentication = AnnotationUtils.findAnnotation(method, IgnoreAuthentication.class);
if (ignoreAuthentication == null) {
    ignoreAuthentication = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), IgnoreAuthentication.class);
}
if (ignoreAuthentication != null) {
    return true; // 直接放行
}


4️⃣ 核心逻辑分解

🔹 静态资源直接放行

String pathName = request.getRequestURL().toString();
if (pathName.contains(".do") || pathName.contains(".jsp") || pathName.contains(".ico") || split.length <= 3) {
    return true;
}

🔹 获取当前用户

User user = AppContext.getCurrentUser();
String token = request.getHeader(AppContext.TOKEN_NAME);
 

若用户为 null,通过 token 尝试从 userLoginRecordService 查找用户登录记录:

UserLoginRecord userLoginRecord = userLoginRecordService.findByToken(token); 

找到了就重新设置到 Redis 并放行,否则响应 token 失效:

Result result = Result.failure(ResponseCode.INVALID_TOKEN); response.getWriter().write(JsonUtil.object2JSON(result)); 

🔹 操作时间间隔校验(防止长期未操作的用户继续访问)

AdminPlatformConfiguration 配置中读取超时时间(分钟):

Integer interval = adminPlatformConfiguration.getOperationTimeInteval(); 

判断逻辑:

if (now.getTime() - lastTime.getTime() > 1000 * 60 * interval) { redisTemplate.delete(token); userLoginRecordService.deleteByUserId(user.getId()); // 返回 401 } 

🔹 企业信息校验(主要用于 B 端多企业场景)

若用户绑定企业,需校验企业是否存在并且未被禁用:

Enterprise findEnterprise = enterpriseService.findById(user.getLastEnterpriseId());
if (findEnterprise == null || findEnterprise.getStatus().equals(1)) {
    redisTemplate.delete(token);
    userLoginRecordService.deleteByUserId(user.getId());
    user.setLastEnterpriseId(-1);
    userService.updateById(user);
    // 返回 401
}

5️⃣ Redis 缓存策略说明

  • 用户登录成功后,token 会作为 key,将 User 对象缓存至 Redis

  • 每次请求更新 lastTime 字段并刷新缓存

  • 超过配置时间(如 30 分钟)未操作则视为超时,token 清除,强制重新登录

缓存相关操作:

 redisTemplate.boundValueOps(token).set(user);

清除缓存:

redisTemplate.delete(token); 

6️⃣ 企业状态校验逻辑说明

企业状态的判断基于:

  • Enterprise 对象是否存在

  • status != 1 (1 通常表示禁用)

一旦不符合,即视为企业非法,清除 token 并强制退出登录。


7️⃣ 常见扩展与建议

扩展项建议实现方式
白名单 URL 配置使用配置文件定义 permitUrls,统一管理免校验路径
用户权限控制拦截器 + 注解 @RequiresPermissions
多平台 token 校验区分 Web、App、小程序等使用场景,适配多种 token 策略
多租户隔离企业 ID 或租户 ID 存入上下文,进行数据隔离控制
Token 存活检测使用 Redis TTL 机制,自带过期时间


8️⃣ 总结

本登录拦截器方案较为完整,具备以下特点:

  • ✅ 支持注解白名单机制

  • ✅ 支持 Redis 缓存用户信息

  • ✅ 支持 token 操作超时校验

  • ✅ 支持企业状态控制

  • ✅ 易于扩展(如权限控制、平台隔离等)

适合用于 中大型 B 端系统 或需要进行 多租户身份校验 的场景。
 

@Slf4j
@Component
public class LoginInterceptor extends HandlerInterceptorAdapter {

    @Resource
    private UserLoginRecordService userLoginRecordService;

    @Resource
    private EnterpriseService enterpriseService;

    @Resource
    private UserService userService;

    @Resource
    private RedisTemplate redisTemplate;

    @Autowired
    private AdminPlatformConfigurationService adminPlatformConfigurationService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String path = request.getRequestURL().toString();
        String[] split = path.split("/");

        // 放行静态资源或路径太短的请求
        if (path.contains(".do") || path.contains(".jsp") || path.contains(".ico") || split.length <= 3) {
            return true;
        }

        // 忽略认证注解判断
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();

            IgnoreAuthentication ignoreAuth = AnnotationUtils.findAnnotation(method, IgnoreAuthentication.class);
            if (ignoreAuth == null) {
                ignoreAuth = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), IgnoreAuthentication.class);
            }
            if (ignoreAuth != null) {
                return true;
            }
        }

        String token = request.getHeader("Token");
        User user = AppContext.getCurrentUser();

        // 用户上下文为空,尝试通过 token 找回登录记录
        if (user == null) {
            UserLoginRecord record = userLoginRecordService.findByToken(token);
            if (record != null) {
                user = userService.getById(record.getUserId());
                redisTemplate.boundValueOps(token).set(user);
                return true;
            }
            return unauthorized(response);
        }

        // 校验操作间隔时间
        Date now = new Date();
        Date lastTime = user.getLastTime();
        if (lastTime == null) {
            user.setLastTime(now);
            redisTemplate.boundValueOps(token).set(user);
        } else {
            AdminPlatformConfiguration config = adminPlatformConfigurationService.selectAll();
            Integer interval = config.getOperationTimeInteval();
            if (interval != null && now.getTime() - lastTime.getTime() > 1000L * 60 * interval) {
                redisTemplate.delete(token);
                userLoginRecordService.deleteByUserId(user.getId());
                return unauthorized(response);
            }
            user.setLastTime(now);
            redisTemplate.boundValueOps(token).set(user);
        }

        // 校验企业状态
        if (user.getLastEnterpriseId() != null && user.getLastEnterpriseId() > 0) {
            Enterprise enterprise = enterpriseService.findById(user.getLastEnterpriseId());
            if (enterprise == null || enterprise.getStatus().equals(1)) {
                redisTemplate.delete(token);
                userLoginRecordService.deleteByUserId(user.getId());
                user.setLastEnterpriseId(-1);
                userService.updateById(user);
                return unauthorized(response);
            }
        }

        return true;
    }

    private boolean unauthorized(HttpServletResponse response) throws Exception {
        Result result = Result.failure(ResponseCode.INVALID_TOKEN);
        response.setCharacterEncoding("utf-8");
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        response.getWriter().write(JsonUtil.object2JSON(result));
        response.getWriter().close();
        response.flushBuffer();
        return false;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值