Skip to content
本页目录

四、后台管理端【注册 / 登录】接口

4.1 实现注册逻辑 🚕

  • 注册需要的字段: phone password

  • 接口地址: /api/auth/register

  • 接口类型: POST

  • 逻辑 :

    1. 校验参数是否合法
    2. 查询当前手机号 是否已经注册
    3. 未注册 -> 新增商家(账号、密码、uid:商家唯一标识【当前时间戳】)
    4. 响应结果

4.2 实现登录逻辑 👻

  • 登录需要的字段: phone password

  • 接口地址: /api/auth/register

  • 接口类型: POST

  • 逻辑:

    1. 校验参数有效性 (起码不能为空)
    2. 校验用户名与密码 【是否存在、正确】
    3. 生成Token 响应结果 【后续介绍】 😢

4.3 JWT 原理、结构、和使用

JWT(JSON Web Tokens) 是一种方便地实现服务器与客户端安全通讯的规范,是目前最流行的跨域认证解决方案。

JWT原理

使用 JWT,服务器认证用户之后,会生成包含一个 JSON 对象信息的 token 返回给用户,如:

json
{
  "name": "moyufed",
  "role": "admin"
}

然后客户端请求服务的时候,都要带上该 token 以供服务器做验证。服务器还会为这个 JSON 添加签名以防止用户篡改数据。通过使用 JWT,服务端不再保存 session 数据,更加容易实现扩展。

JWT 结构

JWT 是一行使用 “.” 分割成三个部分的字符串,这被分隔的三个部分分别是:Header(头部)、Payload(负载)、Signature(签名),访问 https://jwt.io/ ,可以通过修改算法查看签名的计算公式以及结算结果。

第一部分(Header)实际上是一个 JSON 对象,是描述 JWT 的元数据,其中 alg 表示的是签名的算法,默认 HS256,typ 表示 token 的类型是 JWT,比如:

json
{
  "alg": "HS256",
  "typ": "JWT"
}

第二部分(Payload)就是前面提到的 JSON 数据,是希望通过服务器发送给客户端的用户信息,可在这个 JSON 里面定义需要发送的字段,比如:

json
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

第三部分(Signature)用来对 header 和 payload 两部分的数据进行签名,从而防止数据篡改,这个 signature 需要制定一个密钥(Secret),然后通过 header 里面制定的算法来产生签名。产生签名的算法也可以在 https://jwt.io/ 看到,比如:

js
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  your-256-bit-secret
)

最终通过把上面三个部分组合成 Header.Payload.Signature 的形式返回给用户。

JWT 使用

客户端收到服务器返回的 token,可以储存在 cookie 或者 localStorage 里,在之后的请求需要上这个 token,通过以下方式携带 token:

  • x import { Context, Next } from 'koa'import Schema, { Rules } from 'async-validator'​// 验证请求参数中间件const ParamsValidator = (rules: Rules) => {  return async (ctx: Context, next: Next) => {    try {      // 生成验证规则      const validator = new Schema(rules)      // 根据当前请求类型解析参数值      const data = ['GET', 'DELETE'].includes(ctx.method)        ? { ...ctx.query }       : ctx.request.body      // 验证参数值      await validator.validate(data)      // 验证通过      await next()   } catch (error) {      console.log(error)      // 验证失败      ctx.error(error.errors ? error.errors[0].message : error.msg, 500)   } }}​export default ParamsValidator​ts
  • 放在 HTTP 请求的头信息 Authorization 字段里面:Authorization: Bearer <token>
  • 将 token 放在 POST 请求的数据体里面。

4.4 Koa-jwt 的使用

执行 yarn add 安装 koa-jwt 中间件:

bash
yarn add koa-jwt

导入 koa-jwt 并配置好密钥

ts
import jwt from 'koa-jwt'

const secret = 'moyufed-test'; // 定义一个密钥secret,这里只是做演示,建议放在项目配置里面

// 这里调用引入的jwt方法,最终会得到一个中间件,将中间件匹配到 / 路径
router.use(jwt({
    secret,
    debug: true // 开启debug可以看到准确的错误信息
}));

通过浏览器访问, 由于浏览器请求里面没有携带任何 token 信息,服务返回了认证错误 Token not found

判断JWT错误

JWT验证错误时会自动抛出 401的错误信息

js
// 中间件 自定义了 401 响应,将用户验证失败的相关信息返回给浏览器
app.use(function(ctx, next){
  return next().catch((err) => {
    if (401 == err.status) {
      ctx.status = 401;
      ctx.body = 'Protected resource, use Authorization header to get access\n';
    } else {
      throw err;
    }
  });
});

生成Token

由于 koa-jwt 从 koa-v2 分支开始不再导出 jsonwebtokensignverifydecode 方法,若要单独生成 token 、验证 token 等,需另从 jsonwebtoken 中将其引入:

  1. 下载 jsonwebtoken -> yarn add jsonwebtoken

  2. 生成token

    ts
    import jsonwebtoken from 'jsonwebtoken'
    // result为加密的内容   secretKey为加密秘钥   expiresIn 设置token时长
    const token = jsonwebtoken.sign(result, secretKey, { expiresIn: '1d' })
    

验证Token

Koa-jwt 有三种途径获取 token:

  1. 通过自定义 getToken 方法获取
  2. 通过配置里的 cookie key值获取
  3. 通过请求头里面的 Authorization 获取,Authorization 的格式一般是 'Bearer <token>'

在这里主要介绍 如何从请求头中获取

由于JWT中间件会默认从请求头中获取 所以如果需要拦截指定路径下的Token可以如下配置:

ts
// Koa-jwt 使用 `unless` 表达式忽略滤路径
app.use(jwt({ secret: 'shared-secret' }).unless({ path: [/^\/public/] }));

完整实现代码

  1. 将secretKey秘钥存放在config.ts文件中

    ts
    // token秘钥
    export const secretKey = 'DIAN_CAN_SECRET_KEY'
    
  2. app.ts 定义jwt校验方式

    ts
    import jwt from 'koa-jwt'
    import { secretKey } from './config'
    
    // JWT认证中间件 【/api/auth】不需要验证
    app.use(jwt({ secret: secretKey, debug: true }).unless({ path: [/^\/api\/auth/, /^\/api2/] }))
    
    ... 路由文件
    
  3. 登录逻辑中生成token

    ts
    import jsonwebtoken from 'jsonwebtoken'
    
    // 后台- 商家登录
    // 3. 生成Token 响应结果
    const token = jsonwebtoken.sign(result, secretKey, { expiresIn: '1d' })
    

警告

前端保存token 后续请求携带在请求头中 格式为 Bearer <token>

4.5 对接后台管理登录

  1. 打开diancan-pc 下载依赖包 yarn install
  2. 项目组成: PC端项目由 vue2 + vue-router + vuex + elementui 搭建
  3. 启动项目: yarn serve

封装API请求

/src/utils/request.js

js
import Axios from 'axios'
import { Message } from 'element-ui'
import { getToken } from './auth'
import store from '@/store'
import router from '@/router'

// 封装的思路:  简化地址的填写【设置默认地址】  简化导入步骤【添加到Vue原型对象中】  简化返回的结果

// 通过create方法创建新的axios对象  并传入默认配置   那么创建的axios对象就会按照配置去发起请求
const service = Axios.create({
  baseURL: 'http://localhost:5001/api', // 请求默认地址
  timeout: 5000 // 请求超时时间
})

// 添加请求拦截器
service.interceptors.request.use((config) => {
  // 在请求头中添加token
  config.headers.authorization = 'Bearer ' + getToken()
  // 返回请求对象
  return config
})

// 添加响应拦截器
service.interceptors.response.use(
  // 响应成功
  (res) => {
    const data = res.data
    // ---- 判断请求是否成功  不为200既是错误 ----
    if (data.code !== 200) {
      Message.error(data.msg || '出现了错误')
      // token有问题 清理登录信息
      if (data.code === 401) {
        store.dispatch('user/logout')
        router.push('/login')
      }
      return Promise.reject(data.msg) // 将错误的响应结果返回
    }
    return data // 将响应的data值返回
  },
  // 响应失败
  (error) => Promise.reject(error)
)

export default service

修改登录/注册逻辑

登录 / 注册 发送对应API请求 进行基础登录

api/auth.js

js
// 认证模块API 【登录 / 注册】
import request from '@/utils/request'

// * 商家登录
export const loginApi = data => request.post('/auth/login', data)

// * 商家注册
export const registerApi = data => request.post('/auth/register', data)

// * 文件上传
export const uploadApi = data => request.post('/merchant/upload', data)

login.vue

js
// 注册
async register() {
    this.loading = true
    try {
        await registerApi(this.formInput)
        this.$message.success('注册成功')
        this.regi = '注册'
        this.loading = false
    } catch (error) {
        this.loading = false
    }
}

// 登录
async login() {
    this.loading = true
    try {
        await this.$store.dispatch('user/login', this.formInput)
        this.$message.success('登录成功')
        this.loading = false
        this.$router.push('/dashboard')
    } catch (error) {
        this.loading = false
    }
}

提示

登录成功后 若当前商家尚未配置信息 需要进入商家信息页面添加配置