四、后台管理端【注册 / 登录】接口
4.1 实现注册逻辑 🚕
注册需要的字段:
phone
password
接口地址:
/api/auth/register
接口类型:
POST
逻辑 :
- 校验参数是否合法
- 查询当前手机号 是否已经注册
- 未注册 -> 新增商家(账号、密码、uid:商家唯一标识【当前时间戳】)
- 响应结果
4.2 实现登录逻辑 👻
登录需要的字段:
phone
password
接口地址:
/api/auth/register
接口类型:
POST
逻辑:
- 校验参数有效性 (起码不能为空)
- 校验用户名与密码 【是否存在、正确】
- 生成Token 响应结果 【后续介绍】 😢
4.3 JWT 原理、结构、和使用
JWT(JSON Web Tokens) 是一种方便地实现服务器与客户端安全通讯的规范,是目前最流行的跨域认证解决方案。
JWT原理
使用 JWT,服务器认证用户之后,会生成包含一个 JSON 对象信息的 token 返回给用户,如:
{
"name": "moyufed",
"role": "admin"
}
然后客户端请求服务的时候,都要带上该 token 以供服务器做验证。服务器还会为这个 JSON 添加签名以防止用户篡改数据。通过使用 JWT,服务端不再保存 session 数据,更加容易实现扩展。
JWT 结构
JWT 是一行使用 “.” 分割成三个部分的字符串,这被分隔的三个部分分别是:Header(头部)、Payload(负载)、Signature(签名),访问 https://jwt.io/ ,可以通过修改算法查看签名的计算公式以及结算结果。
第一部分(Header)实际上是一个 JSON 对象,是描述 JWT 的元数据,其中 alg
表示的是签名的算法,默认 HS256,typ
表示 token 的类型是 JWT,比如:
{
"alg": "HS256",
"typ": "JWT"
}
第二部分(Payload)就是前面提到的 JSON 数据,是希望通过服务器发送给客户端的用户信息,可在这个 JSON 里面定义需要发送的字段,比如:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
第三部分(Signature)用来对 header 和 payload 两部分的数据进行签名,从而防止数据篡改,这个 signature 需要制定一个密钥(Secret),然后通过 header 里面制定的算法来产生签名。产生签名的算法也可以在 https://jwt.io/ 看到,比如:
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 ParamsValidatorts
- 放在 HTTP 请求的头信息 Authorization 字段里面:
Authorization: Bearer <token>
。 - 将 token 放在 POST 请求的数据体里面。
4.4 Koa-jwt 的使用
执行 yarn add
安装 koa-jwt 中间件:
yarn add koa-jwt
导入 koa-jwt
并配置好密钥
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的错误信息
// 中间件 自定义了 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 分支开始不再导出 jsonwebtoken
的 sign
、 verify
和 decode
方法,若要单独生成 token 、验证 token 等,需另从 jsonwebtoken
中将其引入:
下载
jsonwebtoken
->yarn add jsonwebtoken
生成token
tsimport jsonwebtoken from 'jsonwebtoken' // result为加密的内容 secretKey为加密秘钥 expiresIn 设置token时长 const token = jsonwebtoken.sign(result, secretKey, { expiresIn: '1d' })
验证Token
Koa-jwt 有三种途径获取 token:
- 通过自定义 getToken 方法获取
- 通过配置里的 cookie key值获取
- 通过请求头里面的 Authorization 获取,Authorization 的格式一般是
'Bearer <token>'
在这里主要介绍 如何从请求头中获取
由于JWT中间件会默认从请求头中获取 所以如果需要拦截指定路径下的Token可以如下配置:
// Koa-jwt 使用 `unless` 表达式忽略滤路径
app.use(jwt({ secret: 'shared-secret' }).unless({ path: [/^\/public/] }));
完整实现代码
将secretKey秘钥存放在
config.ts
文件中ts// token秘钥 export const secretKey = 'DIAN_CAN_SECRET_KEY'
app.ts
定义jwt校验方式tsimport jwt from 'koa-jwt' import { secretKey } from './config' // JWT认证中间件 【/api/auth】不需要验证 app.use(jwt({ secret: secretKey, debug: true }).unless({ path: [/^\/api\/auth/, /^\/api2/] })) ... 路由文件
登录逻辑中生成token
tsimport jsonwebtoken from 'jsonwebtoken' // 后台- 商家登录 // 3. 生成Token 响应结果 const token = jsonwebtoken.sign(result, secretKey, { expiresIn: '1d' })
警告
前端保存token 后续请求携带在请求头中 格式为 Bearer <token>
4.5 对接后台管理登录
- 打开
diancan-pc
下载依赖包yarn install
- 项目组成: PC端项目由
vue2
+vue-router
+vuex
+elementui
搭建 - 启动项目:
yarn serve
封装API请求
/src/utils/request.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
// 认证模块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
// 注册
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
}
}
提示
登录成功后 若当前商家尚未配置信息 需要进入商家信息页面添加配置