技术派当前的用户登录信息,主要借助最基础的session/cookie来实现的;现在虽然基本进入分布式session的时代了,但切实去看oauth,sso,jwt等各种登录方案之前,有必要学习最早的cookie/session,知道他的实现方式,然后再改造项目。
Session/Cookie的基本使用
在SpringBoot项目中如何使用session/cookie
1.登录入口,保存session
首先我们设计一个登录的接口,用来模拟真实场景下的登录。
@RestController
public class SessionController{
@RequestMapping(path = "/login")
public String login(String uname ,HttpSession httpSession){
httpSession.setAttribute("name" ,uname);
return "欢迎登录:" + uname;
}
}
从上面的实现中,方法定义了一个HttpSesion的参数类型,具体实现中,就是表示写入session
当session写入完毕后,在这个会话结束之前,后续所有的请求都可以直接获取到对应的session。
2.session的读取
给出两种常见的session获取方式
直接从HttpSession中获取
通过HtttpServletRequest来获取
@RequstMapping("time")
public String showTime(HttpSssion session ){
return sesion.getAttribute("name") + ",当前时间为: " + LocalDateTime.now();
}
@RequestMapping("name")
public String showName(HttpServletRequst requst){
return "当前用户" + request.getSession.getAttribute("name");
3.退出登录
有登录当然会有登出,如下
@RequestMapping(path = "logout")
public String logout(HttpSession httpSession){
//注销当前的session
httpSession.invalidate();
return "登出成功";
}
4、session实现原理
SpringBoot提供了一套非常简单的session机制,他是如何工作的?怎样识别用户身份的?session存在什么地方?这个是我们要搞清楚的。
session:在浏览器窗口打开期间,这个会话一直有效,即先访问login,在访问time,可以直接拿到name,若在此过程中,再次访问了login更新了name,那么访问time获取到的也是新的name
当浏览器关闭之后,重新访问time接口,则此时将拿不到name
核心工作原理:
借助cookie中的JESSIONID来作为用户身份标识,这个数据相同的,认为是同一个用户;然后会将session在内存中存一份,有过期时间的限制,通常每访问一次,过期时间重新刷新
当浏览器不支持cookie时,借助url重写,将sessionId写到url地址中,参数名 = jesessionid
两次的cookie中的JESSIONID是一致的。
从上面描述看,有几点关键:
session主要是存在内存中,根据用户请求cookie来识别用户身份,并且有一个过期时间(内存有大小会出现oom不)
对于用户而言,每次关闭浏览器再重新打开,会重新生成JESSIONID的cookies值,由于这个值的更改,导致后端无法记录之前访问的是谁。
技术派的身份认证
在技术派的实现中,和上面的思路没有差别,不过将读写cookie,使用session的具体逻辑,展示出来而已
当前技术派,针对前台和管理员的后台登陆,有两种,一种是基于微信公众号的,一种是传统的用户名加密码,方式不同,原理一致
具体的session/cookie的具体实例:一管理员的后台为例说明,流程为
1.登录与登出
/**
* 后台用户名 & 密码的方式登录
*
* @param request
* @param response
* @return
*/
@RequestMapping(path = {"login"})
public ResVo<BaseUserInfoDTO> login(HttpServletRequest request, HttpServletResponse response) {
String username = request.getParameter("username");
String pwd = request.getParameter("password");
String session = loginOutService.loginByUserPwd(username, pwd);
if (StringUtils.isNotBlank(session)) {
// cookie中写入用户登录信息
response.addCookie(SessionUtil.newCookie(LoginService.SESSION_KEY, session));
return ResVo.ok(userService.queryBasicUserInfo(ReqInfoContext.getReqInfo().getUserId()));
} else {
return ResVo.fail(StatusEnum.LOGIN_FAILED_MIXED, "登录失败,请重试");
}
}
/**
* 登出
*
* @param response
* @return
*/
@Permission(role = UserRole.LOGIN)
@GetMapping("logout")
public ResVo<Boolean> logOut(HttpServletResponse response) {
Optional.ofNullable(ReqInfoContext.getReqInfo()).ifPresent(s -> loginOutService.logout(s.getSession()));
// 为什么不后端实现重定向? 重定向交给前端执行,避免由于前后端分离,本地开发时端口不一致导致的问题
// response.sendRedirect("/");
// 移除cookie
response.addCookie(SessionUtil.delCookie(LoginService.SESSION_KEY));
return ResVo.ok(true);
}
重点关注上面的两步:
1.根据用户,生成对应唯一session值,保存到redis缓存
2.将session写到cookie中,返回给前端
后续用户再进行访问的时候,每次都要携带这个cookie,然后后台通过这个session来识别用户身份(所以如果这个时候cookie别别人挟持了,那么就危险了)
2.用户身份识别
接下来我们来看一看用户身份识别,核心逻辑再ReqRecordFilter
3.小结
当前技术派中的用户身份识别这套方案,与传统的cookie/session其实没有本质区别,核心思路为
- 用户登录,生成一个唯一表示
- 服务端保存这个唯一标识与用户的映射关系
- 服务端将这个标识(session)写入cookie返回给客户端
- 客户端后续请求,携带cookie
基本流程如上,重点关注相关知识点
- 如何读写cookie
- 如何保存用户信息(如session,如将用户标识与userId保存缓存)
- 在filter识别完用户身份信息
- 识别的用户身份信息,如何供整个链路使用?