JWT-RESTful进行身份认证

视频链接

服务器存储与客户端存储

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

  • 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())

Flask 与Django

flask 一般在请求钩子中设置 或者 在调用时加入
在这里插入图片描述

jwt 使用装饰器

在这里插入图片描述

init.py

# -*- coding: utf8 -*-

"""
权限管理,JWT实现
"""

from .permission import auth_control, current_auth

__all__ = ['auth_control', 'current_auth']
# -*- 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


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 'startask',
        }
        # 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, current_app.config['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, current_app.config['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())

直接调用 该方法

http://www.bjhee.com/flask-ad1.html

权限控制装饰器

# -*- coding: utf8 -*-

from functools import wraps
from typing import Optional, List, Tuple

from flask import current_app

from titan.user.enum import RoleEnum
from titan.factory import redis
from titan.cache import SignOutUserListKey
from stardust.exceptions import PermissionDenied, Unauthenticated
from stardust.auth import current_auth


def _access_control(auth_roles: Optional[List[RoleEnum]] = None) -> Tuple[str, RoleEnum]:
    id_ = current_auth.id
    user_role = RoleEnum(current_auth.role)
    env = current_auth.env

    if redis.sismember(SignOutUserListKey, id_):
        raise PermissionDenied(message='请重新登录')

    if not auth_roles:
        auth_roles = [RoleEnum.DEFAULT]
    if user_role != RoleEnum.ADMIN and user_role not in auth_roles:
        raise Unauthenticated(message="无权访问")

    if current_app.config['ENV'] != env:
        raise Unauthenticated(message="环境有误,请重新登录")

    return id_, user_role


def auth_control(roles: Optional[List[RoleEnum]] = None):
    def wrapper(func):
        @wraps(func)
        def wrap_func(*args, **kwargs):
            id_, user_role = _access_control(roles)
            return func(id_, user_role, *args, **kwargs)
        return wrap_func
    return wrapper

@blueprint.route('/summary', methods=['GET'])
@auth_control()
def task_issue_summary(*args, **kwargs):

    # 获取请求数据,数据校检
    data_pb = api_request(issue_pb2.AnnotationIssueSummeryRequestProto)
    required = {'task_id', 'container_id'}
    variable_check(data_pb, required)

    frame_indexes = controller.task_issue_summary(current_auth.id, data_pb.task_id, data_pb.container_id,
                                                  data_pb.sampling_id)

    resp_dict = dict(frame_indexes=frame_indexes)
    return api_response(issue_pb2.AnnotationIssueSummaryResponseProto, resp_dict)

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
}

在这里插入图片描述

在这里插入图片描述

获取token
在这里插入图片描述

在这里插入图片描述

JWT 更新状态问题(白名单)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

如何让用户无感知获取最新token

在这里插入图片描述

参考

总结

优点

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

安全相关

  • 不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
  • 保护好secret私钥,该私钥非常重要。
  • 如果可以,请使用https协议
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值