JWT、OAuth 2.0、session 用户受权实战

在许多运用中,我们都须要向效劳端供应本身的身份凭据来取得接见一些非公然资本的受权。比方在一个博客平台,我们要修正本身的博客,那末效劳端要求我们能够证实 “我是我” ,才会许可我们修正本身的博客。

为用户供应受权以许可用户操纵非公然资本,有许多种体式格局。比方运用 token、session、cookie,另有许可第三方登录受权的 OAuth 2.0.

为了明白这些手艺的机制和它们之间的关联,本文就来逐一运用这些计划完成一个前端经由历程后端考证受权来接见后端效劳的运用。

我们将用 express 搭建一个简朴的后端,为了保留用户信息,我们运用 mongoDB。前端是一个注册页面和一个登录页面,另外另有一个修正用户暗码的页面,在这个页面上修正暗码的操纵只要在用户登录今后才被许可,也就是被效劳端受权今后才修正暗码,不然返回 401 未受权。

下面就是我们这个简朴 demo 的文件构造:

效劳端构造:

《JWT、OAuth 2.0、session 用户受权实战》

前端页面构造:

《JWT、OAuth 2.0、session 用户受权实战》

如上图,我们在效劳端写了4个路由离别用于用户注册、登录、修正暗码、和登出。个中在登录路由中,用户登录今后将会天生一个用户凭据,在后续修正暗码的路由中将会应用这个凭据来受权用户修正暗码。详细的代码依据差别的受权计划而有所差别。前端相应地分为注册、登录、修正暗码 3 个页面:

注册页面:
《JWT、OAuth 2.0、session 用户受权实战》

登录页面:
《JWT、OAuth 2.0、session 用户受权实战》

修正暗码页面:
《JWT、OAuth 2.0、session 用户受权实战》

我们终究完成的结果就是:
(GIF图过大,能够转到GitHub项目地点检察:地点)

搭建起一个前后端星散的运用框架今后,我们下面顺次运用 token、OAuth 2.0、express-session 来完成用户受权。

1. 运用 session 受权

1.1 session 道理:

应用 session 来考证用户,有两种机制完成。

  1. 须要效劳端在用户登录胜利后天生一个 session ID 保留在效劳端,这个session ID 标识当前会话的用户,今后用户的每一次要求中都邑包括session ID,效劳端能够辨认这个 session ID 考证用户身份然后才会受权。
  2. 把 session ID 和其他数据加密后发给用户,由用户来存储并在今后每次要求中发给效劳端来考证。比方能够用 cookie 存储发送,也能够运用其他客户端存储。

1.2 express-session API:

本文运用 express-session 来完成。而且运用上述 session 的第一种机制。所以先来看一下 express-session 重要的 API:

  • session( options ):天生 session 中间件,运用这个中间件会在当前会话中建立 session,session 数据将会被保留在效劳端,而 session ID 会保留在 cookie。options 为传入的设置参数,有以下这些参数:

    1.  cookie:
           存储 session ID,
           默许值 { path: ‘/‘, httpOnly: true,secure: false, maxAge: null })
    2.  genid:
           一个函数,返回一个字符串用来作为新的 session ID,传入 req 能够按需在 req 上增添一些值。
    3.  name:
           存储 session ID 的 cookie 的名字,默许是'connect.sid',然则假如有多个运用 express-session 的 app 运行在同一个效劳器主机上,须要用差别的名字定名  express-session 的 cookie。
    4.  proxy :
           当设置了secure cookies(经由历程”x-forwarded-proto” header )时信托反向代办。
    5.  resave:
           强迫保留会话,纵然会话在要求时期从未被修正过
    6.  rolling:
           强迫在每次相应时,都设置保留会话标识符的cookie。cookie 到期时刻会被重置为原始时刻 maxAge。默许值为`false`。
    7.  saveUninitialized:
           默许 `true`, 强迫存储未初始化的 session。
    8.  secret ( 必须 ):
           用来对session ID cookie署名,能够供应一个零丁的字符串作为 secret,也能够供应一个字符串数组,此时只要第一个字符串才被用于署名,然则在 express-session 考证 session ID   的时刻会斟酌悉数字符串。 
    9.  store:
           存储 session 的实例。
    10. unset:
           掌握 req.session 是不是作废。默许是 `keep`,假如是  `destroy`,那末 session 就会在相应完毕后被停止。
           
  • req.session:这是 express-session 寄存 session 数据的处所,注重,只要 session ID 存储在 cookie,所以 express-session 会自动搜检 cookie 中的 session ID ,并用这个 session ID 来映射到对应的 session 数据,所以运用 express-session 时我们只需读取 req.session ,express-session 晓得应当读取哪一个 session ID 标识的 session 数据。

    1. 能够从 req.session 读取 session :
           req.session.id:每一个 session 都有一个唯一ID来标识,能够读取这个ID,而且只读不可变动,这是 req.sessionID 的别号;
           req.session.cookie:每一个 session 都有一个唯一 的cookie来存储 session ID,能够经由历程 req.session.cookie 来设置 cookie 的设置项,比方 req.session.cookie.expires 设置为 false ,设置 req.session.cookie.maxAge 为某个时刻。
    2. req.session 供应了这些要领来操纵 session:
           req.session.regenerate( callback (err) ): 天生一个新的 session, 然后挪用 callback;
           req.session.destroy( callback (err) ): 烧毁 session,然后挪用 callback;
           req.session.reload( callback (err) ):  从 store 重载 session 并添补 req.session ,然后挪用 callback;
           req.session.save( callback (err) ): 将 session 保留到 store,然后挪用 callback。这个是在每次相应完今后自动挪用的,假如 session 有被修正,那末 store 中将会保留新的 session;
           req.session.touch(): 用来更新 maxAge。
    
  • req.sessionID:和 req.session.id 一样。
  • store:假如设置这个参数,能够将 session 存储到 redis和mangodb 。一个运用 rtedis 存储 session 的例子。store 供应了一下要领来操纵 store:

    1. store.all( callback (error, sessions) ) :
           返回一个存储store的数组;
    2. store.destroy(sid, callback(error)):
           用session ID 来烧毁 session;
    3. store.clear(callback(error)):
           删除一切 session
    4. store.length(callback(error, len)):
           猎取 store 中一切的 session 的数量
    5. store.get(sid, callbackcallback(error, session)):
           依据所给的 ID 猎取一个 session
    6. store.set(sid, session, callback(error)):
           设置一个 session。
    7. store.touch(sid, session, callback(error)):
            更新一个 session
    

以上就是 express-session 的悉数 API。

1.3 运用 express-session

重点中的重点,巨坑中的巨坑:运用 express-session 是依赖于 cookie 来存储 session ID 的,而 session ID 用来唯一标识一个会话,假如要在一个会话中考证当前会话的用户,那末就要求用户前端能够发送 cookie,而且后端能够吸收 cookie。所以前端我们设置 axios 的 withCredentials = true 来设置 axios 能够发送 cookie,后端我们须要设置相应头 Access-Control-Allow-Credentials:true,而且同时设置 Access-Control-Allow-Origin 为前端页面的效劳器地点,而不能是 * 。我们能够用 cors 中间件替代设置:

// 跨域

app.use(cors({
  credentials:  true,
  origin:  'http://localhost:8082',  // web前端效劳器地点,,不能设置为 * 

}))

我最先就是由于没有设置这个,所以遇到了题目,就是后端登录接口在session中保留 用户名( req.session.username = req.body.username) 今后,在修正用户暗码的接口须要读取 req.session.username 以考证用户的时刻读取不到 req.session.username ,很明显两个接口的 req.session 不是同一个 session,果真 console 出来 的 session ID 是差别的。这就让我想到了 cookie,cookie 是天生今后每次要求都邑带上而且后端能够接见的,如今存储在 cookie 中的 session ID 没有被读取到而是读取到了新 session ID,所以题目就出在后端不能拿到 cookie,也有能够是由于前端发送不出去 cookie。但是最先的时刻搜刮关于 session ID 读取不一致的这个题目我找不到解决办法,而且发明许多人存在一样的题目,然则没有人给出答案,如今经由历程本身的思索想到了解决办法,这是许多人须要防止的巨坑。

如今跨过了最大的一个坑,我们就能够来编写前后端一切的逻辑了。关于注册的逻辑,是一个很简朴的用户注册信息填写页面,它发送用户的名字和暗码到后端注册接口,后端注册接口保留用户的名字和暗码到数据库理。因而我在这里省略掉前端注册页面和后端注册接口,只讲前端登录页面和后端登录接口,前端修正暗码页面和后端修正暗码接口和登出接口。

    1. 前端登录接口:
async function login(){ // 登录
         
        let res = await axios.post('http://localhost:3002/login',{username,password})
        if(res.data.code === 0){
            setLoginSeccess(true)
            alert('登录胜利,请修正暗码')
            
        }else if(res.data.code === 2){
            alert('暗码不准确')
            return
        }else if(res.data.code === 1){
            alert('没有该用户')
            return
        }
    }
    1. 后端登录接口:
const getModel = require('../db').getModel
const router = require('express').Router()
const users = getModel('users')

router.post('/', (req,res,next)=>{
    let {username, password} = req.body
    users.findOne({username},(err,olduser)=>{
        if(!olduser){
            res.send({code:1})// 没有该用户
        }else{
            if(olduser.password === password){// 上岸胜利,天生 session
                req.session.username = olduser.username
                req.session.userID = olduser._id
                console.log('登录时的会话 ID:',req.sessionID)
                req.session.save()
                res.send({code:0})// 登录胜利
            }else{

                res.send({code:2}) // 暗码毛病
            }
        }
    })
})

module.exports = router
    1. 前端修正暗码和登出页面:
// src/axios.config.js:

// 支撑 express-session 的 axios 设置
export function axios_session(){
    axios.defaults.withCredentials = true
    return axios
}
async function modify(){ // 修正暗码
       if(!input.current.value) return alert('请输入新暗码')
       try{
           // 支撑 session 的 axios 挪用
           let res = await axios_session().post('http://localhost:3002/modify',{newPassword:input.current.value})
           if(res.data.code === 0)
               alert('暗码修正胜利')
       }catch(err){
           alert('没有受权 401')  
           console.log(err)
       }
}
async function logout(){ // 登出
        let res = await axios.post('http://localhost:3002/logout')
        if(res.data.code === 0){
            history.back()
        }
}
    1. 后端修正暗码接口:
const getModel = require('../db').getModel
const router = require('express').Router()
const users = getModel('users')
const sessionAuth = require('../middlewere/sessionAuth') 

router.post('/', sessionAuth, (req,res,next)=>{
    let {newPassword} = req.body
    console.log('修正暗码时的会话 ID:',req.session.id)
    if(req.session.username){
        users.findOne({username: req.session.username},(err,olduser)=>{
            olduser.password = newPassword
            olduser.save(err=>{
                if(!err){
                    res.send({code:0})// 修正暗码胜利
                }
            })
        })
    }
})

module.exports = router

sessionAuth 考证中间件:

const sessionAuth = (req,res,next)=>{
    if(req.session && req.session.username){// 考证用户胜利则进入下一个中间件来修正暗码
        next()
    }else{// 考证失利返回 401
        res.sendStatus(401)
    }
}

module.exports = sessionAuth
    1. 后端登出:
const router = require('express').Router()
 
router.post('/', (req,res,next)=>{
    req.session.destroy(()=>console.log('烧毁session,已推出登录'))
    res.send({code:0})
})

module.exports = router

我们还须要挪用 session 的中间件,设置一些参数,才在今后的中间件中运用 req.session 来举行存储、读取和烧毁 session 的操纵:

// server/app.js:

// session
app.use(session({
    secret: '123456789',// 必须,用来署名 session
    unset:'destroy',// 在每次会话就熟后烧毁 session
    resave:true,
    saveUninitialized:false,
    rolling:true,
    cookie:{
        maxAge:60*60*1000// session ID 有用时刻
    }

}))

2. 运用 JWT 受权

2.1 JWT 的道理:

起首来看看 JWT 的观点,JWT 的 token 由 头部(head)、数据(payload)、署名(signature) 3个部份构成 详细每一个部份的构造构成以及JWT更深的解说能够看看这个。个中头部(header)和数据(payload)经由 base64 编码后经由秘钥 secret的署名,就天生了第三部份—-署名(signature) ,末了将 base64 编码的 header 和 payload 以及 signature 这3个部份用圆点 . 衔接起来就天生了终究的 token。

  signature = HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
  token = base64UrlEncode(header) + "." + base64UrlEncode(payload) + signature

token 天生今后,能够将其发送给客户端,由客户端来存储并在今后每次要求中发送会后端用于考证用户。前端存储和发送 token 的体式格局有以下两种:

2.1.1 运用 Header.Authorization + localStorage 存储和发送 token

在 localStorage 中存储 token,经由历程要求头 Header 的 Authorization 字段将 token发送给后端。

这类要领能够防止 CSRF 进击,由于没有运用 cookie ,在 cookie 中没有 token,而 CSRF 就是基于 cookie 来进击的。虽然没有 CSRF ,然则这类要领轻易被 XSS 进击,由于 XSS 能够进击 localStorage ,从中读取到 token,假如 token 中的 head 和 payload 部份没有加密,那末进击者只要将 head 和 payload 的 base64 情势解码出来就能够看到head 和payload 的明文了。这个时刻,假如 payload 庇护敏感信息,我们能够加密 payload。

2.1.2 运用 cookie 存储和发送 token:

在这类情况下,我们须要运用 httpOnly 来使客户端剧本无法接见到 cookie,才保证 token 平安。如许就防止了 CSRF 进击。

2.2 运用 jsonwebtoken 来完成 JWT 用户受权:

jsonwebtoken 重要 API:

1. jwt.sign(payload, secretOrPrivateKey, [options, callback]) 用于签发 token

假如有 callback 将异步的署名 token。

payload 就是我们要在 token 上装载的数据,比方我们能够在上面增添用户ID,用于数据库查询。payload能够是一个object, buffer或许string,payload 假如是 object,能够在内里设置 exp 逾期时刻。

secretOrPrivateKey 即包括HMAC算法的密钥或RSA和ECDSA的PEM编码私钥的string或buffer,是我们用于署名 token 的密钥,secretOrPublicKey 应当和下面 的 jwt.verify 的 secretOrPublicKey 一致。

options 的参数有:

  1)algorithm (default: HS256) 署名算法,这个算法和下面将要讲的 jwt.verify 所用的算法一个一致
  2)expiresIn: 以秒示意或形貌时刻跨度zeit / ms的字符串。如60,"2 days","10h","7d",寄义是:逾期时刻
  3)notBefore: 以秒示意或形貌时刻跨度zeit / ms的字符串。如:60,"2days","10h","7d"
  4)audience:Audience,观众
  5)issuer: Issuer,发行者
  6)jwtid: JWT ID
  7)subject: Subject,主题
  8)noTimestamp:
  9)header
  10)keyid
  11)mutatePayload

2. jwt.verify(token, secretOrPublicKey, [options, callback]) 用于考证 token

假如有 callback 将异步的考证 token。

token 就是我们保留在前端的token,我们将它发送给后端,后端挪用 jwt.verify 并接收 token 和传入放在后端的 secretOrPublicKey 来考证 token。注重这里的 secretOrPublicKey 与之前用于签发 token 的 secretOrPublicKey 应当是同一个。

options 的参数有:

  1)algorithms: 一个包括署名算法的数组,比方  ["HS256", "HS384"].
  2)audience: if you want to check audience (aud), provide a value here. The audience can be checked against a string, a regular expression or a list of strings and/or regular expressions.
  Eg: "urn:foo", /urn:f[o]{2}/, [/urn:f[o]{2}/, "urn:bar"]

  3)complete: return an object with the decoded { payload, header, signature } instead of only the usual content of the payload.
  4)issuer (optional): string or array of strings of valid values for the iss field.
  5)ignoreExpiration: if true do not validate the expiration of the token.
  6)ignoreNotBefore...
  7)subject: if you want to check subject (sub), provide a value here
  8)clockTolerance: number of seconds to tolerate when checking the nbf and exp claims, to deal with small clock differences among different servers
  9)maxAge: the maximum allowed age for tokens to still be valid. It is expressed in seconds or a string describing a time span zeit/ms.
  Eg: 1000, "2 days", "10h", "7d". A numeric value is interpreted as a seconds count. If you use a string be sure you provide the time units (days, hours, etc), otherwise milliseconds unit is used by default ("120" is equal to "120ms").

  10)clockTimestamp: the time in seconds that should be used as the current time for all necessary comparisons.
  11)nonce: if you want to check nonce claim, provide a string value here. It is used on Open ID for the ID Tokens. (Open ID implementation notes)

3. jwt.decode(token [, options]) 解码 token

只是解码 token 中的 payload,不会考证 token。
options 参数有:

  1)json: 强迫在 payload 用JSON.parse 序列化,纵然头部没有声明 "typ":"JWT"  
  2)complete: true 则返回解码后的包括  payload 和 header  的对象.

4. 毛病码

在考证 token 的历程当中能够或抛出毛病,jwt.verify() 的回调的第一个参数就是 err,err 对象有一下几种范例:

  1. TokenExpiredError:
   err = {
        name: 'TokenExpiredError',
        message: 'jwt expired',
        expiredAt: 1408621000
      }
  1. JsonWebTokenError:
err = {
        name: 'JsonWebTokenError',
        message: 'jwt malformed'
        /*
          message 有以下几个能够的值:
            'jwt malformed'
            'jwt signature is required'
            'invalid signature'
            'jwt audience invalid. expected: [OPTIONS AUDIENCE]'
            'jwt issuer invalid. expected: [OPTIONS ISSUER]'
            'jwt id invalid. expected: [OPTIONS JWT ID]'
            'jwt subject invalid. expected: [OPTIONS SUBJECT]'
      */
      }
  1. NotBeforeError:
err = {
        name: 'NotBeforeError',
        message: 'jwt not active',
        date: 2018-10-04T16:10:44.000Z
      }

5. jsonwebtoken 的署名算法

HS256、HS384、HS512、RS256 等。

2.3 最先运用 jsonwebtoken:

后端登录接口如今须要改用 JWT 来签发 token,把本来运用 express-session 的代码去掉:

if(olduser.password === password){// 暗码准确
 
                /*
                
                // 受权要领 1. session 
                req.session.username = olduser.username
                req.session.userID = olduser._id
                console.log('登录时的会话 ID:',req.sessionID)
                req.session.cookie.maxAge = 60*60*1000
                req.session.save()
                res.send({code:0})// 登录胜利

                */

                // 受权要领 2. JWT
                let token = JWT.sign(
                    {username:olduser.username, exp:Date.now() + 1000 * 60}, // payload
                    secret, // 署名密钥
                    {algorithm} // 署名算法
                )
                res.send({
                    code:0,
                    token
                })
                
            }else{

                res.send({code:2}) // 暗码毛病
            }

后端给前端发回了 token,前端须要存储 token 以便于后续要求受权,能够存储在 localStorage ,在修正暗码页面再掏出 localStorage 中 的 token,并再 axios 发送要求之前阻拦要求,在要求头的 Authorization 中带上 token:

前端存储 token:

// src/pages/login.js:

alert('登录胜利,请修正暗码')
localStorage.setItem('token',res.data.token)

前端阻拦 axios 要求,从 localStorage 中掏出保留好的 token,在要求头带上 token:

// src/axios.config.js:

// 支撑 JWT 的 axios 设置
export  function axios_JWT(){
    axios.interceptors.request.use(config => {
        // 在 localStorage 猎取 token
        let token = localStorage.getItem("token");
        console.log('axios设置:token',token)
        // 假如存在则设置要求头
        if (token) {
            config.headers['Authorization'] = token;
            console.log(config)
        }
        return config;
    });
    return axios
}

前端修正暗码页面挪用能够阻拦要求的 aios 来发送修正暗码的要求:

// src/pages/ModifyUserInfo.js:

 // 支撑 JWT 的 axios 挪用
           let res = await axios_JWT().post('http://localhost:3002/modify',{newPassword:input.current.value})

后端修正暗码接口挪用 JWT 的用户认证中间件:

认证中间件:

const JWT = require('jsonwebtoken')
const secret = require('../server.config').JWT_config.secret
const algorithm = require('../server.config').JWT_config.algorithm


function JWT_auth(req,res,next){
    let authorization = req.headers["authorization"]
    console.log('authorization',authorization)
    if(authorization)
    try{
        let token = authorization;
        JWT.verify(token,secret,{algorithm:'HS256'},(err,decoded)=>{ // 用户认证
            if(err){
                console.log(err)
                next(err)
            }else{
                console.log(decoded)
                req.username = decoded.username // 在 req 上增添 username,以便于传到下一个中间件掏出 username 来查询数据库
                next()
            }
        })

    }catch(err){
        res.status(401).send("未受权");
    }
    else
    res.status(401).send("未受权");
}
module.exports = JWT_auth

3. 运用 OAuth 2.0 受权:

3.1 OAuth 2.0 是什么

有的运用会供应第三方运用登录,比方掘金 web 客户端供应了微信、QQ账号登录,我们能够不必注册掘金账号,而能够用已有的微信账号登录掘金。看看用微信登录掘金的历程:

step1: 翻开掘金,未登录状况,点击登录,掘金给我们弹出一个登录框,上面有微信、QQ登录选项,我们挑选微信登录;<br/>

step2: 今后掘金会将我们重定向到微信的登录页面,这个页面给出一个二维码供我们扫描,扫描今后;<br/>

step3: 我们翻开微信,扫描微信给的二维码今后,微信讯问我们是不是赞同掘金运用我们的微信账号信息,我们点击赞同;<br/>

step4: 掘金适才重定向到微信的二维码页面,如今我们赞同掘金运用我们的微信账号信息今后,又重定向回掘金的页面,同时我们能够看到如今掘金的页面上显现我们已处于登录状况,所以我们已完成了用微信登录掘金的历程。<br/>

这个历程比我们注册掘金后才登录要快速多了。这归功于 OAuth2.0 ,它许可客户端运用(掘金)能够接见我们的资本效劳器(微信),我们就是资本的具有者,这须要我们许可客户端(掘金)能够经由历程认证效劳器(在这里指微信,认证效劳器和资本效劳器能够离开也能够是布置在同一个效劳上)的认证。很明显,OAuth 2.0 供应了4种角色,资本效劳器、资本的具有者、客户端运用 和 认证效劳器,它们之间的交换完成了 OAuth 2.0 全部认证受权的历程。

OAuth 2.0 登录的道理,依据4中差别的形式有所差别。本文运用受权码形式,所以只讲受权码形式下 OAuth2.0 的登录历程,其他形式能够自行搜刮进修。

3.2 运用 GitHub OAuth 来登录我们的项目客户端

能够参考GitHub 官网
下面我们改用 OAuth2.0 来运用 GitHub 账号来受权我们上面的运用,从而修正我们运用的暗码。

步骤:

  1. 在 GitHub 上请求注册一个 OAuth application:https://github.com/settings/a…

填写我们的运用称号、运用首页和受权须要的回调 URL:

《JWT、OAuth 2.0、session 用户受权实战》

  1. 然后GitHub 天生了 Client ID 和 Client Secret:

《JWT、OAuth 2.0、session 用户受权实战》

  1. 今后我们在我们原有的登录页面增添一个运用 GitHub 账号登录的进口:

《JWT、OAuth 2.0、session 用户受权实战》

这个登录进口实在就是一个指向 GitHub 登录页面的衔接

 <a href='https://github.com/login/oauth/authorize?client_id=211383cc22d28d9dac52'> 运用 GitHub 账号登录 </a>
  1. 用户进入上面的 GitHub 登录页面今后,能够输入本身的GitHub用户名和暗码登录,然后 GitHub 会将受权码以回调情势传回之前我们设置的 http://localhost:3002/login/callback 这个页面上,比方 http://localhost:3002/login/callback?code=37646a38a7dc853c8a77,

我们能够在 http://localhost:3002/login/callback 这个路由猎取 code 受权码,并连系我们之前取得的 client-id、client_secret,向https://github.com/login/oaut…,token 猎取今后,我们能够用这个 token向 https://api.github.com/user?a… 要求到用户的GitHub账号信息比方GitHub用户名、头像等等。

// server/routes/login.js:

// 运用 OAuth2.0 时的登录接口,
router.get('/callback',async (req,res,next)=>{//这是一个受权回调,用于猎取受权码 code
    var code = req.query.code; // GitHub 回调传回 code 受权码
    console.log(code)
    
    // 带着 受权码code、client_id、client_secret 向 GitHub 认证效劳器要求 token
    let res_token = await axios.post('https://github.com/login/oauth/access_token',
    {
        client_id:Auth_github.client_id,
        client_secret:Auth_github.client_secret,
        code:code
    })
   console.log(res_token.data)

   let token = res_token.data.split('=')[1].replace('&scope','')
   

   // 带着 token 从 GitHub 猎取用户信息
   let github_API_userInfo = await axios.get(`https://api.github.com/user?access_token=${token}`)
   console.log('github 用户 API:',github_API_userInfo.data)

   let userInfo = github_API_userInfo.data

   // 用户运用 GitHub 登录后,在数据库中存储 GitHub 用户名
   users.findOne({username:userInfo.name},(err,oldusers)=>{ // 看看用户之前有无登录过,没有登录就会在数据库中新增 GitHub 用户
    if(oldusers) {
        res.cookie('auth_token',res_token.data)
        res.cookie('userAvatar',userInfo.avatar_url)
        res.cookie('username',userInfo.name)

        res.redirect(301,'http://localhost:8082') // 从GitHub的登录跳转回我们的客户端页面
        return
    }else
    new users({
        username:userInfo.name,
        password:'123', // 为运用第三方登录的能够用户初始化一个暗码,背面用户能够本身去修正
    }).save((err,savedUser)=>{
        if(savedUser){
            res.cookie('auth_token',res_token.data)
            res.cookie('userAvatar',userInfo.avatar_url)
            res.cookie('username',userInfo.name)
         
            res.redirect(301,'http://localhost:8082') // 从GitHub的登录跳转回我们的客户端页面
        }
    })
   })
},
)
module.exports = router

在要求到用户的GitHub信息今后,我们能够将用户头像和用户名存在cookie、里,便于发送给前端在页面上显现出来,通知用户他已用GitHub账号登录了我们的客户端。
同时,我们把GitHub用户名存到我们本身的数据库里,并给一个‘123’简朴的初始化暗码,背面用户能够在取得权限后修正暗码。

  1. 接下来,我们运用GitHub登录后,我们须要取得受权以修正我们的暗码。

我们运用和 JWT 一样的发送token的体式格局,前面我们从GitHub取得用户的token今后有已用cookie的体式格局将其发送给前端,我们在前端能够读取cookie里的token,然后将其经由历程 Authorization 头体式格局给后端考证:

前端读取 token,并加到 Authorization 里:

 // OAuth2.0
    axios.interceptors.request.use(config => {
        // 在 localStorage 猎取 token
        let token = localStorage.getItem("token");
        console.log('axios设置:token',token)
        // 假如存在则设置要求头
        if(document.cookie){
            let OAtuh_token = unescape(document.cookie.split(';').filter(e=>/auth_token/.test(e))[0].replace(/auth_token=/,''))
            config.headers['Authorization'] = OAtuh_token;
            console.log(config)
        }
       
        return config;
    });
   

后端考证中间件 :

const axios = require('axios')

const OAuth=async (req,res,next)=>{
    let OAuth_token = req.headers["authorization"]
    console.log('authorization',OAuth_token)
    console.log('OAuth 中间件拿到cookie中的token:',OAuth_token)
    if(OAuth_token) {
        let token = OAuth_token.split('=')[1].replace('&scope','')
        let github_API_userInfo = await axios.get(`https://api.github.com/user?access_token=${token}`)
        let username = github_API_userInfo.data.name
        req.username = username
        next()
    }
    else res.status(401)
}
module.exports = OAuth

3.3 运用 GitHub OAuth2.0 登录受权的结果图:

GIF地点

总结

session、JWT、OAuth2.0 这三种受权体式格局每一种内里都邑有其他体式格局的影子,重如果体如今用户凭据的存储和发奉上,比方一般所说的基于效劳端的 session,它能够把用户凭据,也就是 session ID 存储在效劳端(内存或许数据库redis等),然则也是能够发给前端经由历程cookie保留的。JWT 能够把作为用户凭据的 token 在效劳端签发后发给用户保留,能够在 localStorage 保留,一样也能够保留在 cookie 。OAuth2.0是比较复杂的一种受权体式格局,然则它背面取得 token 后也能够像 JWT 一样处置惩罚 token 的保留和考证来受权用户。

不管是哪一种体式格局,都邑有一些要注重的平安题目,另有性能上须要统筹的处所。这里有关这方面不再赘述。

末了,本项目标地点:https://github.com/qumuchegi/auth-demo

    原文作者:车格
    原文地址: https://segmentfault.com/a/1190000019210466
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞