项目介绍
导入黑马点评项目
项目架构
基于Session实现登录
基本流程
实现发送短信验证码功能
controller层中
/**
* 发送手机验证码
*/
@PostMapping("code")
public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
// 发送短信验证码并保存验证码
return userService.sendCode(phone,session);
}
Service层中
真的发送的话要接入阿里云或腾讯云的短信发送功能,这里假装发送成功。
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public Result sendCode(String phone, HttpSession session) {
//1.校验手机号
if(RegexUtils.isPhoneInvalid(phone)){
//2.如果不符合,返回错误信息
return Result.fail("手机号格式错误!");
}
//3.符合,生成验证码
String code = RandomUtil.randomNumbers(6);
//4.保存验证码
session.setAttribute("code",code);
//5.发送验证码
log.debug("发送短信验证码成功,验证码:{}",code);
return Result.ok();
}
}
实现验证码登录和注册功能
Controller层中
/**
* 登录功能
* @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码
*/
@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
// 实现登录功能
return userService.login(loginForm,session);
}
Service层中
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
//1.验证手机号
String phone=loginForm.getPhone();
if(RegexUtils.isPhoneInvalid(phone)){
//1.1不符合,返回报错
return Result.fail("手机号格式错误!");
}
//2.校验验证码
Object cacheCode = session.getAttribute("code");
String code = loginForm.getCode();
if(cacheCode==null||!cacheCode.toString().equals(code)){
//3.不一致,报错
return Result.fail("验证码错误");
}
//4.一致,根据手机号查询用户 select * from tb_user where phone=?
User user = query().eq("phone", phone).one();
//5.判断用户是否存在
if(user==null){
//6.不存在,创建新用户并保存
user= createUserWithPhone(phone);
}
//7.存在,保存用户信息到session中
session.setAttribute("user",user);
return Result.ok();
}
private User createUserWithPhone(String phone) {
//1.创建用户
User user=new User();
user.setPhone(phone);
user.setNickName(USER_NICK_NAME_PREFIX+RandomUtil.randomString(10));
//2.保存
save(user);
return user;
}
这里的session之所以能够获取到发送验证码请求时存入的验证码是因为这是在同一个会话当中,session会被返回给前端,下一次请求也会携带同一个session过来。所以才有之前保存的信息在里面。
实现登录校验拦截器
接口多了之后就要拦截器进行统一校验,然后为了传递用户信息给后序的业务,需要将用户信息存入Threadlocal里面的.Threadlocal是每个请求线程的一个独立保存空间。
在拦截器的最后一个方法中,清空thread local的信息,第一可以做到退出登录,第二可以防止内存泄露
新建一个拦截器
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.获取session
HttpSession session = request.getSession();
//2.获取session中的用户
Object user = session.getAttribute("user");
//3.判断用户是否存在
if(user==null){
//4.不存在,拦截
response.setStatus(401);
return false;
}
//5.存在,保存用户信息到ThreadLocal
UserHolder.saveUser((UserDTO) user);
//6.放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//移除用户
UserHolder.removeUser();
}
}
新建配置项
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.excludePathPatterns(
"/shop/**",
"/shop-type/**",
"/upload/**",
"/blog/hot",
"/user/code" ,
"/user/login");
}
}
登录校验接口
@GetMapping("/me")
public Result me(){
// 获取当前登录的用户并返回
UserDTO user= UserHolder.getUser();
return Result.ok(user);
}
隐藏用户敏感信息
在前面代码里返回给前端的是用户完整信息,这是不合理的,所以改成如下。
在session存的时候就只存部分信息。然后上面登录校验逻辑因为黑马资料的问题已经是隐藏之后的了的,所以只改这里一个地方即可。
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
....
....
//7.存在,保存用户信息到session中
session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
return Result.ok();
}
集群的session共享问题
基于Redis实现共享session登录
业务流程
基于redis实现短信登录
发送短信验证码部分的代码修改:
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public Result sendCode(String phone, HttpSession session) {
//1.校验手机号
...
//3.符合,生成验证码
...
//4.保存验证码到Redis
stringRedisTemplate.opsForValue().set("login:code:"+phone,code,2, TimeUnit.MINUTES);
//5.发送验证码
...
}
短信验证码登录,注册功能部分的代码修改:
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
//1.验证手机号
...
// 2.从redis获取 校验验证码
String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY+phone);
...
//4.一致,根据手机号查询用户 select * from tb_user where phone=?
...
//5.判断用户是否存在
...
//7.存在,保存用户信息到Redis中
//7.1.随机生成token,作为登录令牌
String token = UUID.randomUUID().toString(true);
//7.2将User对象转为Hash存储
UserDTO userDTO=BeanUtil.copyProperties(user, UserDTO.class);
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO);
//7.3存储
String tokenKey="login::token:"+token;
stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);
//7.4设置token有效期
stringRedisTemplate.expire(tokenKey,30,TimeUnit.MINUTES);
//8.返回token
return Result.ok(token);
}
登录拦截器代码修改
public class LoginInterceptor implements HandlerInterceptor {
private StringRedisTemplate stringRedisTemplate;
public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate=stringRedisTemplate;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.获取请求头中的token
String token = request.getHeader("authorization");
if (StrUtil.isBlank(token)) {
//4.不存在,拦截
response.setStatus(401);
return false;
}
//2.基于token获取redis中的用户
String tokenKey=RedisConstants.LOGIN_USER_KEY + token;
Map<Object, Object> userMap = stringRedisTemplate.opsForHash()
.entries(tokenKey);
//3.判断用户是否存在
if(userMap.isEmpty()){
//4.不存在,拦截
response.setStatus(401);
return false;
}
//5.将查询到的Hash数据转为UserDTO对象
UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
//6.存在,保存用户信息到ThreadLocal
UserHolder.saveUser( userDTO);
//7.刷新token有效期
stringRedisTemplate.expire(tokenKey,RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
//8.放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//移除用户
...
}
}
因为这里不能用注解注入stringRedisTemplate,要在MVC配置器哪里进行注入
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor(stringRedisTemplate))
.excludePathPatterns(
"/shop/**",
"/shop-type/**",
"/upload/**",
"/blog/hot",
"/user/code" ,
"/user/login");
}
}
出现Long转String的报错
//7.2将User对象转为Hash存储
UserDTO userDTO=BeanUtil.copyProperties(user, UserDTO.class);
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),
CopyOptions.create()
.setIgnoreNullValue(true)
.setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));
解决登录状态刷新问题
登录状态的刷新只会在访问需要拦截的请求时才刷新,如果是不需要拦截的请求就不会刷新.
新增刷新拦截器:
public class RefreshTokenInterceptor implements HandlerInterceptor {
private StringRedisTemplate stringRedisTemplate;
public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate=stringRedisTemplate;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.获取请求头中的token
String token = request.getHeader("authorization");
if (StrUtil.isBlank(token)) {
return true;
}
//2.基于token获取redis中的用户
String tokenKey=RedisConstants.LOGIN_USER_KEY + token;
Map<Object, Object> userMap = stringRedisTemplate.opsForHash()
.entries(tokenKey);
//3.判断用户是否存在
if(userMap.isEmpty()){
return true;
}
//5.将查询到的Hash数据转为UserDTO对象
UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
//6.存在,保存用户信息到ThreadLocal
UserHolder.saveUser( userDTO);
//7.刷新token有效期
stringRedisTemplate.expire(tokenKey,RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
//8.放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//移除用户
UserHolder.removeUser();
}
}
修改原本的登录拦截器
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.判断是否需要拦截(ThreadLocal中是否有用户)
if(UserHolder.getUser()==null){
//没有,需要拦截
response.setStatus(401);
return false;
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//移除用户
UserHolder.removeUser();
}
}
MVC配置类当中
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//登录拦截器
registry.addInterceptor(new LoginInterceptor())
.excludePathPatterns(
"/shop/**",
"/shop-type/**",
"/upload/**",
"/blog/hot",
"/user/code" ,
"/user/login").order(1);
//刷新拦截器
registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate))
.addPathPatterns("/**").order(0);
}
}
原文地址:https://blog.csdn.net/m0_62327332/article/details/134615558
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_33132.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。