cookie、session理论
为什么要设计cookie?
HTTP 是无状态协议,它不对之前发生过的请求和响应的状态进行管理。也就是说,无法根据之前的状态进行本次的请求处理。但是为了实现一些有状态的情况,根据上次的请求来对下次请求处理。如登录认证等,就设计了cookie来在浏览器端管理状态。cookie和session的区别?
cookie保存在客户端浏览器,session保存在服务器。-
cookie的属性
cookie的种类
内存cookie:特点在于cookie保存在内存中,浏览器关闭,cookie消失,属于非持久性cookie。
磁盘cookie:特点在于cookie保存在磁盘中,设置一个过期时间,如果用户手动清除了cookie或者cookie已经到了过期时间,那么硬盘cookie才会被删除,否则就会一直存在。cookie过期时间
HTTP请求头未设置cookie到期时间expires属性:cookie存储在客户端的内存(浏览器占用的内存)中,不会写入磁盘。当浏览器关闭时,cookie将从此永久丢失。
HTTP请求头设置cookie到期时间expires属性:存储在客户端的磁盘中,在指定的到期日期,cookie将从磁盘中删除。-
cookie与session交互过程
当客户端浏览器第一次发请求给服务器时,当前的客户端cookie中没有会话session id,此时服务器会分配一个新的session,将session中的id存储到cookie中返回给客户端浏览器(通过响应头Set-Cookie字段),浏览器会缓存服务器响应的cookie(session id)。
当客户端浏览器第二次发请求给服务器时,当前的客户端会发送请求的请求头中携带cookie字段,里面存放上次的session id,那么服务器就去查找自己的session列表,这样服务器就可以识别出请求的浏览器为上次请求的浏览器,就辨清了身份。
思考,浏览器会cookie过期,服务器session也会设置过期,两种情况
cookie过期:浏览器发送给服务器请求不带cookie值,那么服务器会重新为其生成session id,便于下次请求传递。
session过期:浏览器发送给服务器请求带cookie值,那么服务器会返回401,提示其没有权限,让其重新登陆。关闭浏览器则session过期的说法
一些网站后台在响应浏览器的请求的时候,返回session id在cookie中,但是却没有指定expires过期时间字段,造成cookie只是存储在了浏览器的内存中,没有存到磁盘持久化,这样当浏览器关闭,那么内存被回收,丢失了存储session id的信息,再次访问服务器就找不到对应session了,产生了session丢失的假象,实际上session信息依然还在session列表中,只有到了过期时间或者删除了session才会让session消失。
servlet指定cookie过期时间
@GetMapping("/test/cookie")
public ResultModel<String> testCookie(HttpServletRequest request,
HttpServletResponse response) {
ResultModel<String> resultModel = new ResultModel();
resultModel.setTime(TimeUtil.getNowTime());
String sid = request.getHeader("Cookie");
if (StrUtil.isBlank(sid)) {
sid = UUID.randomUUID().toString().replace("-", "");
Cookie cookie = new Cookie("sid", sid);
cookie.setPath("/");
cookie.setMaxAge(15);
response.addCookie(cookie);
log.info("新生成sid = " + sid);
resultModel.setMsg("新生成sid = " + sid);
resultModel.setRes(sid);
return resultModel;
}
log.info("之前生成的sid = " + sid);
resultModel.setMsg("之前生成的sid = " + sid);
resultModel.setRes(sid);
return resultModel;
}
操作1:第二次请求,浏览器会自动带上Cookie值。
操作2:关闭浏览器,再打开浏览器请求同一个url,依然还会自动带上Cookie值
操作3:不停的刷新浏览器,发现Cookie值一直不变,在过去15s之后,发现浏览器的cookies消失了,再刷新生成了一个新的Cookie值。
Session验证登录
@GetMapping("/test/api")
public ResultModel<String> testApi(HttpServletRequest request) {
ResultModel<String> resultModel = new ResultModel<>();
resultModel.setTime(TimeUtil.getNowTime());
UserInfo userInfo = (UserInfo) request.getSession().getAttribute("user");
if (Objects.isNull(userInfo)) {
resultModel.setMsg("您还未登录");
return resultModel;
}
resultModel.setMsg("执行api service方法");
return resultModel;
}
@GetMapping("/test/logout")
public ResultModel<String> testLogout(HttpServletRequest request) {
ResultModel<String> resultModel = new ResultModel<>();
resultModel.setTime(TimeUtil.getNowTime());
request.getSession().removeAttribute("user");
resultModel.setMsg("用户已退出");
return resultModel;
}
@PostMapping("/test/login")
public ResultModel<UserInfo> testLogin(@RequestHeader String username,
@RequestHeader String password,
HttpServletRequest request) {
ResultModel<UserInfo> resultModel = new ResultModel<>();
resultModel.setTime(TimeUtil.getNowTime());
/**
* 通过数据库去查询username和password的正确性
* 通过new一下作为查询结果了
*/
if (username.equals("zhangsan") && password.equals("123456")) {
HttpSession session = request.getSession();
UserInfo userInfo = new UserInfo(username, password, 30, "男");
session.setAttribute("user", userInfo);
// 设置session的过期时间
session.setMaxInactiveInterval(10);
resultModel.setMsg("用户 " + username +
"登录成功,新生成的JSESSIONID = " + session.getId());
resultModel.setRes(userInfo);
return resultModel;
}
resultModel.setMsg("登录失败:用户名和密码错误");
return resultModel;
}
setMaxInactiveInterval方法:session的过期时间,单位为秒,超过指定的时间,就会自动删除session中存储的值。
第一次登录会自动生成JSESSIONID响应 :
session过期之后再登录,会自动刷新JSESSIONID,返回响应:
- 我们发现session的操作其实也是基于cookie来实现的。
- 后面为了解决每次业务操作api都要进行校验,我们可以使用过滤器来方便我们的校验。
token设计
- token的好处:
token自身就携带了身份验证的信息,不需要服务器存储session,减轻服务器压力。
session进行身份验证依赖cookie,不适合移动端,相反token可以被客户端存储,还可以跨语言使用。
session会存在跨域的问题,但是token就不会了。
基于JWT实现token
- token工具类:
public class JwtUtil {
public static final String SECRET = "haha";
public static <V> String createJWT(Map<String, Object> map, int expireTimes, int expireUnit, SignatureAlgorithm algorithm) {
JwtBuilder builder = Jwts.builder();
builder.setClaims(map);
builder.signWith(algorithm, SECRET);
Calendar instance = Calendar.getInstance();
instance.add(expireUnit, expireTimes);
builder.setExpiration(instance.getTime());
return builder.compact();
}
public static String createJWT(Map<String, Object> map, int expireTimes) {
return createJWT(map, expireTimes, Calendar.SECOND, SignatureAlgorithm.HS256);
}
public static <V> String createJWT(String key, String value, int expireTimes) {
Map<String, Object> map = new HashMap<>();
map.put(key, value);
return createJWT(map, expireTimes);
}
public static Claims parseJWT(String jwt){
return Jwts.parser().
setSigningKey(SECRET).parseClaimsJws(jwt).getBody();
}
public static void main(String[] args) {
String jwt = createJWT("1", "sunpy", 60);
System.out.println(jwt);
System.out.println(parseJWT(jwt).get("1"));
parseJWT(jwt).entrySet().forEach(entry -> {
System.out.println("key: " + entry.getKey() + " value: " + entry.getValue());
});
}
}