JWT-RESTful进行身份认证

视频链接

文章目录

  • 服务器存储与客户端存储
    • 基于服务器的身份认证方式存在一些问题:
    • 客户端存储
  • JWT的实现
    • Python语言实现
    • Go语言实现
  • 总结
    • 优点
    • 安全相关

服务器存储与客户端存储

基于服务器的身份认证方式存在一些问题:

  • Sessions : 每次用户认证通过以后,服务器需要创建一条记录保存用户信息,通常是在内存中,随着认证通过的用户越来越多,服务器的在这里的开销就会越来越大。
  • Scalability : 由于Session是在内存中的,这就带来一些扩展性的问题。
  • CORS : 扩展我们的应用,让我们的数据被多个移动设备使用时,我们必须考虑跨资源共享问题。当使用AJAX调用从另一个域名下获取资源时,我们可能会遇到禁止请求的问题。
  • CSRF : 用户很容易受到CSRF攻击。

客户端存储

JWT与Session的差异 相同点是,它们都是存储用户信息;然而,Session是在服务器端的,而JWT是在客户端的。

Session方式存储用户信息的最大问题在于要占用大量服务器内存,增加服务器的开销。

而JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力。

Session的状态是存储在服务器端,客户端只有session id;而Token的状态是存储在客户端。

JWT的实现

  • playload
    载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

  • 标准中注册的声明
    公共的声明
    私有的声明

  • 标准中注册的声明 (建议但不强制使用) :

    iss: jwt签发者
    sub: jwt所面向的用户
    aud: 接收jwt的一方
    exp: jwt的过期时间,这个过期时间必须要大于签发时间
    nbf: 定义在什么时间之前,该jwt都是不可用的.
    iat: jwt的签发时间
    jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

Python语言实现

# -*- coding: utf8 -*-

from typing import Optional

import datetime

import jwt
from werkzeug.local import LocalProxy
from flask import current_app, request, has_app_context, _app_ctx_stack

from . import exceptions
import os


registered_claims = {'iss', 'sub', 'aud', 'exp', 'nbf', 'iat', 'jti'}


class _AuthObject:
    def __init__(self, **kwargs):
        for key, val in kwargs.items():
            setattr(self, key, val)

    def __getattr__(self, item):
        return None

    def __repr__(self):
        return str(self.__dict__)


def encode_token(iss: Optional[str] = None,
                 expire: Optional[datetime.timedelta] = None,
                 **kwargs):
    """
    encode jwt token
    :param iss:
    :param expire: timedelta object, set the expire time of jwt
    :param kwargs:
    :return:
    """
    try:
        header = {'algorithm': 'HS256', 'type': 'JWT'}
        payload = {
            'iat': datetime.datetime.utcnow(),
            'iss': iss or 'website.com',
        }
        # update public claim names
        payload.update(**kwargs)
        # if set jwt expire time, update exp claim
        if expire:
            payload['exp'] = payload['iat'] + expire

        # gen jwt token
        token = jwt.encode(payload, os.getenv('SECRET_KEY'), headers=header)

        return token
    except Exception:
        raise exceptions.Internal(message='无效token')


def decode_token(token, verify_exp: bool = False):
    """
    decode jwt token
    :param token:
    :param verify_exp:
    :return:
    """
    try:
        tmp = {}
        payload = jwt.decode(token, os.getenv('SECRET_KEY'), options={
                             'verify_exp': verify_exp})
        # get the public claim names
        for field in payload.keys():
            if field in registered_claims:
                continue
            tmp[field] = payload[field]

        return tmp
    except jwt.ExpiredSignatureError:
        raise exceptions.Unauthenticated(message='token已过期')
    except jwt.InvalidTokenError:
        raise exceptions.Unauthenticated(message='无效token')


def _get_auth():
    if not has_app_context():
        raise RuntimeError(
            'No application found. Either work inside a view function or push'
            ' an application context.'
        )

    if not hasattr(_app_ctx_stack.top, 'auth'):
        # get and validate auth filed in request header
        auth_header = request.headers.get('Authorization')
        if not auth_header:
            raise exceptions.InvalidArgument(message="请求头错误")
        auth_attr = auth_header.split(' ')
        if not auth_attr or auth_attr[0] != 'JWT' or len(auth_attr) != 2:
            raise exceptions.InvalidArgument(message="token格式错误")

        payload = decode_token(auth_attr[1])
        # set auth data to app context
        _app_ctx_stack.top.auth = _AuthObject(**payload)

    return getattr(_app_ctx_stack.top, 'auth')


# global variable
current_auth = LocalProxy(lambda: _get_auth())

Go语言实现

package main

import (
	"fmt"
	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
	"net/http"
	"time"
)

//自定义一个字符串
var jwtkey = []byte("www.topgoer.com")
var str string

type Claims struct {
	UserId uint
	jwt.StandardClaims
}

func main() {
	r := gin.Default()
	r.GET("/set", setting)
	r.GET("/get", getting)
	//监听端口默认为8080
	r.Run(":8080")
}

//颁发token
func setting(ctx *gin.Context) {
	expireTime := time.Now().Add(7 * 24 * time.Hour)
	claims := &Claims{
		UserId: 2,
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: expireTime.Unix(), //过期时间
			IssuedAt:  time.Now().Unix(),
			Issuer:    "127.0.0.1",  // 签名颁发者
			Subject:   "user token", //签名主题
		},
	}
	fmt.Println(claims)
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	fmt.Println(token)
	tokenString, err := token.SignedString(jwtkey)
	fmt.Println(tokenString)
	if err != nil {
		fmt.Println(err)
	}
	str = tokenString
	ctx.JSON(200, gin.H{"token": tokenString})
}

//解析token
func getting(ctx *gin.Context) {
	tokenString := ctx.GetHeader("Authorization")
	//vcalidate token formate
	if tokenString == "" {
		ctx.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "权限不足"})
		ctx.Abort()
		return
	}
	token, claims, err := ParseToken(tokenString)
	if err != nil || !token.Valid {
		ctx.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "权限不足"})
		ctx.Abort()
		return
	}
	fmt.Println(111)
	fmt.Println(claims.UserId)
}
func ParseToken(tokenString string) (*jwt.Token, *Claims, error) {
	Claims := &Claims{}
	token, err := jwt.ParseWithClaims(tokenString, Claims, func(token *jwt.Token) (i interface{}, err error) {
		return jwtkey, nil
	})
	fmt.Println(token, Claims)
	return token, Claims, err
}

JWT-RESTful进行身份认证_第1张图片

在这里插入图片描述

获取token
JWT-RESTful进行身份认证_第2张图片

在这里插入图片描述

参考

总结

优点

因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
它不需要在服务端保存会话信息, 所以它易于应用的扩展

安全相关

  • 不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
  • 保护好secret私钥,该私钥非常重要。
  • 如果可以,请使用https协议
    JWT-RESTful进行身份认证_第3张图片

你可能感兴趣的:(Go,#,中间件)