1. 程式人生 > 其它 >vue+kotlin 實現基於JWT的 token認證

vue+kotlin 實現基於JWT的 token認證

1.JWT

JWT全稱(JsonWebToken),簡單來說它是Json格式的加密字串,其中包含敏感資訊,使我們能夠驗證不同服務之間的傳送者。

為什麼要使用jwt呢?

在以往的驗證使用者狀態,我們採取的是Cookie、Session機制。客戶端將Cookie儲存起來,服務端儲存Session。在客戶端請求服務端的時候會在請求頭上攜帶Cookie資訊,服務端會使用過濾器過濾驗證Cookie資訊,從而達到識別使用者狀態的效果。

Session和Cookie機制存在以下問題

  • Session是存在服務端的,需要佔用伺服器記憶體。如果是分散式的情況下,還要考慮Session共享問題
  • Cookie被截獲,容易被偽造請求攻擊
Token

基於以上問題,Token就出現了

Token是由在客戶端第一次發來登入請求,服務端會生成一個token返回給客戶端,由客戶端自己儲存。在每次請求資源時候需要帶上token。服務端對token進行解密驗證,驗證通過則返回資料。

Token的流程如下圖

JWT的構成

header

jwt的頭部有兩部分組成

  • 宣告型別
  • 宣告加密的演算法
{
  'typ': 'JWT',
  'alg': 'HS256'
}

Payload

載荷就是存放有效資訊的地方

signature

簽名信息

2.程式碼實現

kotlin服務端

登入介面

object LoginController {

    suspend fun loginAction(call: ApplicationCall) {
        val result = ResponseResult()
        if (call.parameters["username"]=="zs"&&call.parameters["password"]=="1234"){
            result.code=200
            result.message="登陸成功"
            call.response.header("Access-Control-Expose-Headers","Authorization")
            call.response.header("Authorization",JwtUtils.generationToken(call.parameters["username"]!!))
        }else{
            result.code=403
            result.message="使用者名稱或者密碼錯誤"
        }
        call.respond(result)
    }
}

攔截器

import io.ktor.routing.*

class TokenRouteSelector(val role: String) : RouteSelector(RouteSelectorEvaluation.qualityConstant) {
    override fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation {
        return RouteSelectorEvaluation.Constant
    }
    override fun toString(): String = "(tokenCheck ${role})"
}

fun Route.tokenCheck(role: String = "default", build: Route.() -> Unit): Route {
    val route = createChild(TokenRouteSelector(role))
    route.intercept(ApplicationCallPipeline.Features) {
        val token = call.request.headers["Authorization"]
        println(token)
        if (call.request.uri.contains("login")) {
            proceed()
        } else {
            token?.let {
                if (JwtUtils.getClaimByToken(token).toString() != "{}") {
                    if (!JwtUtils.isTokenExpired(JwtUtils.getClaimByToken(token))) {
                        proceed()
                    }
                } else {
                    call.respond(HttpStatusCode.Forbidden, mapOf("code" to "error token"))
                    finish()
                }
            }?: let {
                call.respond(HttpStatusCode.Unauthorized, mapOf("code" to "no auth token"))
                finish()
            }
        }
    }
    route.build()
    return route
}

jwt工具類

package com.oasis.tga.utils

import io.jsonwebtoken.Claims
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.impl.DefaultClaims
import java.util.*

class JwtUtils {
    companion object{
        private val nowDate = Date()
        private val expireDate = Date(nowDate.time + 1000 * 60 * 60 * 24 * 7)
        private val secret = "dwadadag234423"
				// 生成token
        fun generationToken(username: String): String {
            return Jwts.builder()
                // header
                .setHeaderParam("typ", "JWT")
                // payload
                .claim("username", username)
                .setSubject(username)
                .setIssuedAt(nowDate)
                .setExpiration(expireDate)
                .setId(UUID.randomUUID().toString())
                // signature
                .signWith(SignatureAlgorithm.HS256, secret)
                .compact()
        }
				// 解析token
        fun getClaimByToken(jwt: String): Claims {
            return try {
                Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(jwt)
                    .body
            } catch (e: Exception) {
                DefaultClaims()!!
            }
        }
				// 判斷按是否過期
        fun  isTokenExpired(claims: Claims):Boolean{
            return  claims.expiration.before(Date())
        }


    }

}
vue客戶端

登入請求 login.vue

const that = this
this.$refs[formName].validate((valid) => {
     if (valid) {
            this.axios.get('http://localhost:8080/api/login', {params: this.form}).then(function (response) {
              if (response.data.code == 200 && response.data.message === "登陸成功") {
                console.log(response)
                //全域性儲存token
                window.localStorage["Authorization"] =response.headers['authorization'];
                // 使用 vue-router 路由到指定頁面,該方式稱之為程式設計式導航
                // that.$router.push("/main");
              } else {
                that.$message.error('使用者名稱或密碼錯誤');
              }
            })
          } else {
            this.dialogVisible = true;
            return false;
          }

  });

index.js

// 導航守衛
// 使用 router.beforeEach 註冊一個全域性前置守衛,判斷使用者是否登陸
router.beforeEach((to, from, next) => {
  if (to.path === '/login') {
    next();
  } else {
    let token = localStorage.getItem('Authorization');
    if (token === null || token === '') {
      next('/login');
    } else {
      next();
    }
  }
});

main.js

// 攔截請求
axios.interceptors.request.use(
  config => {
    config.headers.Authorization = window.localStorage["Authorization"]
    return config
  }
);
//攔截響應
axios.interceptors.response.use(
  res => {
    return res
  },
  error => {
    if (error.response.status === 403) {
      window.localStorage.removeItem('Authorization');
      router.push({name: 'login'})
    }
  }
);

App.vue 引入表單重新整理-跟token程式碼無關

  export default {
    name: 'App',
    provide(){
      return {
        reload: this.reload
      }
    },
    data(){
      return {
        isRouterAlive:true
      }
    },
    methods:{
      reload(){
        this.isRouterAlive=false
        this.$nextTick(()=>this.isRouterAlive=true)
      }
    },

  }

使用以下程式碼引入

inject: ['reload']