实现流程
发送短信验证码,并将验证码保存在redis。
根据用户登录数据完成登录。
- 如果是新用户还要创建用户。
- 如果查询到则转化为dto保存。
将用户数据被保存到redis,并将token保存到redis,同时返回给前端。
创建登录校验拦截器。
注册登录校验拦截器。
发送验证码接口
请求参数:手机号phone
无结果对象
service层实现
@override
public result sendcode(string phone) {
//1.校验手机号
if (regexutils.isphoneinvalid(phone)) {
return result.fail("手机号格式错误!");
}
//2.生成验证码
string code = randomutil.randomnumbers(6);
//3.将验证码保存在redis
stringredistemplate.opsforvalue().set(login_code_key + phone, code, login_code_ttl, timeunit.minutes);
//4.模拟发送验证码
log.debug("发送短信验证码成功,验证码:{}", code);
return result.ok();
}
接口介绍
- 根据正则表达式检验手机号格式。
- 通过hutool生成6位随机数作为验证码。
- 将验证码保存在redis中。
- 将验证码发送给客户端(此处应使用相关云服务完成,笔者使用日志只为记录数据)。
登录接口
请求参数:已经封装好的loginformdto对象
结果对象:token
service层实现
@override
public result login(loginformdto loginform) {
//1.校验手机号格式
string phone = loginform.getphone();
if (regexutils.isphoneinvalid(phone)) {
return result.fail("手机号格式错误!");
}
//2.校验验证码
string cachecode = stringredistemplate.opsforvalue().get(login_code_key + phone);
string code = loginform.getcode();
if (cachecode == null || !cachecode.equals(code)) {
return result.fail("验证码错误!");
}
//3.根据手机号查询用户
user user = query().eq("phone", phone).one();
//4.判断用户是否存在
if (user == null) {
//5.没有用户则创建新用户
user = createuserwithphone(phone);
}
//6.保存用户信息到redis,(以哈希储存)
//1.通过uuid拼装tokenkey
string token = uuid.randomuuid().tostring(true);
string tokenkey = login_user_key + token;
//2.将userdto对象转为hashmap存储, 由于使用的是stringredistemplate,要保证所有的值都是字符串类型,要把lang转换为string
userdto userdto = beanutil.copyproperties(user, userdto.class);
map<string, object> usermap = beanutil.beantomap(userdto, new hashmap<>(),
copyoptions
.create()//创建默认数据拷贝选项
.setignorenullvalue(true) //忽略null值
/*字段值修改器,接收实现修改的方法*/
.setfieldvalueeditor(
(fieldname, fieldvalue) -> fieldvalue.tostring()
)
);
stringredistemplate.opsforhash().putall(tokenkey, usermap);
//3.设置token的有效期
stringredistemplate.expire(tokenkey, login_user_ttl, timeunit.seconds);
//7.返回token
return result.ok(tokenkey);
}
接口介绍
- 校验手机号格式。
- 校验验证码。
- 根据手机号查询用户信息(使用mybatisplus)。
- 通过uuid生成随机token,所为key,将查询后得到的userdto(将user转换为userdto,避免暴漏敏感数据),封装为hash(使用hutool相关api),存入redis,并设置有效期。
- 返回token。
手动创建拦截器
在实现了handlerinterceptor的类中重写prehandle和aftercompletion来实现对每一次请求的处理。
public class loginininterceptor implements handlerinterceptor {
private stringredistemplate stringredistemplate;
public loginininterceptor(stringredistemplate stringredistemplate) {
this.stringredistemplate = stringredistemplate;
}
@override
public boolean prehandle(httpservletrequest request, httpservletresponse response, object handler) throws exception {
//获取请求头中的token
string token = request.getheader("authorization");
if (token == null) {
response.setstatus(401);
return false;
}
//根据token从redis中获取用户信息
string key = login_user_key + token;
map<object, object> usermap = stringredistemplate.opsforhash().entries(key);
if (usermap.isempty()) {
response.setstatus(401);
return false;
}
//将map转换为userdto对象
userdto userdto = beanutil.fillbeanwithmap(usermap, new userdto(), false);
//保存用户信息到threadlocal
userholder.saveuser(userdto);
//刷新token有效期
stringredistemplate.expire(key, login_user_ttl, timeunit.seconds);
return true;
}
@override
public void aftercompletion(httpservletrequest request, httpservletresponse response, object handler, @nullable exception ex) throws exception {
userholder.removeuser();
}
}
用前注意:
该自定义拦截器未交给spring容器管理,不能使用@autowired或者@resource自动注入依赖,此处使用构造器注入stringredistemplate。
执行逻辑:
prehandle执行拦截校验
- getheader获取请求头中的token,并进行非空判断。
- 根据token从redis中获取usermap,再次使用hutool包的api将map集合转换为实体类。
- 使用工具类将userdto保存到threadlocal,实现跨层级数据共享。
- 刷新token有效期,提升用户体验。
aftercompletion执行清理threadlocal
请求完成后清理threadlocal中的数据,防止内存泄漏和数据污染。
注册拦截器
在含有@configuration 且实现了webmvcconfigurer 的配置类中添加自定义拦截器
@configuration
public class mvcconfig implements webmvcconfigurer {
@resource
private stringredistemplate stringredistemplate;
@override
public void addinterceptors(interceptorregistry registry) {
registry.addinterceptor(new loginininterceptor(stringredistemplate))
.excludepathpatterns(
"/user/code",
"/user/login",
"/blog/hot",
"/shop/**",
"/shop-type/**",
"/voucher/**",
"/upload/**"
);
}
}
执行逻辑:
- 使用@resource注解注入stringredistemplate
- 重写addinterceptors方法
- 调用registry的addinterceptor方法添加拦截器(形参中new出loginininterceptor,并将正确注入的stringredistemplate传递给loginininterceptor,完成自定义拦截器中的依赖注入)。
- 调用excludepathpatterns指明不拦截的请求
拦截器的优化
由于将校验登录状态和刷新token有效期合并在了一个拦截器中,所以在用户访问一些不拦截登录的接口时无法刷新token有效期,为此,我们将它拆分为两个拦截器
刷新拦截器:
只为刷新token有效期
- 如果无token直接放行让用户进行登录
- 如果有token则将用户保存在threadloacl(便于拦截登录)并刷新token有效期
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 {
//获取请求头中的token
string token = request.getheader("authorization");
if (token == null) {
return true;
}
//根据token从redis中获取用户信息
string key = token;
map<object, object> usermap = stringredistemplate.opsforhash().entries(key);
if (usermap.isempty()) {
return true;
}
//将map转换为userdto对象
userdto userdto = beanutil.fillbeanwithmap(usermap, new userdto(), false);
//保存用户信息到threadlocal
userholder.saveuser(userdto);
//刷新token有效期
stringredistemplate.expire(key, login_user_ttl, timeunit.seconds);
return true;
}
@override
public void aftercompletion(httpservletrequest request, httpservletresponse response, object handler, @nullable exception ex) throws exception {
userholder.removeuser();
}
}
登录拦截器:
只为拦截未登录用户
public class loginininterceptor implements handlerinterceptor {
@override
public boolean prehandle(httpservletrequest request, httpservletresponse response, object handler) throws exception {
//判断是否需要拦截(threadlocal中是否有用户)
if (userholder.getuser() == null) {
//无用户,进行拦截
response.setstatus(401);
return false;
}
//有用户,放行
return true;
}
@override
public void aftercompletion(httpservletrequest request, httpservletresponse response, object handler, @nullable exception ex) throws exception {
userholder.removeuser();
}
}
配置拦截顺序
@configuration
public class mvcconfig implements webmvcconfigurer {
@resource
private stringredistemplate stringredistemplate;
@override
public void addinterceptors(interceptorregistry registry) {
registry.addinterceptor(new loginininterceptor())
.excludepathpatterns(
"/user/code",
"/user/login",
"/blog/hot",
"/shop/**",
"/shop-type/**",
"/voucher/**",
"/upload/**"
).order(1);
registry.addinterceptor(new refreshtokeninterceptor(stringredistemplate)).order(0);
}
}
order值低的优先进行拦截
到此这篇关于springboot基于redis实现登录校验的示例的文章就介绍到这了,更多相关springboot redis登录校验内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论