news 2026/10/29 2:15:13

spring 的 @CookieValue 注解 用法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
spring 的 @CookieValue 注解 用法

@CookieValue是 Spring MVC 中用于访问 HTTP Cookie 的核心注解,它提供了一种简洁高效的方式将 Cookie 值绑定到控制器方法参数上。

核心属性详解
属性类型默认值说明
value/nameString“”Cookie 名称
requiredbooleantrue是否必须存在
defaultValueStringValueConstants.DEFAULT_NONE默认值(当 Cookie 不存在时使用)
  • value / name :指定要读取的Cookie名称,两者是互为别名的关系,用哪个都行。
  • required :默认是 true ,表示如果取不到这个Cookie,框架直接抛异常,最终表现是接口返回400错误。
  • defaultValue :默认值是 ValueConstants.DEFAULT_NONE ,一个内部约定字符串,用来区分"用户没有设置默认值"和"用户设置的默认值是空字符串"这两种情况。

使用demo:

@GetMapping("/dashboard") public String dashboard(@CookieValue("SESSION_ID") String sessionId) { // 使用sessionId获取用户信息 User user = sessionService.getUser(sessionId); return "dashboard"; }
@GetMapping("/preferences") public String preferences(@CookieValue(value = "THEME", defaultValue = "light") String theme) { // 如果Cookie不存在,使用默认值"light" return "preferences-" + theme; }

使用案例

一、用Cookie给用户打组

AB实验是 @CookieValue 非常典型的应用场景。运营同学做新功能灰度时,往往希望一批用户看到新版界面,另一批用户看到旧版界面,并保证同一个用户多次访问始终落在同一分组里。通常的做法是用一个独立的Cookie记录用户分组,比如 AB_TEST_GROUP=experiment 。

@GetMapping("/recommend") public RecommendResult recommend( @CookieValue(name = "AB_TEST_GROUP", defaultValue = "control") String group) { boolean isExperiment = "experiment".equals(group); return recommendService.getRecommend(isExperiment); }

这个接口在 group 为 experiment 时走新版推荐策略,其它情况走老版策略。由于设置了 defaultValue = "control" ,新用户第一次没被种Cookie时也不会报错,框架自动兜底到对照组。

这一类需求最容易被忽略的细节是: AB_TEST_GROUP 这个Cookie的 Path 和 Domain 一定要和页面请求路径保持一致,否则会出现用户明明被种了实验分组,接口却拿不到值的现象。

二、用户维度限流:自定义注解+AOP+@CookieValue

先定义一个限流注解:

@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface UserRateLimit { int qps() default 10; int windowSeconds() default 60; }

再写一个AOP切面,从Cookie中取用户身份:

@Aspect @Component public class UserRateLimitAspect { @Around("@annotation(userRateLimit)") public Object limit(ProceedingJoinPoint joinPoint, UserRateLimit userRateLimit) throws Throwable { ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attrs.getRequest(); String userId = resolveUserIdFromCookie(request); if (userId == null) { throw new UnauthorizedException("未登录"); } String key = "rate:user:" + userId; long count = redisTemplate.opsForValue().increment(key); if (count == 1) { redisTemplate.expire(key, Duration.ofSeconds(userRateLimit.windowSeconds())); } if (count > userRateLimit.qps()) { throw new RateLimitException("请求过于频繁"); } return joinPoint.proceed(); } private String resolveUserIdFromCookie(HttpServletRequest request) { Cookie[] cookies = request.getCookies(); if (cookies == null) { return null; } for (Cookie cookie : cookies) { if ("USER_ID".equals(cookie.getName())) { return cookie.getValue(); } } return null; } }

然后在接口上直接用注解:

@GetMapping("/submit") @UserRateLimit(qps = 5, windowSeconds = 60) public Result submit() { // 业务逻辑 }

这个组合拳的价值在于:Controller方法里不需要出现任何Cookie相关代码,限流逻辑完全沉淀在切面层,业务代码保持干净。如果你对"根据Cookie识别用户"和"基于注解做接口限流"这两个点都感兴趣,非常推荐把上面这套组合方式应用到项目里,实测下来比在拦截器里写一段模板代码可维护得多。

三、多语言与主题偏好:一个注解完成偏好读取

用户偏好类Cookie也非常适合@CookieValue。比如站点支持多语言,用户切换语言时前端写一个LANG=zh-CN的Cookie,后端接口读取这个值动态组装文案或数据。

@GetMapping("/article") public ArticleVO getArticle( @CookieValue(name = "LANG", defaultValue = "zh-CN") String lang, @CookieValue(name = "THEME", defaultValue = "light") String theme) { ArticleVO article = articleService.findById(articleId); article.setTheme(theme); article.setContent(i18nService.localize(article.getContent(), lang)); return article; }

这里两个@CookieValue共用同一个请求,框架会分别独立解析两个不同名称的Cookie,互不干扰。即使浏览器只带了其中一个,另一个也会被defaultValue兜住,不会因为缺少一个偏好项导致整个接口报400。

高频问题速查表
现象可能原因解决办法
接口返回400 Bad Requestrequired=true但请求未携带Cookie设置required=false或补充defaultValue
拿到null但浏览器明明有CookieCookie名不匹配;依赖了参数名推断;Path范围不对显式指定name/value,检查Cookie作用域
Cookie值中文或特殊字符乱码Cookie值不满足RFC 6265,未做编码写入时URLEncoder.encode,读取时URLDecoder.decode
前端JS拿不到CookieCookie设置了HttpOnly服务端读取不受影响,前端通过接口获取
跨域请求带不上CookieSameSite限制或跨域未配置withCredentials合理设置SameSite,前端请求开启凭证携带

四个让我记忆深刻的教训


第一个教训:在旧接口上新增 @CookieValue 参数,没有设置 required=false ,导致线上大量老客户端请求直接400。老客户端不会主动携带新Cookie,而新参数又强制必填,这一改直接把兼容性改没了。从那以后,凡是给已有接口加Cookie参数,我一律先把 required 设为 false 或者给默认值,经过版本迭代确认客户端都改造完成后再收紧。

第二个教训: @CookieValue String user 依赖参数名推断,结果项目编译参数没保留方法名,Spring里拿到的参数名变成 arg0 ,线上一直查不到这个Cookie,排查了一下午才发现是对不上参数名。这个坑在上面2.3节已经详细说过,这里再强调一次:显示写 name ,杜绝一切隐性依赖。

第三个教训:Cookie值里放了签名信息,但签名结果中包含 + 、 = 、 / 等字符,写入和读取时没有做URL编码,导致服务端拿到的值和生成签名时的原文不一致,验签永远失败。这个问题的本质是Cookie值和URL传输之间的编码约定不一致,解决方式是在服务端写入前统一编码、读取后统一解码。

第四个教训:有人图省事在Controller里直接用 @CookieValue 取用户角色做权限判断,绕过了拦截器的统一鉴权,结果Cookie被篡改后越权访问了管理接口。Cookie本来就是客户端可控数据,不能作为安全边界,权限判断必须依赖服务端签发的Token和统一鉴权组件, @CookieValue 只负责把值取出来,不负责验证值的真伪。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/29 2:14:11

DeepSeek V4.1 Flash、MiniMax M3.1 Flash 与 Kimi K3.1 Pro:你的 Agent 该选谁?

截至2026年10月26日,DeepSeek V4.1 Flash、MiniMax M3.1 Flash与Kimi K3.1 Pro分别是DeepSeek、MiniMax和月之暗面已发布的通用模型,均支持视觉与工具调用。三者提供百万级上下文,但思考控制、计费和权重开放进度不同。10月24日发布的K3.1 Pr…

作者头像 李华
网站建设 2026/10/29 2:13:14

20页里9页AI根本没看过:GEOInflow实测,用盲区数说话

SEO时代有一件事是确定的:Googlebot来没来、抓了哪页、收录没有,Search Console会给你答案。AI时代这件事归零了——ChatGPT、Claude、豆包的一次回答可能正在引用你的内容,但它们的爬虫来没来、读了哪几页、漏了哪几页,没有任何平…

作者头像 李华
网站建设 2026/10/29 2:12:57

油气行业数字化转型对定位服务的新要求

油气行业的数字化转型正在从数据采集走向业务重构:场站少人化无人化运行、管道完整性精细管理、生产系统统一数据治理,这些转变都指向同一个基础问题——每一台设备、每一名人员、每一段管道,都需要被准确地放置在时空坐标之中。位置数据由此…

作者头像 李华
网站建设 2026/10/29 2:12:24

靶场通关13--westwild

靶场通关13–westwild靶机:WestWild: 1.1(VulnHub,作者 Hashim Alshareff) 难度:Intermediate 类型:boot2root(Linux / SMB 凭据泄漏 sudo 提权) 攻击机:Kali 192.168…

作者头像 李华
网站建设 2026/10/29 2:11:33

【共创稿事节】Scroll嵌套List的手势冲突

Scroll 套 List 是 ArkUI 里最经典的嵌套滚动组合:页面纵向滚动,里面一个横向分类条或纵向子列表。默认手势行为却常常和预期不符——子列表滚到底,外层 Scroll 不接手,页面卡住;或者反过来,子列表刚滚一点,外层就把整个手势抢走。健康菜谱助手的首页改版为此折腾了三天,这篇把…

作者头像 李华