前言
今天帶來的其實也沒啥,就是簡簡單單的校驗,去校驗token,然后就好了,但是區別是啥呢,咱們這邊有個大冤種就是這個 GateWay。此外這邊的全部代碼都是在WhiteHolev0.7里面的,可見的。
由于這個玩意,咱們不好再像以前直接去在攔截器里面去搞事情。而且說實話,請求那么多,如果全部都在GateWay去做的話,我是真的懶得去寫那些啥配置了,到時候放行哪些接口都會搞亂。
所以問題背景就是在分布式微服務的場景下,如何去更好地校驗token。并且通過我們的token我們可以做到單點登錄。
那么這個時候我們就不得不提到我們上篇博文提到的內容了:
當然重點是登錄模塊。
基于 Spring Boot + MyBatis Plus + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能
- 項目地址:https://github.com/YunaiV/ruoyi-vue-pro
- 視頻教程:https://doc.iocoder.cn/video/
token存儲
既然我們要校驗,那么我們要做的就是拿到這個token,那么首先要做的就是生成token,然后存儲token,咱們上一篇博文已經說的很清楚了,甚至還給出了對應的工具類。我們的流程是這樣的:
那么在這里的話,和先前不一樣的是,由于咱們的這個其實是一個多端的,所以的話咱們不僅僅有PC端還有移動端(當然移動端的作者也是我這個大冤種)所以token的話也是要做到多端的。那么這樣的話,我們就要對上次做一點改動。
這里的話,和上次不一樣的地方有兩個。
Token 存儲實體
這里新建了一個token的實體,用來存儲到redis里面。
@Data
@AllArgsConstructor
@NoArgsConstructor
publicclassLoginToken{
//這個是我們的存儲Redis里面的Token
privateStringPcLoginToken;
privateStringMobileLoginToken;
privateStringLoginIP;
}
login 業務代碼
之后就是我們修改后的代碼了。這個也就是和先前做了一點改動,主要是做多端的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())){
//登錄成功,簽發token,按照平臺類型去簽發不同的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());
}
}
}
枚舉類修改
同樣的這里和先前的枚舉類有一點不一樣,主要是多了一點東西。
publicenumBizCodeEnum{
UNKNOW_EXCEPTION(10000,"系統未知異常"),
VAILD_EXCEPTION(10001,"參數格式校驗失敗"),
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,"請求異常!觸發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;
}
}
當然同樣的,多的東西還有幾個異常類,這個其實就是繼承了Exception。
/**
*校驗用戶登錄時,參數不對的情況,此時可能是惡意爬蟲
**/
publicclassBadLoginParamsExceptionextendsException{
publicBadLoginParamsException(){}
publicBadLoginParamsException(Stringmessage){
super(message);
}
}
publicclassBadLoginTokenExceptionextendsException{
publicBadLoginTokenException(){}
publicBadLoginTokenException(Stringmessage){
super(message);
}
}
publicclassNotLoginExceptionextendsException{
publicNotLoginException(){}
publicNotLoginException(Stringmessage){
super(message);
}
}
其他的倒還是和先前的保持一致。
存儲效果
那么到此我們在登錄部分完成了對token的存儲,但是這個是在服務端,現在這個玩意已經存到了咱們的redis里面:
客戶端存儲
現在我們服務端已經存儲好了,那么接下來就是要在客戶端進行存儲。這個也好辦,我們直接來看到完整的用戶登錄代碼就知道了。
<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',//隨機串內容
identifyCode:'',
//校驗
rules:{
username:
[
{required:true,message:"請輸入用戶名",trigger:"blur"}
],
password:[
{required:true,message:"請輸入密碼(區分大小寫)",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('請填寫正確驗證碼')
this.refreshCode()
}
else{
//這邊后面做一個提交,服務器驗證,通過之后獲得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("請將對應信息填寫完整!")
}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
做了一點優化:
這個代碼是在main.js直接搞的。
Storage.prototype.setExpire=(key,value,expire)=>{
letobj={
data:value,
time:Date.now(),
expire:expire
};
localStorage.setItem(key,JSON.stringify(obj));
}
//Storage優化
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 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能
token驗證
前面咱們說完了這個存儲,那么現在的話咱們就是驗證服務了。首先我們來看到什么地方需要驗證。
我們拿這個為例子:
主頁的話,都是get請求,沒啥技術含量,不過我不介意再水一篇博客~。那么就是咱們這個頁面需要。
那么在這里的話我先說一下執行流程,這樣的話咱們完整的案例就起來了:
前端提交
那么現在咱們來看看前端的代碼: