SpringBoot 实现用户登录,分布式Session功能

您所在的位置:网站首页 session如何实现用户登陆剔除 SpringBoot 实现用户登录,分布式Session功能

SpringBoot 实现用户登录,分布式Session功能

2023-09-22 21:26| 来源: 网络整理| 查看: 265

之前介绍过不少关于登录功能的代码,本文介绍一下关于分布式Session 的功能实现,

完整代码(以后写博客,尽量给 git 地址)在 https://github.com/saysky/sensboot

通常,我们的项目都是多实例部署的,生产环境通常一个模块可能都会部署四五台服务器。

我们知道用户登录后,需要存储 session 信息,session 信息通常是存储在服务器的内存中的,不能持久化(服务器重启失效),多台服务器也不能共存。为了解决这个问题,我们可以将 session 存到几个服务器共享的地方里去,比如 Redis,只要在一个内网中,几台服务器可以共享 Redis (Redis本质也是装在某台服务器中)。

具体怎么实现呢?这里简单描述下:

用户登录成功,通过UUID生成一个随机唯一字符串,命名为 token,通过向 redis 中 set 一个值,key 为 token 字符串,value 为用户对象序列化后的字符串。当用户访问其他页面,请求方法时,检验请求参数或 cookie 中是否有 token如果有,则从 redis 查询 token,验证 token 是否有效如果没有,则抛出异常 “用户未登录”

关于参数验证,这里可以通过 SpringMVC 的 resolveArgument 方法来统一解决,即所有方法参数验证时都会验证用户名是否登录。而不需要在每个方法里都写一段检查用户名是否登录,这样就太冗余了。

下面是具体实现,由上到下(重要到次要)贴代码,完整代码在 GitHub 中可以获取。

一、基本登录

LoginController

登录的实现在 UserServiceImpl 中

package com.liuyanzhao.sens.controller; import com.liuyanzhao.sens.result.Result; import com.liuyanzhao.sens.service.UserService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletResponse; /** * @author 言曌 * @date 2019-07-22 14:07 */ @Controller @Slf4j public class LoginController { @Autowired private UserService userService; /** * 登录功能 * 验证用户名和密码,登录成功,生成token,存入到redis中 * 登录成功 * * @param response * @param username * @param password * @return */ @PostMapping("/doLogin") @ResponseBody public Result doLogin(HttpServletResponse response, @RequestParam("username") String username, @RequestParam("password") String password) { //登录 log.info("用户登录:username:{}, password:{}", username, password); //判断用户名是否存在 String token = userService.login(response, username, password); return Result.success(token); } }

UserServiceImpl

为了代码简洁,UserService 接口这里就不贴了

package com.liuyanzhao.sens.service.impl; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.plugins.Page; import com.liuyanzhao.sens.entity.User; import com.liuyanzhao.sens.exception.GlobalException; import com.liuyanzhao.sens.mapper.UserMapper; import com.liuyanzhao.sens.result.CodeMsg; import com.liuyanzhao.sens.service.UserService; import com.liuyanzhao.sens.utils.RedisUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; import java.util.UUID; /** * 用户业务逻辑实现类 * MyBatis Plus 版本 */ @Service public class UserServiceImpl implements UserService { public static final String COOKIE_NAME_TOKEN = "token"; /** * token过期时间,2天 */ public static final int TOKEN_EXPIRE = 3600 * 24 * 2; @Autowired private UserMapper userMapper; @Autowired private RedisUtil redisUtil; @Override public User findByUsername(String username) { return userMapper.findByUsername(username); } @Override public String login(HttpServletResponse response, String username, String password) { //判断用户名是否存在 User user = findByUsername(username); if (user == null) { throw new GlobalException(CodeMsg.USERNAME_NOT_EXIST); } //验证密码,这里为了例子简单,密码没有加密 String dbPass = user.getPassword(); if (!password.equals(dbPass)) { throw new GlobalException(CodeMsg.PASSWORD_ERROR); } //生成cookie String token = UUID.randomUUID().toString().replace("-", ""); addCookie(response, token, user); return token; } @Override public User getByToken(HttpServletResponse response, String token) { if (StringUtils.isEmpty(token)) { return null; } User user = JSON.parseObject(redisUtil.get(COOKIE_NAME_TOKEN + "::" + token), User.class); //重置有效期 if (user == null) { throw new GlobalException(CodeMsg.USER_NOT_LOGIN); } addCookie(response, token, user); return user; } private void addCookie(HttpServletResponse response, String token, User user) { //将token存入到redis redisUtil.set(COOKIE_NAME_TOKEN + "::" + token, JSON.toJSONString(user), TOKEN_EXPIRE); //将token写入cookie Cookie cookie = new Cookie(COOKIE_NAME_TOKEN, token); cookie.setMaxAge(TOKEN_EXPIRE); cookie.setPath("/"); response.addCookie(cookie); } }

UserMapper Dao层 和 User 实体 这里也不贴了

我相信你都学到了分布式 Session 这里,MyBatis 的基本使用应该不成问题吧

GitHub里也有完整代码

登录成功,目前密码是没有加密的,登录成功,data里有 token 字符串,前端可以将 token

存起来,比如 APP 端,没有 cookie 这种东西的话,可以存在 localStorage,然后请求的时候携带 token 到参数上即可。

目前我们后端是将 token 存在 cookie 里,所以前端非APP端,无需携带参数。

二、封装参数验证

UserArgumentResolver

package com.liuyanzhao.sens.config; import com.liuyanzhao.sens.entity.User; import com.liuyanzhao.sens.exception.GlobalException; import com.liuyanzhao.sens.result.CodeMsg; import com.liuyanzhao.sens.service.UserService; import com.liuyanzhao.sens.service.impl.UserServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.MethodParameter; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 用户参数验证,验证是否有token */ @Service public class UserArgumentResolver implements HandlerMethodArgumentResolver { @Autowired private UserService userService; @Override public boolean supportsParameter(MethodParameter parameter) { Class clazz = parameter.getParameterType(); return clazz == User.class; } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class); String paramToken = request.getParameter(UserServiceImpl.COOKIE_NAME_TOKEN); String cookieToken = getCookieValue(request, UserServiceImpl.COOKIE_NAME_TOKEN); if (StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) { // return null; throw new GlobalException(CodeMsg.USER_NOT_LOGIN); } String token = StringUtils.isEmpty(paramToken) ? cookieToken : paramToken; return userService.getByToken(response, token); } private String getCookieValue(HttpServletRequest request, String cookiName) { Cookie[] cookies = request.getCookies(); if (cookies == null || cookies.length


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3