当前位置:   article > 正文

vue3+golang-jwt前后端分离实现token验证_bearer token生成算法golang

bearer token生成算法golang

        今天练习token登录认证,发现网上查的资料大多都看不懂,然后自己琢磨了半天勉强实现了这个功能,记录一下,博主是小白,大伙勿喷……

        首先我们的前端发送一个http请求给后端,一般登录验证为POST请求,这里使用fetch方法来发起请求,具体fetch的用法可以自行上网查,原理跟axios相差不大:

  1. const handleLogin = () => {
  2. let url='api/user/login';
  3. fetch(url,{
  4. method: 'POST',
  5. mode: 'cors',
  6. cache: 'no-cache',
  7. credentials: 'same-origin',
  8. headers: new Headers({
  9. 'Content-Type': 'application/json',
  10. }),
  11. redirect: 'follow',
  12. body: JSON.stringify(formData)
  13. }).then((v)=>{
  14. return v.json(); //用json序列化函数处理响应的数据
  15. }).then((v)=>{
  16. //如果该响应的结果为空
  17. if(!v){
  18. message.error("null respone!")
  19. }
  20. if(v.status===2005){
  21. message.error(v.msg);
  22. return
  23. }else if(v.status===200||v.status===201){
  24. message.success("进入聊天室成功")
  25. // console.log(v.data.token)
  26. localStorage.setItem("token",v.data.token);
  27. setTimeout(() => {
  28. routers.push('/chat')
  29. }, 1500);
  30. }
  31. }).catch((err) => {
  32. // console.log(err);
  33. message.error(err);
  34. });
  35. };

PS:上面的body中是携带了username和password,最后使用JSON.stringify()函数转化为json字符串传给后端

当请求成功后,后端会返回一个token,我们需要将token通过localStorage对象存储在本地浏览器中

localStorage.setItem("token",v.data.token);

             

 此时前端就拿到了token,之后便可以使用这个token去完成相应的路由守卫和请求拦截了。

 不过我们先来看看后端是如何生成token的:

首先这里直接先给出整个Login函数的处理:

  1. //用户登录
  2. func Login(w http.ResponseWriter, r *http.Request) {
  3. var err error
  4. //msg用于响应客户端,,msg.data里存放着用户信息
  5. msg := define.ReplyProto{
  6. Status: 200,
  7. Msg: "success",
  8. }
  9. //如果不是请求方法不是post
  10. if strings.ToLower(r.Method) != "post" {
  11. msg.Status = -400
  12. msg.Msg = "invalid request,should be post"
  13. respone.Resp(w, &msg)
  14. return
  15. }
  16. //buf接收请求发送过来的用户信息
  17. buf, err := ioutil.ReadAll(r.Body)
  18. if err != nil {
  19. msg.Status = -403
  20. msg.Msg = err.Error()
  21. return
  22. }
  23. //如果请求的参数为空
  24. if buf == nil {
  25. msg.Status = -500
  26. msg.Msg = "invalid/nil request param"
  27. return
  28. }
  29. jsonMap := make(map[string]interface{})
  30. err = json.Unmarshal(buf, &jsonMap)
  31. if err != nil {
  32. fmt.Println("1:" + err.Error())
  33. }
  34. //获取用户名和密码
  35. username := jsonMap["username"]
  36. password := jsonMap["password"]
  37. //如果用户名或密码为空
  38. if username == "" || password == "" {
  39. msg.Status = -500
  40. msg.Msg = "invalid/empty user/password"
  41. respone.Resp(w, &msg)
  42. return
  43. }
  44. //连接数据库比对用户名和密码
  45. s := `select id,username from t_user where username = $1 and password=crypt($2,password) `
  46. var userID int
  47. result := dao.DB.QueryRow(context.Background(), s, username, password)
  48. err = result.Scan(&userID, &username)
  49. nonexistent := err == pgx.ErrNoRows
  50. //密码错误或者用户不存在
  51. if nonexistent {
  52. msg.Status = 2005
  53. msg.Msg = "用户名/密码错误,请重新登录!"
  54. respone.Resp(w, &msg)
  55. return
  56. }
  57. //创建token
  58. token, err := createToken(userID, username)
  59. if err != nil {
  60. msg.Status = 501
  61. msg.Msg = "生成token失败!"
  62. respone.Resp(w, &msg)
  63. return
  64. }
  65. msg.Data = []byte(fmt.Sprintf(`
  66. {"id":%d,"username":"%s","token":"%s"}`, userID, username, token))
  67. //响应客户端
  68. respone.Resp(w, &msg)
  69. return
  70. }

具体的封装函数就不细追究,我们只需要关注一下如何生成token即可,登录成功后我们可以得到username和userid(password出于安全,我们一般是不操作这个数据的),这里的userid是用户注册时会生成的,我这里只要在查询数据库比对密码的时候返回一下userid和username即可(具体看每个人的思路是如何的),而这两个数据是可以拿来生成token的,此时我们就可以引进jwt包

import "github.com/dgrijalva/jwt-go"

封装一个CreateToken的函数,参数为username和userid来生成token

  1. //自定义令牌
  2. var mySigningKey = []byte("Key of Chery")
  3. //创建token
  4. func createToken(userid int, username interface{}) (s string, err error) {
  5. // Create the Claims
  6. claims := MyClaim{
  7. Username: username,
  8. Id: userid,
  9. StandardClaims: jwt.StandardClaims{
  10. NotBefore: time.Now().Unix() - 60, //生效时间,这里是一分钟前生效
  11. ExpiresAt: time.Now().Unix() + 60*60, //过期时间,这里是一小时过期
  12. Issuer: "chery", //签发人
  13. },
  14. }
  15. //SigningMethodHS256,HS256对称加密方式
  16. token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
  17. //通过自定义令牌加密
  18. ss, err := token.SignedString(mySigningKey)
  19. if err != nil {
  20. fmt.Println("生成token失败")
  21. }
  22. return ss, err
  23. }

查看官方文档,我们可以使用jwt.NewWithClaims()函数来生成token,参数中claims是一个结构体(因此我们可以自定义自己的claims结构体,我上面定义的是MyClaims),而结构体里的jwt.StandardClaims{}类型则是定义token的规则,如过期时间,签发人,生效时间等……(因为token存在被盗的风险,因此建议有效时间设置短一些)

源码中给出的结构:

  1. type StandardClaims struct {
  2. Audience string `json:"aud,omitempty"`
  3. ExpiresAt int64 `json:"exp,omitempty"`
  4. Id string `json:"jti,omitempty"`
  5. IssuedAt int64 `json:"iat,omitempty"`
  6. Issuer string `json:"iss,omitempty"`
  7. NotBefore int64 `json:"nbf,omitempty"`
  8. Subject string `json:"sub,omitempty"`
  9. }

 这里解释各个字段的意义:

  • 发行人:iss
  • 到期时间:exp
  • 主题:sub
  • 受众:aud
  • 编号:jti
  • 生效时间:nbf
  • 有效时间:exp
  • 签发时间:iat

第一个参数是指定一个加密方法,最后这个函数会返回一个Token的结构体,

  1. func NewWithClaims(method SigningMethod, claims Claims) *Token {
  2. return &Token{
  3. Header: map[string]interface{}{
  4. "typ": "JWT",
  5. "alg": method.Alg(),
  6. },
  7. Claims: claims,
  8. Method: method,
  9. }
  10. }

最后通过SignedString()函数使用我们自己定义的唯一令牌去生成token字符串,这个token就可以返回给前端了

  1. func (t *Token) SignedString(key interface{}) (string, error) {
  2. var sig, sstr string
  3. var err error
  4. if sstr, err = t.SigningString(); err != nil {
  5. return "", err
  6. }
  7. if sig, err = t.Method.Sign(sstr, key); err != nil {
  8. return "", err
  9. }
  10. return strings.Join([]string{sstr, sig}, "."), nil
  11. }

核心代码就只有几行罢了

        到这里我们的token是如何生成的就搞清楚了,那么接下来回到前端,我们需要设置一下路由守卫,和如何将每个请求都带上token:

        这里就简单的判断本地浏览器有无token,如果没有就说明一定没有登录,跳回到登录页。如果token存在则放行。但是这样做还不够,试想一下,如果有人知道我们的api接口和参数,是不是就可以直接使用postman等工具就可以把获得我们后端的数据了呢?因此前端需要在每次请求中都带上token,而后端则会再检验这个token的合法性,从而判断该请求是否合法……

  1. //路由守卫
  2. router.beforeEach((to,from,next)=>{
  3. console.log(to.name)
  4. let token=localStorage.getItem('token');
  5. //如果token存在则放行
  6. if(token){
  7. next();
  8. }else{
  9. if(to.name==='login'||to.name==='register'){
  10. next()
  11. }else{
  12. next('/login')
  13. }
  14. }
  15. })

让每次请求都带上token有两种方法,一种是放到cookie中,另一种则是放在请求头header里,这里我们使用第二种方法,第一种方法有兴趣可以自行查找资料(这种方法没有第二种安全,容易被CSRF(跨站请求伪造)攻击):

很简单,只需要在请求前先获取本地的token,如果没有token则让用户重新登录,如果有,则添加进请求头里:

  1. let token=localStorage.getItem('token');
  2. if(!token){
  3. message.warning("身份已过期,请重新登录!")
  4. routers.push('/login');
  5. return
  6. }else{
  7. let url='/api/user/userList';
  8. fetch(url,{
  9. method: 'GET',
  10. mode: 'cors',
  11. cache: 'no-cache',
  12. credentials: 'same-origin',
  13. headers: new Headers({
  14. 'Content-Type': 'application/json',
  15. 'Authorization':'Bearer '+token,
  16. }),
  17. redirect: 'follow',
  18. }).then((v)=>{
  19. return v.json()
  20. }).then((v)=>{
  21. if(!v){
  22. message.info("null respone!")
  23. return;
  24. }
  25. if(v.status===401||v.status===402){
  26. message.error("身份已过期,请重新登录!")
  27. routers.push('/login');
  28. return
  29. }else if(v.status===200||v.status===201){
  30. this.userList=v.data;
  31. console.log(this.userList);
  32. }
  33. }).catch((err)=>{
  34. console.log(err);
  35. message.error("系统错误!");
  36. })
  37. }

核心就是在请求头里添加一个Authorization字段来存放token,一般我们会再拼接一个"Bearer "字符串在前面。

  1. headers: new Headers({
  2. 'Content-Type': 'application/json',
  3. 'Authorization':'Bearer '+token,
  4. }),

axois同理:

  1. axios.get(url,
  2. {
  3. headers: {
  4. 'Authorization': 'Bearer ' + token,
  5. },
  6. params: {
  7. ……
  8. }
  9. }
  10. )

 这样我们的后端就能获得这个token了,那么后端要如何验证这个token是否合法呢?

这里的处理方法也十分简单,就是直接通过request.Header即可获得请求头了,而header是一个map[string] []string类型的map,因此直接取就可以了,最后除去"Bearer ",即我们想要的token了,注意这里我们还需要判断token的长度是否小于或等于7,如果是的话证明只有"Bearer "或者是非法的token,为了避免我们下面截取字符串的操作不会发生越界,因此这个很重要!

  1. var err error
  2. //连接成功
  3. msg := define.ReplyProto{
  4. Status: 0,
  5. Msg: "success",
  6. }
  7. //从请求头中获取token
  8. header := r.Header
  9. //如果token为空
  10. if header["Authorization"] == nil {
  11. msg.Status = 402
  12. msg.Msg = "token为空"
  13. respone.Resp(w, &msg)
  14. return
  15. }
  16. //获得想要的token部分
  17. token := header["Authorization"][0]
  18. if len(token)<=7{
  19. msg.Status = 402
  20. msg.Msg = "非法token"
  21. respone.Resp(w, &msg)
  22. return
  23. }
  24. token = token[7:]
  25. //验证token是否合法和过期
  26. err = user.ConfirmToken(token)
  27. if err != nil {
  28. msg.Status = 401
  29. msg.Msg = "token过期或者为非法token"
  30. respone.Resp(w, &msg)
  31. return
  32. }

这里我们就可以再封装一个检验token的ConfirmToken函数了,根据官方文档,我们可以使用jwt.ParseWithClaims函数去解析,需要传入一个token字符串,和我们自定义的MyClaims结构体的空接口实现以及一个keyfun()函数,需要我们去返回一个我们自定义的令牌。这个函数最后会返回一个Token结构体,通过断言即可获得我们想要的参数,从而可以使用这些参数去进行进一步的验证和使用:

  1. func ConfirmToken(token string) (err error) {
  2. Token, err := jwt.ParseWithClaims(token, &MyClaim{}, func(token *jwt.Token) (interface{}, error) {
  3. return mySigningKey, nil
  4. })
  5. if err != nil {
  6. fmt.Println(err.Error())
  7. return err
  8. }
  9. fmt.Println(Token.Claims.(*MyClaim).Username)
  10. fmt.Println(Token.Claims.(*MyClaim).Id)
  11. return err
  12. }

 如果token是非法的或者是过期的,则会返回一个err,打印出来就类似于我下面那个token is expired by 55m39s,意思就是这个token已经过期了

 OK,到这里就基本完成了整个前后端分离的token验证啦!如果想要了解得更深入,可以自行去查看下源码

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/638493
推荐阅读
相关标签
  

闽ICP备14008679号