Session登陆实践
Session登录是一种常见的Web应用程序身份验证和状态管理机制。当用户成功登录到应用程序时,服务器会为其创建一个会话(session),并在会话中存储有关用户的信息。这样,用户在与应用程序交互的整个会话期间都可以被识别,并且可以维护其状态。
以下是Session登录的基本流程:
- 用户提交登录请求: 用户在应用程序的登录页面输入用户名和密码,然后提交登录表单。
- 服务器验证身份: 应用程序服务器接收到登录请求后,会验证用户提供的用户名和密码是否匹配数据库中存储的凭据。如果验证成功,将进入下一步;否则,用户将收到身份验证失败的通知。
- 创建Session: 一旦用户身份验证成功,服务器会为该用户创建一个唯一的会话标识符(Session ID)。通常,这个Session ID会被存储在用户的浏览器中,例如通过Cookie或URL参数的方式。
- 存储用户信息: 服务器将与用户相关的信息(如用户ID、角色等)存储在该Session中。这些信息可以在整个会话期间用于标识用户和维护其状态。
- 返回登录成功响应: 服务器向用户的浏览器返回登录成功的响应,可能包括一些用户信息或重定向到用户的个人资料页面。
- 保持会话状态: 在用户与应用程序交互的过程中,服务器会根据Session ID识别用户,并使用存储在Session中的信息来维护用户的状态。这可以包括用户的登录状态、权限、购物车内容等。
- 注销处理: 用户在应用程序中选择注销时,服务器会销毁与用户关联的Session,用户需要重新进行身份验证才能再次访问受保护的资源。
单机Session登陆
单机(单节点)Session登录是指在单一服务器环境中进行用户身份验证和会话管理的方式。这种情况下,用户的身份信息和会话状态仅存储在单个服务器上,而不涉及多个服务器之间的共享
实践
以下案例基于SpringBoot
Controller
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {/*** 用户登录** @param userLoginRequest* @param request* @return*/@PostMapping("/login")public BaseResponse<UserVO> userLogin(@RequestBody UserLoginRequest userLoginRequest, HttpServletRequest request) {if (userLoginRequest == null) {throw new BusinessException(ErrorCode.PARAMS_ERROR);}String userAccount = userLoginRequest.getUserAccount();String userPassword = userLoginRequest.getUserPassword();if (StringUtils.isAnyBlank(userAccount, userPassword)) {throw new BusinessException(ErrorCode.PARAMS_ERROR);}User user = userService.userLogin(userAccount, userPassword, request);UserVO userVO = new UserVO();BeanUtils.copyProperties(user, userVO);return ResultUtils.success(userVO);}/*** 用户注销** @param request* @return*/@PostMapping("/logout")public BaseResponse<Boolean> userLogout(HttpServletRequest request) {if (request == null) {throw new BusinessException(ErrorCode.PARAMS_ERROR);}boolean result = userService.userLogout(request);return ResultUtils.success(result);}}
Service
/*** 用户登陆*/
@Override
public User userLogin(String userAccount, String userPassword, HttpServletRequest request) {// 1. 校验if (StringUtils.isAnyBlank(userAccount, userPassword)) {throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");}if (userAccount.length() < 4) {throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号错误");}if (userPassword.length() < 8) {throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码错误");}// 2. 加密String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());// 查询用户是否存在QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.eq("userAccount", userAccount);queryWrapper.eq("userPassword", encryptPassword);User user = userMapper.selectOne(queryWrapper);// 用户不存在if (user == null) {log.info("user login failed, userAccount cannot match userPassword");throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户不存在或密码错误");}// 3. 记录用户的登录态 request.getSession().setAttribute(USER_LOGIN_STATE, user); // USER_LOGIN_STATE是常量可自定义return user;
}/*** 用户注销** @param request*/
@Override
public boolean userLogout(HttpServletRequest request) {if (request.getSession().getAttribute(USER_LOGIN_STATE) == null) {throw new BusinessException(ErrorCode.OPERATION_ERROR, "未登录");}// 移除登录态request.getSession().removeAttribute(USER_LOGIN_STATE);return true;
}/*** 获取当前登录用户** @param request* @return*/
@Override
public User getLoginUser(HttpServletRequest request) {// 先判断是否已登录Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);User currentUser = (User) userObj;if (currentUser == null || currentUser.getId() == null) {throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);}// 从数据库查询(追求性能的话可以注释,直接走缓存)long userId = currentUser.getId();currentUser = this.getById(userId);if (currentUser == null) {throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);}return currentUser;
}
流程解答
HttpServletRequest
中的 Session
是一个表示用户会话的对象,它属于Java Servlet API 中的 HttpSession
接口。HttpSession
提供了一种在请求之间存储和检索用户特定数据的方式,允许在整个用户会话期间保持状态信息。
在HttpServletRequest
中,你可以通过调用getSession()
方法来获取与当前请求相关联的HttpSession
对象。例如:
HttpSession session = request.getSession();
getSession()
方法会检查请求中是否存在与会话相关的标识符(通常是Cookie中的JSESSIONID
),如果存在,则返回与该标识符相关联的HttpSession
对象;如果不存在,则创建一个新的HttpSession
对象,并在响应中将新的会话标识符(JSESSIONID
)发送给客户端。
HttpSession
的结构是一个键值对的存储结构,类似于一个Map
。你可以使用setAttribute
和getAttribute
方法来设置和获取会话中的属性。例如:
// 设置会话属性
session.setAttribute("USER_LOGIN_STATE", "userId");// 获取会话属性
String userId = (String) session.getAttribute("USER_LOGIN_STATE");
这里的会话属性是根据键值对存储在HttpSession
中的,你可以根据需要在会话中存储和检索数据,以便在用户的整个会话期间保持状态。
会话通常在用户访问应用程序时被创建。当用户首次访问应用程序时,Servlet容器会为其创建一个新的HttpSession
对象,并将其与请求关联。这个会话对象将持续存在,直到会话过期、用户注销或关闭浏览器。会话的过期时间可以通过配置进行调整。
总而言之,HttpServletRequest
中的Session
是一个HttpSession
对象,它在用户访问应用程序时被创建,用于在请求之间共享和保持用户状态信息。
设置Sesion有效期
在项目的Resource目录下的application.yml
或application.properties
中修改配置
spring:#session的失效时间 86400s = 1天session:timeout: 86400
分布式Session登陆
单机模式下,不同的Session对象都被保存在同一个服务器中,服务器根据保存在用户浏览器Cookie中的SessionId查找Session对象
假如我们的服务端是分布式的,也就是有多台服务器同时提供服务,假如在用户登陆请求发送到服务器A,服务器A保存了<SessionId, 用户Id>;突然服务器A宕机,接下来当前用户的所有请求将要发送到服务器B,但此时服务器B中没有当前保存当前用户的登陆态,显然单机Session登陆不太适合分布式下的用户登陆
解决方案:
单机Session登陆的局限在于,Session保存在一台服务器上,其他服务器无法获取用户是否登陆;那么把用户登陆的Session保存在所有服务器都能获取的地方不久好了嘛
这里介绍最常用的解决方案也就是使用Redis存储Session
实践
将单机Session登陆改为分布式Session登陆只需要以下配置👇
需要安装Redis,这里就不介绍怎么安装了,可自行上网搜索
在项目的pom.xml文件中引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId>
</dependency>
spring-boot-starter-data-redis
是SpringBoot简化操作Redis的依赖,spring-session-data-redis
是简化将Session保存在Redis 的依赖
在项目的Resource目录下的application.yml
或application.properties
中修改配置
spring:session:storeType: redis # 使用Redis存储Sessiontimeout: 86400 #session的失效时间 86400s = 1天redis:port: 6379 #Redis所在的端口号host: xxx.xxx.xxx.xxx # Redis的远程地址,部署在单机可以写localhostdatabase: 0password: xxx #Redis没有密码,可以删除此行
只需要添加依赖和修改一下配置即可将单机Session登陆改为分布式Session登陆,这就是SpringBoot的强大之处🐮
此时我们登陆一下就可以看见Redis中存储了用户的Session
如果觉得本篇文章对您有帮助,可否点个小赞😺;篇幅较长建议收藏🫠;关注一手等待后续更新更多干货🚀