JWT实现跨域请求
cookie的弊端
最近在做一个web项目,我想实现一个功能:客户端浏览器每次访问首页的时候,就从请求中获取cookie,如果cookie中含有用户信息,就自动为用户登录,否则就不登陆。 但是我的前端是基于vue实现的,起在8080端口,服务端起在9090端口,众所周知cookie是不可以跨域的,满足不同协议,不同域名,不同端口 即为跨域,虽然前后端都部署在localhost,但是不同端口造成了跨域。
虽然我们可以采取cors策略 等办法实现跨域,但是相对来说增添了麻烦。
使用JWT实现跨域
我采取了网上的很多办法,依然不能让cookie实现跨域,于是就想到了使用JWT+localStorage 的办法。其实JWT是可以放在cookie里面的,但是在本文的场景中cookie不能跨域。
大致思路是这样的:
- 用户登录成功以后,服务端把用户的手机号码生成一个Token令牌,然后返回给客户端浏览器。
- 客户端浏览器在收到服务端的response以后,把Token令牌储存到localStorage中,但是localStorage默认是没有过期时间的,也就是永久保存,所以我们还需要做一层封装,维护一个过期时间。
- 下一次用户访问首页的时候,如果储存的token没有过期,就把token令牌储存在request中。
- 服务端获取到request中的token令牌,如果正确,就自动为用户登录
案例
在这里我做了一个简单的小demo来演示大致的流程,用了gin、vue以及一个go的jwt库:Gin中使用JWT。
客户端
首先在前端创建一个storage.js 文件来对localStorage做一次封装:
<template>
<div class="form">
<input type="text" v-model="phoneNumber">
<input type="submit" value="提交" @click="submit">
<br>
<br>
<input type="submit" value="第二次提交" @click="submit2">
</div>
</template>
<script>
import storage from '../../assets/storage'
export default {
data() {
return {
phoneNumber: null,
}
},
methods: {
submit() {
var forms = new FormData();
forms.append("phoneNumber", this.phoneNumber);
this.$axios.post("http://127.0.0.1:9092/token", forms).then((res) => {
storage.set("access_token", res.data.token)
console.log("后端发送来的token储存到本地:");
console.log(res.data.token);
})
},
submit2() {
var forms = new FormData();
forms.append("Authorization", 'Bearer ' + storage.get("access_token"))
console.log("从浏览器缓存取出token发送:" + forms.get("Authorization"));
this.$axios.post("http://127.0.0.1:9092/token2", forms).then((res) => {
console.log("打印后端的数据:");
console.log(res.data.message);
})
},
}
}
</script>
服务端
首先创建一个jwt的model在token.go 文件中,我们在里面定义了jwt结构体、生成token令牌的方法以及解析token令牌的方法:
package models
import (
"errors"
"github.com/dgrijalva/jwt-go"
"time"
)
type MyClaims struct {
PhoneNumber string `json:"phoneNumber"`
jwt.StandardClaims
}
const TokenExpireDuration = time.Hour * 720
var mySecret = []byte("123456")
func GenToken(phoneNumber string) (string, error) {
c := MyClaims{
phoneNumber,
jwt.StandardClaims {
ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(),
Issuer: "goShop",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
tmp, err := token.SignedString(mySecret)
return tmp, err
}
func ParseToken(tokenString string) (*MyClaims, error) {
token, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
return mySecret, nil
})
if err != nil {
return nil, err
}
if Claims, ok := token.Claims.(*MyClaims); ok && token.Valid {
return Claims, nil
}
return nil, errors.New("invalid token")
}
最后我们在入口函数中定义接受客户端两次请求的路由:
package tests
import (
"fmt"
"github.com/gin-gonic/gin"
"go-shop/middleware"
"go-shop/models"
"net/http"
"testing"
)
func authHandler(c *gin.Context) {
phoneNumber := c.PostForm("phoneNumber")
tokenString, _ := models.GenToken(phoneNumber)
fmt.Println("生成token:", tokenString)
c.JSON(http.StatusOK, gin.H{
"msg" : "success",
"token" : tokenString,
})
}
func authHandler2(c *gin.Context) {
fmt.Println("token认证成功!")
c.JSON(http.StatusOK, gin.H{
"message" : "success",
})
}
func TestJWT(t *testing.T) {
r := gin.Default()
r.Use(middleware.Cors())
r.POST("/token", authHandler)
r.POST("/token2", middleware.JWTAuthMiddleware(),authHandler2)
r.Run(":9092")
}
在表单中输入我的手机号码,然后点击提交,服务端会生成一段token令牌发送回来: 然后点击第二次提交按钮,客户端会把令牌从localStorage中提取出来,加在request中发送给服务端,服务端验证成功以后,发送反馈消息:
参考资料:
vue-localStorage 缓存过期时间
vue保存后端发来的token+vue向后端发送网络请求携带token
|