关于cookie

 

      前几天在记录用户信息时用到了cookie,由于cookie在写用户信息时含有特殊字符@,由于需求明确要求用户名带有@,纠结了一番。其实有个最好的解决方案,就是通过URLEncode来进行编码,在客户端可以通过URLDecode进行解码,由于用此方案需要各个模块改的东西比较多,网站其它模块依赖用户中心写用户信息的api,此动作需要涉及其它部门协调修改,最终没有使用这个方案。最后从头开始对cookie研究了一番,首先介绍一下cookie的一些基本信息。

    Cookie概念:

Cookie的格式实际上是一段纯文本信息, 由服务器随着网页一起发送到客户端, 并保存在客户端硬盘中指定的目录的. 大家都传说Cookie会造成严重的安全威胁什么的, 

其实不是这么回事情. 服务器读取Cookie的时候, 只能够读取到这个服务器相关的信息.。

而且, 浏览器一般只允许存放300个Cookie, 每个站点最多存放20个, 而且, 每个Cookie的大小现在在4K, 根本不会占用多少空间。并且 Cookie是有时效性质的. 例如, 设置了Cookie的存活时间为1分钟, 则一分钟后这个Cookie就会被浏览器删除。

    Cookie版本:

目前有两个版本:

版本0 : 由Netscape公司制定的,也被几乎所有的浏览器支持. Java中为了保持兼容性, 目前只支持到版本0, Cookie的内容中不能空格,方括号,圆括号,等于号(=),逗号,双引号,斜杠,问号,@符号,冒号,分号。

版本1 : 根据RFC 2109(http://www.ietf.org/rfc/rfc2109.txt)文档制定的. 放宽了很多限制. 上面所限制的字符都可以使用. 但为了保持兼容性, 应该尽量避免使用这些特殊字符。

 

    由于线上运行的tomcat服务器的版本是6.0.29,在写入cookie后,发现含有特殊字符@的用户名自动被tomcat加了两个双引号,tomcat6.0.16版本以下的tomcat不会存在以上问题。

在反复纠结后最终决定在http的header里写入带有@的用户名,通过方法request.addHeader("set-Cookie","name=xxx;value=123@163.com;"),发现cookie确实被写成功了且没有双引号,但是通过request.getCookie*()获取cookie时发现读取

的信息的不全了,比如cookie的值是123@163.com 使用request.getCookies()获取cookie的值得到的是123,而@符号后面的就没了。在tomcattomcat6.0.16版本以下不存在上述问题,读写含有@的cookie信息时正常的。

    于是看了大体看了一下tomcat关于cookie的处理。

    我首先选择了tomcat6.0.29的源码。org.apache.tomcat.util.http.Cookies的定义了一个特殊字符常量。

      public static final char[] SEPARATORS = { '\t', ' ', '"', '(', ')', ',', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '{', '}' };

      org.apache.tomcat.util.http.ServerCookie定义了如下变量和方法判断是否是非法字符。

      private static final String tspecials2 = "()<>@,;:\\\"/[]?={} \t";

      public static boolean isToken2(String value, String literals) {
	    String tspecials2 = literals == null ? "()<>@,;:\\\"/[]?={} \t" : literals;
	    if (value == null) return true;
	    int len = value.length();

	    for (int i = 0; i < len; i++) {
	      char c = value.charAt(i);
	      if (tspecials2.indexOf(c) != -1)
		return false;
	    }
	    return true;
      }
 

再具体看一下tomcat6.0.14版本对cookie的特殊字符的处理。org.apache.tomcat.util.http.ServerCookie定义了如下方法判断是否是非法字符。

public static boolean isToken2(String value) {

	    if (value == null) return true;
	    int len = value.length();

	    for (int i = 0; i < len; i++) {
	      char c = value.charAt(i);

	      if ((c < ' ') || (c >= '') || (",; \"".indexOf(c) != -1))
		return false;
	    }
	    return true;
	  }
  

 通过源码对比发现两个版本对cookie的特殊字符处理不同,导致了以上问题。所以在处理含有特殊字符的cookie时,最佳的解决方案是通过URL编码,js进行解码。

简要介绍一下js进行编码、解码通常的方法:

encodeURI() 函数可把字符串作为 URI 进行编码。该方法不会对 ASCII 字母和数字进行编码,也不会对这些 ASCII 标点符号进行编码: - _ . ! ~ * ' ( ) 。  该方法的目的是对 URI 进行完整的编码,因此对以下在 URI 中具有特殊含义的 ASCII 标点符号,encodeURI() 函数是不会进行转义的:;/?:@&=+$,#。如果 URI 组件中含有分隔符,比如 ? 和 #,则应当使用 encodeURIComponent() 方法分别对各组件进行编码,此方法的解码为decodeURI()。

  escape() 函数可对字符串进行编码,这样就可以在所有的计算机上读取该字符串。该方法不会对 ASCII 字母和数字进行编码,也不会对下面这些 ASCII 标点符号进行编码: - _ . ! ~ * ' ( ) 。其他所有的字符都会被转义序列替换, 可以使用 unescape() 对 escape() 编码的字符串进行解码。

  encodeURIComponent() 函数可把字符串作为 URI 组件进行编码。该方法不会对 ASCII 字母和数字进行编码,也不会对这些 ASCII 标点符号进行编码: - _ . ! ~ * ' ( ) 。  其他字符(比如 :;/?:@&=+$,# 这些用于分隔 URI 组件的标点符号),都是由一个或多个十六进制的转义序列替换的,此方法解码方式decodeURIComponent。

 

  最好介绍一下cookie的生命周期,cookie的生命周期通常可以用cookie.setMaxAge(time)来的设定,time<0表示当前浏览器生效,time=0清除当前cookie,time>0表示当前cookie的

  时间单位为秒。但是在不同的tomcat版本和浏览器会有不同的情况,当TOMCAT服务器是6.0.16及一下版本时,则不支持含有特殊符号的cookie值,这样的cookie存储在浏览器全部关闭就就会失效。

          TOMCAT版本都是5.5,所以在IE下设置cookie值时,如果设置了version为1或者cookie中含有特殊符号,则存储的cookie不管是否设置了失效时间,都会在浏览器关闭后自动失效。

 

 

 

 

<think>我们来实现一个完整的Spring Boot示例,展示如何使用Cookie和Session进行用户状态管理。 需求:用户访问/login进行登录(模拟),服务器创建会话并将用户信息存入session,同时返回一个包含session ID的Cookie。 当用户访问/profile时,通过Cookie中的session ID获取用户信息。 步骤: 1. 创建Spring Boot项目,添Spring Web和Spring Session依赖(这里使用内存session,生产环境建议Redis) 2. 实现登录接口,设置Cookie 3. 实现个人资料接口,从Session读取数据 注意:为了简化,我们使用内存Session(默认)并模拟用户登录。 依赖(pom.xml): <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-core</artifactId> </dependency> 代码实现:</think>### Cookie与Session实践示例:用户登录状态管理 以下是一个完整的Spring Boot实现,展示如何通过Cookie传递Session ID并在服务端管理用户状态: ```java import org.springframework.web.bind.annotation.*; import javax.servlet.http.*; import java.util.*; @RestController public class AuthController { // 模拟用户数据库(实际应使用数据库) private static final Map<String, String> USER_DB = Map.of( "admin", "admin123", "user", "password" ); // 登录接口:验证凭证并创建会话 @PostMapping("/login") public ResponseEntity<String> login( @RequestParam String username, @RequestParam String password, HttpServletRequest req, HttpServletResponse res ) { // 1. 验证用户凭证 if (!USER_DB.containsKey(username) || !USER_DB.get(username).equals(password)) { return ResponseEntity.status(401).body("用户名或密码错误"); } // 2. 创建唯一会话ID String sessionId = UUID.randomUUID().toString(); // 3. 在服务端存储Session数据(实际应使用Redis) HttpSession session = req.getSession(true); session.setAttribute("username", username); session.setAttribute("loginTime", new Date()); session.setMaxInactiveInterval(30 * 60); // 30分钟超时 // 4. 设置安全Cookie传递Session ID Cookie sessionCookie = new Cookie("SESSION_ID", sessionId); sessionCookie.setHttpOnly(true); sessionCookie.setSecure(true); // 生产环境应开启HTTPS sessionCookie.setPath("/"); sessionCookie.setMaxAge(30 * 60); // 与Session超时一致 res.addCookie(sessionCookie); return ResponseEntity.ok("登录成功!"); } // 个人资料接口:通过Session获取用户数据 @GetMapping("/profile") public ResponseEntity<String> profile(HttpServletRequest req) { // 1. 获取Session对象(不自动创建新Session) HttpSession session = req.getSession(false); // 2. 验证会话有效性 if (session == null || session.getAttribute("username") == null) { return ResponseEntity.status(401).body("请先登录"); } // 3. 返回个性化数据 String username = (String) session.getAttribute("username"); Date loginTime = (Date) session.getAttribute("loginTime"); return ResponseEntity.ok(String.format( "用户: %s\n登录时间: %tT\n会话ID: %s", username, loginTime, session.getId() )); } // 登出接口:销毁会话 @PostMapping("/logout") public ResponseEntity<String> logout(HttpServletRequest req, HttpServletResponse res) { // 1. 使当前Session失效 HttpSession session = req.getSession(false); if (session != null) { session.invalidate(); } // 2. 清除客户端Cookie Cookie cookie = new Cookie("SESSION_ID", null); cookie.setPath("/"); cookie.setMaxAge(0); // 立即过期 res.addCookie(cookie); return ResponseEntity.ok("已退出登录"); } } ``` ### 关键安全机制说明 1. **会话固定攻击防护** ```java // 登录成功后重置Session ID req.changeSessionId(); ``` 2. **会话劫持防护** ```java // 绑定客户端指纹 String fingerprint = req.getHeader("User-Agent") + "@" + req.getRemoteAddr(); session.setAttribute("client_fingerprint", sha256(fingerprint)); // 每次请求验证指纹 public boolean validateFingerprint(HttpServletRequest req) { String current = req.getHeader("User-Agent") + "@" + req.getRemoteAddr(); String stored = (String) req.getSession().getAttribute("client_fingerprint"); return stored != null && stored.equals(sha256(current)); } ``` 3. **Cookie安全属性强化** ```properties # application.properties配置 server.servlet.session.cookie.http-only=true server.servlet.session.cookie.secure=true server.servlet.session.cookie.same-site=lax ``` ### 测试流程 1. **登录** ```bash curl -X POST http://localhost:8080/login \ -d "username=admin&password=admin123" \ -c cookies.txt ``` 2. **访问个人资料** ```bash curl -b cookies.txt http://localhost:8080/profile # 返回:用户: admin 登录时间: 14:30:25 会话ID: D2E5F7... ``` 3. **登出** ```bash curl -X POST -b cookies.txt http://localhost:8080/logout ``` ### 会话存储优化(Redis集成) ```java // 添依赖:spring-boot-starter-data-redis @Configuration @EnableRedisHttpSession public class RedisSessionConfig { @Bean public LettuceConnectionFactory connectionFactory() { return new LettuceConnectionFactory(); // 默认连接localhost:6379 } } ``` > 配置后Session自动存储在Redis,支持分布式扩展
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值