0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

輕松搞定分布式token校驗

jf_ro2CN3Fa ? 來源:CSDN ? 2023-09-21 16:34 ? 次閱讀


前言

今天帶來的其實也沒啥,就是簡簡單單的校驗,去校驗token,然后就好了,但是區(qū)別是啥呢,咱們這邊有個大冤種就是這個 GateWay。此外這邊的全部代碼都是在WhiteHolev0.7里面的,可見的。

0e888dba-581f-11ee-939d-92fbcf53809c.png

由于這個玩意,咱們不好再像以前直接去在攔截器里面去搞事情。而且說實話,請求那么多,如果全部都在GateWay去做的話,我是真的懶得去寫那些啥配置了,到時候放行哪些接口都會搞亂。

所以問題背景就是在分布式微服務(wù)的場景下,如何去更好地校驗token。并且通過我們的token我們可以做到單點登錄。

那么這個時候我們就不得不提到我們上篇博文提到的內(nèi)容了:

  • https://blog.csdn.net/FUTEROX/article/details/127232757

當(dāng)然重點是登錄模塊。

基于 Spring Boot + MyBatis Plus + Vue & Element 實現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能

  • 項目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 視頻教程:https://doc.iocoder.cn/video/

token存儲

既然我們要校驗,那么我們要做的就是拿到這個token,那么首先要做的就是生成token,然后存儲token,咱們上一篇博文已經(jīng)說的很清楚了,甚至還給出了對應(yīng)的工具類。我們的流程是這樣的:

0ea272ac-581f-11ee-939d-92fbcf53809c.png

那么在這里的話,和先前不一樣的是,由于咱們的這個其實是一個多端的,所以的話咱們不僅僅有PC端還有移動端(當(dāng)然移動端的作者也是我這個大冤種)所以token的話也是要做到多端的。那么這樣的話,我們就要對上次做一點改動。

這里的話,和上次不一樣的地方有兩個。

Token 存儲實體

這里新建了一個token的實體,用來存儲到redis里面。

0ec0bb68-581f-11ee-939d-92fbcf53809c.png
@Data
@AllArgsConstructor
@NoArgsConstructor
publicclassLoginToken{
//這個是我們的存儲Redis里面的Token
privateStringPcLoginToken;
privateStringMobileLoginToken;
privateStringLoginIP;
}

login 業(yè)務(wù)代碼

之后就是我們修改后的代碼了。這個也就是和先前做了一點改動,主要是做多端的token嘛。

@Service
publicclassloginServiceImplimplementsLoginService{

@Autowired
UserServiceuserService;
@Autowired
RedisUtilsredisUtils;
//為安全期間這里也做一個20防刷
@Override
publicRLogin(LoginEntityentity){

Stringusername=entity.getUsername();
Stringpassword=entity.getPassword();
password=password.replaceAll("","");
if(redisUtils.hasKey(RedisTransKey.getLoginKey(username))){
returnR.error(BizCodeEnum.OVER_REQUESTS.getCode(),BizCodeEnum.OVER_REQUESTS.getMsg());
}
redisUtils.set(RedisTransKey.setLoginKey(username),1,20);
UserEntityUser=userService.getOne(
newQueryWrapper().eq("username",username)
);
if(User!=null){
if(SecurityUtils.matchesPassword(password,User.getPassword())){
//登錄成功,簽發(fā)token,按照平臺類型去簽發(fā)不同的Token
Stringtoken=JwtTokenUtil.generateToken(User);
//登錄成功后,將userid--->token存redis,便于做登錄驗證
StringipAddr=GetIPAddrUtils.GetIPAddr();
if(entity.getType().equals(LoginType.PcType)){
LoginTokenloginToken=newLoginToken(token,null,ipAddr);
redisUtils.set(RedisTransKey.setTokenKey(User.getUserid()+":"+LoginType.PcType)
,loginToken,7,TimeUnit.DAYS
);
returnObjects.requireNonNull(R.ok(BizCodeEnum.SUCCESSFUL.getMsg())
.put(LoginType.PcLoginToken,token))
.put("userid",User.getUserid());
}elseif(entity.getType().equals(LoginType.MobileType)){
LoginTokenloginToken=newLoginToken(null,token,ipAddr);
redisUtils.set(RedisTransKey.setTokenKey(User.getUserid()+":"+LoginType.MobileType)
,loginToken,7,TimeUnit.DAYS
);
returnObjects.requireNonNull(R.ok(BizCodeEnum.SUCCESSFUL.getMsg())
.put(LoginType.PcLoginToken,token))
.put("userid",User.getUserid());
}else{
returnR.error(BizCodeEnum.NUNKNOW_LGINTYPE.getCode(),BizCodeEnum.NUNKNOW_LGINTYPE.getMsg());
}
}else{
returnR.error(BizCodeEnum.BAD_PUTDATA.getCode(),BizCodeEnum.BAD_PUTDATA.getMsg());
}
}else{
returnR.error(BizCodeEnum.NO_SUCHUSER.getCode(),BizCodeEnum.NO_SUCHUSER.getMsg());
}
}
}
枚舉類修改

同樣的這里和先前的枚舉類有一點不一樣,主要是多了一點東西。

0ed5d7e6-581f-11ee-939d-92fbcf53809c.png
publicenumBizCodeEnum{
UNKNOW_EXCEPTION(10000,"系統(tǒng)未知異常"),
VAILD_EXCEPTION(10001,"參數(shù)格式校驗失敗"),
HAS_USERNAME(10002,"已存在該用戶"),
OVER_REQUESTS(10003,"訪問頻次過多"),
OVER_TIME(10004,"操作超時"),
BAD_DOING(10005,"疑似惡意操作"),
BAD_EMAILCODE_VERIFY(10007,"郵箱驗證碼錯誤"),
REPARATION_GO(10008,"請重新操作"),
NO_SUCHUSER(10009,"該用戶不存在"),
BAD_PUTDATA(10010,"信息提交錯誤,請重新檢查"),
NOT_LOGIN(10011,"用戶未登錄"),
BAD_LOGIN_PARAMS(10012,"請求異常!觸發(fā)5次以上賬號將保護性封禁"),
NUNKNOW_LGINTYPE(10013,"平臺識別異常"),
BAD_TOKEN(10014,"token校驗失敗"),
SUCCESSFUL(200,"successful");

privateintcode;
privateStringmsg;
BizCodeEnum(intcode,Stringmsg){
this.code=code;
this.msg=msg;
}

publicintgetCode(){
returncode;
}

publicStringgetMsg(){
returnmsg;
}
}

當(dāng)然同樣的,多的東西還有幾個異常類,這個其實就是繼承了Exception。

/**
*校驗用戶登錄時,參數(shù)不對的情況,此時可能是惡意爬蟲
**/
publicclassBadLoginParamsExceptionextendsException{
publicBadLoginParamsException(){}
publicBadLoginParamsException(Stringmessage){
super(message);
}

}
publicclassBadLoginTokenExceptionextendsException{
publicBadLoginTokenException(){}
publicBadLoginTokenException(Stringmessage){
super(message);
}
}
publicclassNotLoginExceptionextendsException{
publicNotLoginException(){}
publicNotLoginException(Stringmessage){
super(message);
}
}

其他的倒還是和先前的保持一致。

存儲效果

那么到此我們在登錄部分完成了對token的存儲,但是這個是在服務(wù)端,現(xiàn)在這個玩意已經(jīng)存到了咱們的redis里面:

0ef76cc6-581f-11ee-939d-92fbcf53809c.png

客戶端存儲

現(xiàn)在我們服務(wù)端已經(jīng)存儲好了,那么接下來就是要在客戶端進行存儲。這個也好辦,我們直接來看到完整的用戶登錄代碼就知道了。

<template>
<div>
<el-form:model="formLogin":rules="rules"ref="ruleForm"label-width="0px">
<el-form-itemprop="username">
<el-inputv-model="formLogin.username"placeholder="賬號">
<islot="prepend"class="el-icon-s-custom"/>
el-input>
el-form-item>
<el-form-itemprop="password">
<el-inputtype="password"placeholder="密碼"v-model="formLogin.password">
<islot="prepend"class="el-icon-lock"/>
el-input>
el-form-item>
<el-form-itemprop="code">
<el-row:span="24">
<el-col:span="12">
<el-inputv-model="formLogin.code"auto-complete="off"placeholder="請輸入驗證碼"size="">el-input>
el-col>
<el-col:span="12">
<divclass="login-code"@click="refreshCode">

<s-identify:identifyCode="identifyCode">s-identify>
div>
el-col>
el-row>
el-form-item>
<el-form-item>
<divclass="login-btn">
<el-buttontype="primary"@click="submitForm()"style="margin-left:auto;width:35%">登錄el-button>
<el-buttontype="primary"@click="goRegister"style="margin-left:27%;width:35%">注冊el-button>
div>
el-form-item>
el-form>
div>
template>

<script>
importSIdentifyfrom"../../components/SIdentify/SIdentify";
exportdefault{
name:"loginbyUserName",
components:{SIdentify},
data(){
return{
formLogin:{
username:"",
password:"",
code:""
},
identifyCodes:'1234567890abcdefjhijklinopqrsduvwxyz',//隨機串內(nèi)容
identifyCode:'',
//校驗
rules:{
username:
[
{required:true,message:"請輸入用戶名",trigger:"blur"}
],
password:[
{required:true,message:"請輸入密碼(區(qū)分大小寫)",trigger:"blur"}
],
code:[
{required:true,message:"請輸入驗證碼",trigger:"blur"}
]
}

}
},
mounted(){
//初始化驗證碼
this.identifyCode=''
this.makeCode(this.identifyCodes,4)
},
methods:{
refreshCode(){
this.identifyCode=''
this.makeCode(this.identifyCodes,4)
},
makeCode(o,l){
for(leti=0;ithis.identifyCode+=this.identifyCodes[this.randomNum(0,this.identifyCodes.length)]
}
},
randomNum(min,max){
returnMath.floor(Math.random()*(max-min)+min)
},

submitForm(){

if(this.formLogin.code.toLowerCase()!==this.identifyCode.toLowerCase()){
this.$message.error('請?zhí)顚懻_驗證碼')
this.refreshCode()

}
else{
//這邊后面做一個提交,服務(wù)器驗證,通過之后獲得token
this.axios({
url:"/user/user/login",
method:'post',
data:{
"username":this.formLogin.username,
"password":this.formLogin.password,
"type":"PcType",
}
}).then((res)=>{
res=res.data
if(res.code===10001){
alert("請將對應(yīng)信息填寫完整!")
}elseif(res.code===0){
alert("登錄成功")
localStorage.setExpire("LoginToken",res.PcLoginToken,this.OverTime)
localStorage.setExpire("userid",res.userid,this.OverTime)
this.$router.push({path:'/userinfo',query:{'userid':res.userid}});
}else{
alert(res.msg);
}
})
}
},
goRegister(){
this.$router.push("/register")
}
},
}
script>

<stylescoped>
style>

這里的話,咱們對localStorage做了一點優(yōu)化:

這個代碼是在main.js直接搞的。

Storage.prototype.setExpire=(key,value,expire)=>{
letobj={
data:value,
time:Date.now(),
expire:expire
};
localStorage.setItem(key,JSON.stringify(obj));
}
//Storage優(yōu)化
Storage.prototype.getExpire=key=>{
letval=localStorage.getItem(key);
if(!val){
returnval;
}
val=JSON.parse(val);
if(Date.now()-val.time>val.expire){
localStorage.removeItem(key);
returnnull;
}
returnval.data;
}

這個this.OverTime 就是一個全局變量,就是7天過期的意思。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能

  • 項目地址:https://github.com/YunaiV/yudao-cloud
  • 視頻教程:https://doc.iocoder.cn/video/

token驗證

前面咱們說完了這個存儲,那么現(xiàn)在的話咱們就是驗證服務(wù)了。首先我們來看到什么地方需要驗證。

我們拿這個為例子:

0f08e3a2-581f-11ee-939d-92fbcf53809c.png

主頁的話,都是get請求,沒啥技術(shù)含量,不過我不介意再水一篇博客~。那么就是咱們這個頁面需要。

那么在這里的話我先說一下執(zhí)行流程,這樣的話咱們完整的案例就起來了:

0f2db538-581f-11ee-939d-92fbcf53809c.png

前端提交

那么現(xiàn)在咱們來看看前端的代碼: