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']