两次MD5加密+Redis缓存+Token实现分布式Session登录
- 一. 两次MD5加密
- ①. 做MD5加密的目的
- ②. 做两次MD5加密的目的
- ③. 实现MD5两次加密功能
- 二. 分布式Session的实现
- ①. 产生的问题
- ②. 解决的办法
- ③. Threadlocal存储User对象
- ④. 登录功能创建token,存入到Redis和cookies中
- ⑤. 配置每次请求打到Controller层接口自动绑定User用户信息
- ⑤. 自定义配置中添加UserArgumentResolver用户参数解析器
- ⑤.访问接口测试token效果和用户信息绑定传入
一. 两次MD5加密
①. 做MD5加密的目的
做MD5加密的目的
- 如果不做任何处理,那明文密码就会在网络上进行传输,假如在传输过程中被恶意用户取得这个数据,就可以得到这个密码,所以不安全
②. 做两次MD5加密的目的
做两次MD5加密的目的
- 第二次加密的目的:防止数据库被入侵,被人通过彩虹表反查出密码,所以服务端接受到密码后,也不是直接写入到数据库,而是生成一个随机盐(salt),再进行一次MD5加密后存入数据库
③. 实现MD5两次加密功能
package com.xizi.miaosha.util;
import org.apache.commons.codec.digest.DigestUtils;
/**
* @author xizizzz
* @description: 两次md5加密算法
* @date 2021-6-23下午 07:48
*/
public class EncyptionUtil {
//md5加密算法 导入commons-codec包
public static String md5(String str) {
return DigestUtils.md5Hex(str);
}
//md5加密+盐
public static String md5WithSalt(String pwd, String salt) {
//取字符串0 2 5 4上位置的字符拼接成新的字符串
String str = "" + salt.charAt(0) + salt.charAt(2) + pwd + salt.charAt(5) + salt.charAt(4);
return md5(str);
}
public static void main(String[] args) {
System.out.println(md5WithSalt("123456", "1a2b3c4d"));
System.out.println(md5WithSalt(md5WithSalt("123456", "1a2b3c4d"), "1a2b3c4d"));
}
}
二. 分布式Session的实现
①. 产生的问题
产生的问题
- 秒杀服务实际是分布式的多台服务器,这时候假如用户登录是在第一个服务器,第一个请求到了第一台服务器,但是第二个请求到了第二个服务器,那么用户的session信息就丢失了。
②. 解决的办法
解决的办法
- 实现思路:用户登录成功之后,给这个用户生成一个sessionId(用token来标识这个用户),并写到cookie中传递给客户端;然后客户端在随后的访问中,都在cookie中传递这个token,服务端拿到这个token之后,就根据这个token来取得对应的session信息(token利用uuid生成)
③. Threadlocal存储User对象
// Threadlocal存储user不会对对象产生影响,每次进来一个请求都会产生自身的线程变量来存储
public class UserContext {
//ThreadLocal用来存取用户信息 一个线程保存一个用户信息
private static ThreadLocal<User> userHolder = new ThreadLocal<User>();
//获取
public static void setUser(User user) {
userHolder.set(user);
}
//设置
public static User getUser() {
return userHolder.get();
}
}
④. 登录功能创建token,存入到Redis和cookies中
登录功能的业务代码
// 登录实现
public ResultEnum login(LoginVO loginVO, HttpServletResponse response) {
log.info("【登录认证】用户信息:{}", loginVO.toString());
if (loginVO == null) {
// 抛出参数错误
throw new CustomException(ResultEnum.PARAM_ERROR);
}
String mobile = loginVO.getMobile();
//TKMybatis内部方法实现 直接调用
User user = userMapper.selectByPrimaryKey(mobile);
if (user == null) {
// 抛出 用户不存在
throw new CustomException(ResultEnum.USER_NOT_EXIST);
}
//获取盐值
String salt = user.getSalt();
//调用工具类进行md5两次加密 进行密码比较
String loginPwd = EncyptionUtil.md5WithSalt(loginVO.getPassword(), salt);
if (!loginPwd.equals(user.getPassword())) {
//抛出 密码错误
throw new CustomException(ResultEnum.PASSWORD_ERROR);
}
//将用户设置到ThreadLocalh中
UserContext.setUser(user);
// 登录成功,调用UUID工具类生成token
String token = UUIDUtil.getUUID();
//将token 存入到cookie中 user用户信息和token存入redis中
addUpdateSession(response, token, user);
//返回 登录成功枚举类属性
return ResultEnum.LOGIN_SUCCESS;
}
第一次访问增加sesseion,后续访问更新session
// 第一次访问增加sesseion,后续访问更新session(更新就是重新生成session,以保持session的存活时间)
private void addUpdateSession(HttpServletResponse response, String token, User user) {
//创建一个cookie对象
Cookie cookie = new Cookie(CookieProperties.COOKIE_NAME, token);
//设置cookie的过期时间
cookie.setMaxAge(UserPrefix.getByCookie.getExpireSeconds());
cookie.setPath("/");
response.addCookie(cookie);
// 将token和user用户信息存入到redis
redisService.set(UserPrefix.getByCookie, token, user);
}
⑤. 配置每次请求打到Controller层接口自动绑定User用户信息
其功能就是解析request请求User用户参数自动绑定数据到Controller的入参上
package com.xizi.miaosha.config;
/**
* @author xizizzz
* @description: 其功能就是解析request请求参数并绑定数据到Controller的入参上
* @date 2021-6-23下午 07:48
*/
// 自定义一个参数解析器需要实现HandlerMethodArgumentResolver接口,
// 重写supportsParameter和resolveArgument方法,配置文件中加入resolver配置。
@Component
public class UserArgumentResolver implements HandlerMethodArgumentResolver {
@Autowired
private UserService userService;
//返回User对象类型
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
Class<?> clazz = methodParameter.getParameterType();
return (clazz == User.class);
}
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
//返回ThreadLocal中存的的用户
return UserContext.getUser();
}
}