石家庄logo标志设计,wordpress4.9.8优化,最美logo图案大全,tiktok国际版网页入口一、隐藏敏感信息
之前我们对 /user/me 路径#xff0c;直接返回了登录的所有用户信息#xff0c;其中的 passward 等敏感信息也会被返回到前端#xff0c;这是很危险的#xff0c;故我们需要选择性的返回用户信息#xff0c;隐藏敏感用户信息
我们可以创建一个 UserDTO…一、隐藏敏感信息
之前我们对 /user/me 路径直接返回了登录的所有用户信息其中的 passward 等敏感信息也会被返回到前端这是很危险的故我们需要选择性的返回用户信息隐藏敏感用户信息
我们可以创建一个 UserDTO 类将 user 中可以返回的信息封装到其中后将 UserDTO 返回
Data
public class UserDTO {private Long id;private String nickName;private String icon;
}
然后我们将登录成功时存入 user 的操作改为存入 UserDTO ,这里使用的是 hutool 工具中 BeanUtil 来封装将之前的 user 换成 UserDTO 后再存入 session //保存用户登录信息
// session.setAttribute(user,user);session.setAttribute(user, BeanUtil.copyProperties(user, UserDTO.class));
在拦截器进行登录校验时我们会拿出这个 user ,需要将拦截器的对应代码也修改 //前置拦截Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//拿到 session 中的 userObject user request.getSession().getAttribute(user);System.out.println(user进入前置拦截器);//若用户不存在拦截if(user null){response.setStatus(401);//响应 401 状态码表示未授权return false;}//将用户保存在 ThreadLocal 中,调用 UserHolder 中的静态方法UserHolder.saveUser((UserDTO) user);//放行System.out.println(前置拦截放行);return HandlerInterceptor.super.preHandle(request, response, handler);}
public class UserHolder {//user 对应的的线程池private static final ThreadLocalUserDTO tl new ThreadLocal();//往线程池中存入用户public static void saveUser(UserDTO user){tl.set(user);}//拿到当前线程的 user,一个线程只有一个 userpublic static UserDTO getUser(){return tl.get();}//移除当前线程的 userpublic static void removeUser(){tl.remove();}}
修改完毕后我们再次使用 postman 进行测试完成发验证码登录等了校验三个请求 可以看到这次返回的用户信息只含有 id,昵称头像。保护了敏感信息。
二、解决集群的 session 共享问题
使用 session 进行登录信息的存储当出现多台 tomcat 时存储的信息会出现无法共享的情况我们可以通过 Redis 来存储信息来解决
对于验证码我们可以使用手机号作为 key 验证码存入 value中
对于登录信息我们可以使用哈希结构存储不同的信息使用随机 token 生成随机且唯一的 key,在响应时将 token 返回给浏览器在之后需要用到 token 的请求需要在请求中发送 token
在完成业务之前我们先配置一下 redis 并测试是否可以正常访问
yml 配置文件
server:port: 8082spring:application:name: mydpdatasource:url: jdbc:mysql://localhost:3306/learnbaseusername: rootpassword: 1234redis:host: 127.0.0.1port: 6379lettuce:pool:max-active: 8 # 最大连接max-idle: 8 # 最大空闲连接min-idle: 0max-wait: 100 # 最大等待时间单位毫秒jackson:default-property-inclusion: non_null # JSON处理时忽略非空字段
测试 redis 是否连接正常
SpringBootTest
class MyDianpingApplicationTests {Resourceprivate StringRedisTemplate stringRedisTemplate;Testvoid redisText() {stringRedisTemplate.opsForValue().set(dataRides,check);Object dataRides stringRedisTemplate.opsForValue().get(dataRides);System.out.println(dataRides);}}
若 redis 中写入了键值对 dataRides , check 则可以认为连接时畅通的
接下来我们便可以通过 Redis 来优化登录业务了
发送验证码
首先在成员变量位置注入 StringRedisTemplate Resourceprivate StringRedisTemplate stringRedisTemplate;
Controller 层和 UserService 接口无需改动只需修改 UserServiceImpl 的 sendCode 方法即可 Overridepublic Result sendCode(String phone, HttpSession session) {//先用 hutool 工具校验手机号是否合法if (phone null || !PhoneUtil.isPhone(phone)) {return Result.fail(请输入合法的手机号);//若不合法直接响应错误}//用 hutool 工具生成六位验证码String code RandomUtil.randomNumbers(6);//将生成的验证码放入 session//TODO 优化保存验证码到redis//TODO 后面两个参数时验证码的有效期2分钟设置后 redis 会在底层加上 set key value ex 120stringRedisTemplate.opsForValue().set(login:code:phone,code,2, TimeUnit.MINUTES);//给手机号发送验证码这里模拟发送验证码的操作而不是真正的发送System.out.println(手机收到了一条验证码短信code);return Result.ok();}
登录逻辑
从 redis 中获取验证码并验证验证通过后存入用户信息到 redis
生成 token 作为唯一的标识符将用户信息封装为哈希表将其挂在 token 下后存入 redis
最后返回 token ,在之后每次需要用到用户信息的请求中我们都将 token 作为请求头发送请求 Overridepublic Result login(LoginFormDTO loginFormDTO, HttpSession session) {if(loginFormDTO null){return Result.fail(无效操作);}//校验手机号String phone loginFormDTO.getPhone();if (phone null || !PhoneUtil.isPhone(phone)) {return Result.fail(请输入合法的手机号);//若不合法直接响应错误}//拿到 session 域中的验证码//TODO 优化从 redis 中获取验证码String code1 stringRedisTemplate.opsForValue().get(login:code:phone);String code loginFormDTO.getCode();if(!code.equals(code1)){return Result.fail(验证码输入错误请重新输入);}//验证码正确判断是否存在用户User user userMapper.selectByPhone(phone);if(user null){//若不存在就创建一个用户并存入 mysqluser createUserByPhone(phone);userMapper.insert(user);}//保存用户登录信息//TODO 优化生成 token ,使用哈希的方式保存用户信息//使用 hutool 的 UUID 来生成 tokenString token UUID.randomUUID().toString();UserDTO userDTO BeanUtil.copyProperties(user,UserDTO.class);//使用 stringRedisTemplate使用 hash 的方式存储存信息时必须保证所有 key value 都是 String 类型MapString,String userMap new HashMap();userMap.put(id,Long.toString(userDTO.getId()));userMap.put(nickName,userDTO.getNickName());userMap.put(icon,userDTO.getIcon());stringRedisTemplate.opsForHash().putAll(login:token:token,userMap);//设置 token 有效日期此处设定初始有效日期可以通过通用拦截器更新有效日期stringRedisTemplate.expire(login:token:token,30,TimeUnit.MINUTES);//TODO 优化返回生成的 tokenreturn Result.ok(token);} 拦截器更新 token 有效期登录验证
为了做到当用户完成任何操作后token 的有效期更新以保证用户的使用体验我们可以设置一个全局拦截器将 token 的更新操作写在全局拦截器中并在全局拦截器中将用户信息存入线程池中
再使用第二级拦截器判断用户是否登录登录的用户一定会存在于线程池我们可以通过此来判断用户是否登录
一级拦截
public class RefreshTokenInterceptor implements HandlerInterceptor {//这个类没有被 Spring 管理我们可以使用其配置类 MvcConfig 拿到后通过有参构造传递进来private StringRedisTemplate stringRedisTemplate;public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate){this.stringRedisTemplate stringRedisTemplate;}//前置拦截Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//获取请求头中的 tokenString token request.getHeader(authorization);if(StrUtil.isBlank(token)){//token 不存在直接放行return true;}//基于 token 取用户信息MapObject, Object userMap stringRedisTemplate.opsForHash().entries(login:token:token);if(userMap.isEmpty()){//没有信息直接放行return true;}//有用户信息将 userMap 转为 UserDTO 后保存到 ThreadLocal 中UserDTO userDTO BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);//最后一个参数表示是否忽略转换过程中的错误UserHolder.saveUser(userDTO);//更新 token 的失效时间stringRedisTemplate.expire(login:token:token,30, TimeUnit.MINUTES);//放行return true;}//渲染后拦截Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//在渲染后将 user 从线程中移除UserHolder.removeUser();}
}
二级拦截
public class LoginInterceptor implements HandlerInterceptor {//前置拦截Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//判断线程池中是否有登录用户,若没有则拦截返回 401 状态码if(UserHolder.getUser() null){response.setStatus(401);return false;}return true;}}
测试