前后端分离

前后端分离定义

  • 前端,即客户端,负责渲染用户显示界面
  • 后端,即服务器端,负责接收http请求,处理数据
  • API(Application Programming Interface)是一些预先定义的函数,或指软件系统不同组成部分衔接的约定

前后端分离请求过程

  • 前端通过http请求后端API
  • 后端以json形式返回前端数据
  • 前端生成用户显示界面

前后端分离优点

  • 前后端开发人员各司其职
  • 前端可以有效率用客户端处理数据,有效降低服务端压力
  • 服务端错误不会直观的反馈到用户
  • 后端灵活搭配各类前端 - 如安卓等
  • 前端+后端可完全并行开发,加快开发效率

分离常见问题

问题 答案
如何解决http无状态? 采用token(详情见下方章节)
如果前端为JS,如何解决跨域问题? 采用CORS(详情见下方章节)
如何解决csrf问题 采用token
Single Page web Application 是否会影响Search Engine Optimization效果 会,前后端分离后,往往页面不存在静态文字【例如新闻的详细内容】
”老板,这个逻辑到底是让前端做还是后端做啊?“ 底线原则: 数据校验需要前后端都做

token令牌

base64

方法 作用 参数 返回值
b64encode 将输入的参数转化为base64规则的串 预加密的明文,类型为bytes;例:b'chaogege' base64对应编码的密文,类型为bytes;例:b'Z3VveGlhb25hbw=='
b64decode 将base64串解密回明文 base64密文,类型为bytes;例:b'Z3VveGlhb25hbw==' 参数对应的明文,类型为bytes;例:b'chaogege'
urlsafe_b64encode 作用同b64encode,但是会将+替换成-,将/替换成_ 同b64encode 同b64encode
urlsafe_b64decode 作用同b64decode 同b64decode 同b64decode
import base64

string_info = b'chaogege'

# base64加密
bs_result = base64.b64encode(string_info)
print(bs_result)
# bs_result: b'Y2hhb2dlZ2U='

# base64解密
ss_result = base64.b64decode(bs_result)
print(ss_result)
# ss_result: b'chaogege'

SHA-256

安全散列算法的一种(hash)

  • hash三大特点
  • 定长输出
  • 不可逆
  • 雪崩
from hashlib import sha256

s = sha256()  # 1.创建sha256对象
s.update(b'chaogege')  # 2.添加欲hash的内容,类型为 bytes
binary_result = s.digest() # 3.获取最终结果(纯二进制,用于计算)
hex_result = s.hexdigest() # 4.十六进制结果(十六进制,用于存储)

# binary_result: b'j\x89\x16\x18\xce\x86C\xbfz^\xf5\x83q\xad\xd6\x8a\xa7\x97\x81\xfb\xaa\xa4d\x1b\xc5\xc9\xdd\xfa\x8b\x0c(\xa0'
# hex_result: 6a891618ce8643bf7a5ef58371add68aa79781fbaaa4641bc5c9ddfa8b0c28a0
print(binary_result)
print(hex_result)

HMAC-SHA256

HMAC-SHA256是一种通过特别计算方式之后产生的消息认证码,使用散列算法同时结合一个加密密钥

  • 它可以用来保证数据的完整性,同时可以用来作某个消息的身份验证
import hmac

# 1. 生成hmac对象
# 参数1:密钥key,bytes类型,
# 参数2:欲加密的串,bytes类型
# 参数3:hmac的算法,指定为SHA256

key = b'123456'
string = b'{"username":"chaogege"}'
h = hmac.new(key, string, digestmod='SHA256')
result = h.hexdigest() # 获取最终结果

# result: 6ae909d8d5f8258d04a806d8ce1e5915e2351eda3efbcc1b9c535c41137c55ab
print(result)

JWT - json-web-token

三大组成

header

  • 格式为字典-元数据格式如下
  • 该部分数据需要转成json串并用base64编码
{'alg':'HS256', 'typ':'JWT'}
#alg代表要使用的 算法
#typ表明该token的类别 - 此处必须为 大写的 JWT

payload

  • 格式为字典-此部分分为公有声明和私有声明
  • 公共声明:JWT提供了内置关键字用于描述常见的问题
  • 此部分均为可选项,用户根据自己需求按需添加key,常见公共声明如下
  • 公共声明和私有声明均在同一个字典中;转成json串并用base64加密
{
  'exp':xxx, # Expiration Time 此token的过期时间的时间戳
  'iss':xxx, # (Issuer) Claim 指明此token的签发者
  'iat':xxx, # (Issued At) Claim 指明此创建时间的时间戳
}
  • 私有声明:用户可根据自己业务需求,添加自定义的key,例如如下:
{'username': 'chaogege'}

signature

  • 根据header中的alg确定 具体算法进行加密,以下用HS256为例
sign = hmac.new(自定义的key,base64后的header + b'.' + base64后的payload,digestmod="SHA256")

# 对加密后的结果进行base64编码
sign = base64.b64encode(sign)

jwt结果格式

  • base64(header) + b'.' + base64(payload) + b'.' + base64(sign)
  • 最终结果如下
b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6Imd1b3hpYW9uYW8iLCJpc3MiOiJnZ2cifQ.Zzg1u55DCBqPRGf9z3-NAn4kbA-MJN83SxyLFfc5mmM'

校验jwt规则

  • 解析header, 确认alg
  • 签名校验 - 根据传过来的header和payload按 alg指明的算法进行签名,将签名结果和传过来的sign进行对比,若对比一致,则校验通过
  • 获取payload自定义内容

pyjwt

# 安装
pip3 install pyjwt

常用方法

  • encode(payload, key, algorithm)
  1. payload:jwt三大组成中的payload,需要组成字典,按需添加公有声明和私有声明 例如: {'username': 'chaogege', 'exp': 1562475112} 参数类型: dict
  2. key : 自定义的加密key 参数类型:str
  3. algorithm: 需要使用的加密算法[HS256, RSA256等] 参数类型:str
  4. 若payload中添加了exp字段; 则exp字段得值需为 当前时间戳+此token得有效期时间
  • decode(token, key, algorithm, issuer)
  1. token: token串 参数类型:bytes/str
  2. key: 自定义的加密key ,需要跟encode中的key保持一致 参数类型:str
  3. algorithm:需要使用的加密算法[HS256, RSA256等] 参数类型:str
  4. issuer:发布者,若encode payload中添加 'iss' 字段,则可针对该字段校验 若iss校验失败,则抛出jwt.InvalidIssuerError 参数类型:str
  5. 在执行decode时,若检查到exp字段,且token过期,则抛出jwt.ExpiredSignatureError
  • pyjwt使用示例
import jwt
import time

# 生成token
token = jwt.encode({'uname':'chaogege'}, '123456',algorithm='HS256')

# 验证签发者:issuer
token = jwt.encode({'uname':'chaogege','iss':'dadashop.com'}, '123456', algorithm='HS256')
result = jwt.decode(token, '123456', algorithms='HS256', issuer='dadashop.com')
print(result)

# 验证有效期
token = jwt.encode({'uname':'chaogege', 'exp':time.time()+10}, '123456', algorithm='HS256')
time.sleep(2)
result = jwt.decode(token, '123456', algorithms='HS256')
print(result)
  • 手写jwt验证代码
"""
    实现jwt的生成和校验
"""
import base64
import hmac
import json
import time


class Jwt:
    def __init__(self):
        pass

    def strtobase64(self,str):
        return base64.urlsafe_b64encode(str.encode()).replace(b'=',b'')
    def base64tostr(self,byte):
        return base64.urlsafe_b64decode(byte)

    def dicttojson(self,dict):
        return json.dumps(dict,separators=(',',':'))

    def jsontodict(self,str):
        return json.loads(str)

    def get_sign(self,header,payload,key):
        sign = hmac.new(key.encode(),header+b'.'+payload,digestmod='SHA256').digest()
        return base64.urlsafe_b64encode(sign).replace(b'=',b'')


    # def make_token(self, payload, key, expire=600):
    def make_token(self,payload_dict,key):
        # 1.第一部分:header
        header_dict = {"alg": "HS256","typ": "JWT"}
        header_json = self.dicttojson(header_dict)
        header = self.strtobase64(header_json)
        # 2.第2部分:payload 共有声明和私有声明
        payload_json = self.dicttojson(payload_dict)
        payload = self.strtobase64(payload_json)
        # 3.第3部分:对前两部分的和进行hmac加密,并进行base64编码
        sign = self.get_sign(header,payload,key)
        return header+b'.'+payload+b'.'+sign
    def check_token(self,token,key):
        # 1.验证签名
        header = token.split('.')[0]
        payload = token.split('.')[1]
        sign = token.split('.')[2]
        if sign.encode() != self.get_sign(header.encode(),payload.encode(),key):
            return False
        payload_json = self.base64tostr(payload)
        payload_dict = self.jsontodict(payload_json)
        if payload_dict['exp'] < time.time():
            return False
        return True

o = Jwt()
get_str = o.make_token({"sub": "1234567890", "name": "John Doe", "iat": 1516239022, "exp": 1716239022}, 'test')
# print(get_str)
is_ok = o.check_token('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE3MTYyMzkwMjJ9.zYaDerrl9TYk3M17nN6237AWKP6LM7vIxg7TzaUdas0','test')
print(is_ok)