Session登陆实践

Session登陆实践

Session登录是一种常见的Web应用程序身份验证和状态管理机制。当用户成功登录到应用程序时,服务器会为其创建一个会话(session),并在会话中存储有关用户的信息。这样,用户在与应用程序交互的整个会话期间都可以被识别,并且可以维护其状态。

img

以下是Session登录的基本流程:

  1. 用户提交登录请求: 用户在应用程序的登录页面输入用户名和密码,然后提交登录表单。
  2. 服务器验证身份: 应用程序服务器接收到登录请求后,会验证用户提供的用户名和密码是否匹配数据库中存储的凭据。如果验证成功,将进入下一步;否则,用户将收到身份验证失败的通知。
  3. 创建Session: 一旦用户身份验证成功,服务器会为该用户创建一个唯一的会话标识符(Session ID)。通常,这个Session ID会被存储在用户的浏览器中,例如通过Cookie或URL参数的方式。
  4. 存储用户信息: 服务器将与用户相关的信息(如用户ID、角色等)存储在该Session中。这些信息可以在整个会话期间用于标识用户和维护其状态。
  5. 返回登录成功响应: 服务器向用户的浏览器返回登录成功的响应,可能包括一些用户信息或重定向到用户的个人资料页面。
  6. 保持会话状态: 在用户与应用程序交互的过程中,服务器会根据Session ID识别用户,并使用存储在Session中的信息来维护用户的状态。这可以包括用户的登录状态、权限、购物车内容等。
  7. 注销处理: 用户在应用程序中选择注销时,服务器会销毁与用户关联的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。你可以使用setAttributegetAttribute方法来设置和获取会话中的属性。例如:

// 设置会话属性
session.setAttribute("USER_LOGIN_STATE", "userId");

// 获取会话属性
String userId = (String) session.getAttribute("USER_LOGIN_STATE");

这里的会话属性是根据键值对存储在HttpSession中的,你可以根据需要在会话中存储和检索数据,以便在用户的整个会话期间保持状态。

会话通常在用户访问应用程序时被创建。当用户首次访问应用程序时,Servlet容器会为其创建一个新的HttpSession对象,并将其与请求关联。这个会话对象将持续存在,直到会话过期、用户注销或关闭浏览器。会话的过期时间可以通过配置进行调整。

总而言之,HttpServletRequest中的Session是一个HttpSession对象,它在用户访问应用程序时被创建,用于在请求之间共享和保持用户状态信息。

设置Sesion有效期

在项目的Resource目录下的application.ymlapplication.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.ymlapplication.properties中修改配置

spring:
  session:
  	storeType: redis # 使用Redis存储Session
    timeout: 86400 #session的失效时间 86400s = 1天
  redis:
    port: 6379 #Redis所在的端口号
    host: xxx.xxx.xxx.xxx # Redis的远程地址,部署在单机可以写localhost
    database: 0
    password: xxx #Redis没有密码,可以删除此行

只需要添加依赖和修改一下配置即可将单机Session登陆改为分布式Session登陆,这就是SpringBoot的强大之处🐮

此时我们登陆一下就可以看见Redis中存储了用户的Session

在这里插入图片描述
在这里插入图片描述
如果觉得本篇文章对您有帮助,可否点个小赞😺;篇幅较长建议收藏🫠;关注一手等待后续更新更多干货🚀

相关推荐

  1. gin实现登录逻辑,包含cookie,session

    2024-03-10 00:14:06       50 阅读
  2. Redis 实现分布式Session 登录相关细节

    2024-03-10 00:14:06       30 阅读
  3. go-gin中session实现redis前缀和db库选择+单点登录

    2024-03-10 00:14:06       25 阅读
  4. <span style='color:red;'>Session</span>

    Session

    2024-03-10 00:14:06      38 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-10 00:14:06       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-10 00:14:06       106 阅读
  3. 在Django里面运行非项目文件

    2024-03-10 00:14:06       87 阅读
  4. Python语言-面向对象

    2024-03-10 00:14:06       96 阅读

热门阅读

  1. LeetCode168. Excel Sheet Column Title

    2024-03-10 00:14:06       46 阅读
  2. mysql与oracle的区别

    2024-03-10 00:14:06       42 阅读
  3. HTTPS运行加密的过程

    2024-03-10 00:14:06       50 阅读
  4. 从零开始学HCIA之IPv6基础06

    2024-03-10 00:14:06       43 阅读
  5. Tomcat

    Tomcat

    2024-03-10 00:14:06      46 阅读
  6. 三分钟补算法系列(一)

    2024-03-10 00:14:06       41 阅读
  7. LeetCode买卖股票的最佳时机

    2024-03-10 00:14:06       54 阅读
  8. 云计算 3月4号 (自配本地和远程yum源)

    2024-03-10 00:14:06       42 阅读
  9. 多网卡情况下如何获取连接的ip地址c++

    2024-03-10 00:14:06       47 阅读
  10. 657.机器人能否返回原点

    2024-03-10 00:14:06       48 阅读
  11. 统计子矩阵

    2024-03-10 00:14:06       45 阅读
  12. React Redux使用详细讲解

    2024-03-10 00:14:06       45 阅读