# 鉴权

鉴权(authentication)是指验证用户是否拥有访问系统的权利。

由于 http 是无状态的,它不对之前发生过的请求和响应的状态进行管理。也就是说,无法根据之前的状态进行本次的请求处理。

于是引入了 Cookie 技术。Cookie 技术通过在请求和响应报文中写入 Cookie 信息来控制客户端的状态。

# 原生实现session

const http = require('http')
const session = {};

http.createServer((req, res) => {
    console.log('cookie:', req.headers.cookie)

    const sessionKey = 's_sid'
    const cookie = req.headers.cookie
    // 根据cookie判断是否登录
    if(cookie && ~cookie.indexOf(sessionKey)) {
        const pattern = new RegExp(`${sessionKey}=([^;]+);?\s*`)
        const sid = pattern.exec(cookie)[1]
        console.log('session:',sid ,session ,session[sid])
        
        res.end('hello world');
    } else {
        const sid = (Math.random() * 99999999).toFixed()
        console.log(sid);
        // 设置cookie
        res.setHeader('Set-Cookie', `${sessionKey}=${sid};`)
        session[sid] = {name : 'laotie'}
        res.end('login or register, please');
    }
}).listen(3000, (res) => {
    console.log('app listen to *: 3000')
});

# koa-session

npm i koa-session -S

// index.js
const koa = require('koa')
const app = new koa()
const session = require('koa-session')
// 签名key keys作⽤ ⽤来对cookie进⾏签名
app.keys = ['some secret'];
// 配置项
const SESS_CONFIG = {
    key: 'key: session', // cookie键名
    maxAge: 86400000, // 有效期,默认⼀天
    httpOnly: true, // 仅服务器修改
    signed: true, // 签名cookie
};
// 注册
app.use(session(SESS_CONFIG, app));
// 测试
app.use(ctx => {
    // 获取
    let n = ctx.session.count || 0;
    // 设置
    ctx.session.count = ++n;
    ctx.body = '第' + n + '次访问';
});
app.listen(3000)

# Token

# cookie的不足:

  • 服务器有状态(存储了session)
  • 不适用于app

Token可以解决这个问题。

# Token鉴权流程:

 1. 客户端使⽤⽤户名跟密码请求登录
 2. 服务端收到请求,去验证⽤户名与密码
 3. 验证成功后,服务端会签发⼀个令牌(Token),再把这个 Token 发送给客户端
 4. 客户端收到 Token 以后可以把它存储起来,⽐如放在 Cookie ⾥或者 Local Storage
⾥
 5. 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
 6. 服务端收到请求,然后去验证客户端请求⾥⾯带着的 Token,如果验证成功,就向客户端返回
请求的数据

# koa实现token

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
    <div id="app">
        <div>
            <input v-model="username" />
            <input v-model="password" />
        </div>
        <div>
            <button @click="login">login</button>
            <button @click="logout">logout</button>
            <button @click="getUser">getUser</button>
        </div>
        <div><button @click="logs = []">Clear log</button></div>
        <ul>
            <li v-for="(log,idx) in logs" :key="idx">
                {{ log }}
            </li>
        </ul>
    </div>
    <script>
        axios.interceptors.request.use(
            config => {
                config.baseURL = 'http://localhost:3000/';
                const token = localStorage.getItem('token');
                if(token) {
                    config.headers.common['Authorization'] = "Bearer " + token;
                }
                return config;
            },
            err => {
                return Promise.reject(err);
            }
        );
        axios.interceptors.response.use(
            response => {
                app.logs.push(JSON.stringify(response.data));
                return response;
            },
            err => {
                app.logs.push(JSON.stringify(response.data));
                return Promise.reject(err);
            }
        );
        let app = new Vue({
            el: '#app',
            data: {
                username: 'test',
                password: 'test',
                logs: []
            },
            methods: {
                async login() {
                    const res = await axios.post('/users/login-token', {
                        username: this.username,
                        password: this.password
                    })
                    localStorage.setItem("token", res.data.token);
                },
                async logout() {
                    localStorage.removeItem("token");
                },
                async getUser() {
                    await axios.get("/users/getUser-token");
                }
            }
        })
    </script>
</body>
</html>

index.js

const Koa = require('koa')
const router = require('koa-router')()
const jwt = require("jsonwebtoken")
const jwtAuth = require("koa-jwt")
const secret = "it's a secret"
const bodyParser = require('koa-bodyparser')
const static = require('koa-static')
const app = new Koa();
app.keys = ['some secret'];
app.use(static(__dirname + '/'));
app.use(bodyParser())

router.post("/users/login-token", async ctx => {
    const { body } = ctx.request;
    //登录逻辑,略
    //设置session
    const userinfo = body.username;
    ctx.body = {
        message: "登录成功",
        user: userinfo,
        // ⽣成 token 返回给客户端
        token: jwt.sign(
            {
            data: userinfo,
            // 设置 token 过期时间,⼀⼩时后,秒为单位
            exp: Math.floor(Date.now() / 1000) + 60 * 60
            },
            secret
        )
    };
});

router.get(
    "/users/getUser-token",
    jwtAuth({
        secret
    }),
    async ctx => {
        // 验证通过,state.user
        console.log(ctx.state.user);

        //获取session
        ctx.body = {
            message: "获取数据成功",
            userinfo: ctx.state.user.data
        };
    }
)
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000);