php 登陆令牌例子,常见登录认证 DEMO

⭐️ 更多前端技术和知识点,搜索订阅号 JS 菌 订阅

1460000019853663?w=640&h=426

basic auth

basic auth 是最简单的一种,将用户名和密码通过 form 表单提交的方式在 Http 的 Authorization 字段设置好并发送给后端验证

要点:

不要通过 form 提交表单的默认方式发送请求,转而使用 fetch 或 ajax

客户端注意设置 Authorization 字段的值为 'Basic xxx',通过该 Http 字段传递用户名密码

base64 的方法在客户端要注意兼容性 btoa ,建议使用现成的库如 'js-base64' 等,NodeJS 方面使用全局的 Buffer

服务端验证失败后,注意返回 401,但不用返回 'WWW-Authenticate: Basic realm="..."' 避免浏览器出现弹窗

AMD

login

require.config({

baseUrl: 'js/libs',

paths: {

'zepto': 'zepto.min',

},

shim: {

'zepto': 'zepto',

}

});

define(['zepto'], function ($) {

let $form = $('#form')

$form.on('submit', (e) => {

e.preventDefault()

$.ajax({

// ajax 发送验证请求

type: 'POST',

url: '/login',

headers: {

'Content-Type': 'application/x-www-form-urlencoded',

'Authorization': 'Basic ' + btoa($('#username').val() + ':' + $('#password').val()),

// 通过 Authorization 传递 base64 编码后的用户名密码

},

success: function (data) {

console.dir(data) // 回调

}

})

})

});

(忽略上述 ajax 加 requirejs 古老的写法 ? )

const Koa = require('koa')

const static = require('koa-static')

const router = require('koa-better-router')().loadMethods()

const koaBody = require('koa-body')

const app = new Koa()

app.use(koaBody())

app.use(router.middleware())

app.use(static('public'))

app.listen(8080)

router.post('/login', (ctx, next) => {

// 省略从数据库中提取用户密码

if (ctx.get('Authorization') === 'Basic ' + Buffer('fdsa:fdsa').toString('base64')) {

// 获取 Authorization 字段 比对 base64 用户名密码

ctx.body = 'secret'

ctx.type = 'text/html'

ctx.status = 200 // 匹配成功

} else {

ctx.status = 401 // 匹配失败

}

next()

})

cookie auth

这种登录方式实际上就是验证用户信息后,将验证 session 存放在 session cookie 内。一旦过期就需要用户重新登录

要点:

session cookie 用户信息容易被截取,需要设置 https

session 的会话时间内 cookie 有效,如需要长时生效需要设置过期时间 Max-age, Expires 等

const Koa = require('koa')

const static = require('koa-static')

const router = require('koa-better-router')().loadMethods()

const koaBody = require('koa-body')

const fs = require('fs')

const app = new Koa()

app.listen(8080)

app.use(koaBody())

app.use(router.middleware())

app.use(static('public'))

router.post('/login', (ctx, next) => {

// 省略从数据库中提取用户密码

let auth = ctx.request.body

if (auth.username === 'fdsa', auth.password === 'fdsa') {

// session cookie验证的用户名和密码属于明文传输,需要 https

ctx.cookies.set('auth', auth.username) // 没有设置过期时间,属于Session Cookie

// Koa 服务端默认设置的 cookie 是 session cookie

ctx.status = 200

ctx.type = 'application/json'

ctx.body = { data: 1 }

next()

} else {

ctx.status = 401

next()

}

})

router.get('/admin', (ctx, next) => {

if (ctx.cookies.get('auth')) {

ctx.body = 'secret'

ctx.status = 200

next()

}

})

SessionSigned Cookie Auth

目前常用的方法,针对 cookie Auth 的改进

要点:

经过签名的 Cookie 安全性提高,要注意加强对签名的密钥的保护

可通过每次访问受权限限制的页面刷新 SessionCookie

Koa 建议使用 koa-session 库

const Koa = require('koa')

const static = require('koa-static')

const router = require('koa-better-router')().loadMethods()

const koaBody = require('koa-body')

const session = require('koa-session'); // session

const app = new Koa()

app.listen(8080)

app.use(koaBody())

app.use(router.middleware())

app.use(static('public'))

app.keys = ['session key'] // 签名

app.use(session({

key: '_session',

signed: true, // 签名,经过签名的 cookie 安全性比普通 cookie 高

maxAge: 'session' // 设置过期时间 session 表示当前会话有效

}, app))

router.post('/login', (ctx, next) => {

// 省略从数据库中提取用户密码

let auth = ctx.request.body

if (auth.username === 'fdsa', auth.password === 'fdsa') {

// 登陆成功,username 结合签名放入到 session cookie 中用于将来鉴别身份

ctx.session.user = auth.username

ctx.status = 200

ctx.type = 'application/json'

ctx.body = { data: 1 }

next()

} else {

ctx.status = 401

next()

}

})

router.get('/admin', (ctx, next) => {

if (ctx.session.user === 'fdsa') {

let count = ctx.session.count || 0

// 每次都将刷新 session cookie 存在客户端的 session cookie 会随着刷新动作而变化

ctx.session.count = ++count

ctx.body = 'visit count: ' + count

ctx.status = 200

next()

} else {

ctx.status = 401

next()

}

})

JWT token auth

此种令牌登录方式比较主流,用户输入登录信息,发送给服务器验证,通过后返回 token,token 可以存储在前端任何地方。随后用户请求需要验证的资源,发送 http 请求的同时将 token 放置在请求头中,后端解析 JWT 并判断令牌是否新鲜并有效

要点:

用户输入其登录信息

服务器验证信息是否正确,并返回已签名的token

token储在客户端,常见的是存储在local storage中,但也可以存储在session或cookie中

之后的HTTP请求都将token添加到请求头里

服务器解码JWT,并且如果令牌有效,则接受请求

一旦用户注销,令牌将在客户端被销毁,不需要与服务器进行交互一个关键是,令牌是无状态的。后端服务器不需要保存令牌或当前session的记录。

1. 基本介绍

首先,拥有某网站账号的某 client 使用自己的账号密码发送 post 请求 login,由于这是首次接触,server 会校验账号与密码是否合法,如果一致,则根据密钥生成一个 token 并返回,client 收到这个 token 并保存在本地的 localStorage。在这之后,需要访问一个受保护的路由或资源时,而只要附加上你保存在本地的 token(通常使用 Bearer 属性放在 Header 的 Authorization 属性中),server 会检查这个 token 是否仍有效,以及其中的校验信息是否正确,再做出相应的响应。

优点是自包含不需要服务端储存、无状态客户端销毁即可实现用户注销,以及跨域、易于实现CDN,比cookie更支持原生移动端应用

JWT 的三个部分:header头, payload载荷, signature签名,即:xxx.yyy.zzz

header部分(base64之前):

{

"alg": "SHA256", // algorithm 哈希算法主要有 HMAC、SHA256、RSA等等

"typ": "JWT" // type 令牌类型,应当设置为 JWT

}

payload部分(base64之前):

三种payload声明类型:registered, public, private,其中,registered 还包括 iss(issuer),sub(subject),aud(audience),exp(expiration time),nbf(not before),iat(issued at),jti(JWT ID)

{

"sub": "subject id",

"exp": "1300819380",

"role": "admin"

}

signature部分

如果使用 HMACSHA256 方式:

HMACSHA256(

base64UrlEncode(header) + "." +

base64UrlEncode(payload),

secret)

这三个部分之间加入.即完成了JWT的构造

需要注意,header部分和payload部分只是经过了base64的编码,并未加密,不能在载荷部分保存涉及安全的东西

JWT 令牌通常通过 HTTP 的 Authorization: Bearer 来传输,并存储在 session cookie, localStorage 等地方

2. 例子

login

 
  

getData

server:

const Koa = require('koa')

const static = require('koa-static')

const router = require('koa-better-router')().loadMethods()

const koaBody = require('koa-body')

const jwt = require('jsonwebtoken')

const fs = require('fs')

const app = new Koa()

app.listen(8080)

app.use(koaBody())

app.use(router.middleware())

app.use(static('public'))

app.keys = ['private key']

router.post('/login', (ctx, next) => {

// 省略从数据库中提取用户密码

if (ctx.request.body) {

if (ctx.request.body.username === 'fdsa', ctx.request.body.password === 'fdsa') {

// 生成 jwt token

let token = jwt.sign({ username: 'fdsa', role: 'admin' }, app.keys[0], { algorithm: 'HS256' })

ctx.cookies.set('koa:token', token)

ctx.body = { data: 1, token }

ctx.status = 200

} else {

ctx.body = { data: 0, err: 'error' }

ctx.status = 401

}

} else {

ctx.status = 401

}

next()

})

// 通过 session cookie 验证令牌

router.get('/admin', (ctx, next) => {

let token = ctx.cookies.get('koa:token')

if (token) {

// 验证 jwt 令牌

jwt.verify(token, app.keys[0], function (err, decoded) {

if (err) {

ctx.status = 401

console.log(err)

} else {

ctx.body = `welcome ${decoded.role}, ${decoded.username}`

ctx.type = 'text/html'

ctx.status = 200

}

});

} else {

ctx.status = 401

}

})

// 通过 Authorization 验证令牌

router.get('/secret.json', (ctx, next) => {

let token = ctx.get('Authorization').split(' ')[1]

if (token) {

jwt.verify(token, app.keys[0], function (err, decoded) {

if (err) {

ctx.status = 401

console.log(err)

} else {

if (decoded.role === 'admin') {

let msg = fs.readFileSync('./secret.json', 'utf-8')

ctx.body = { data: 1, msg }

ctx.status = 200

} else {

ctx.status = 401

}

}

})

} else {

ctx.status = 401

}

})

client:

require.config({

baseUrl: 'js/libs',

paths: {

'zepto': 'zepto.min',

},

shim: {

'zepto': 'zepto',

}

});

(在此忽略此前写的古老的 requireJS ?)

define(['zepto'], function ($) {

let $form = $('#form')

$form.on('submit', (e) => {

e.preventDefault()

$.ajax({

// ajax 发送验证请求

type: 'POST',

url: '/login',

headers: {

'Content-Type': 'application/x-www-form-urlencoded'

},

data: {

username: $('#username').val(),

password: $('#password').val()

},

success: function (data) {

if (data.data === 1) {

// 返回的token用于发起请求受限资源

window.localStorage.setItem('koa:token', data.token)

location.replace('./admin')

}

}

})

})

$('#getData').on('click', (e) => {

e.preventDefault()

$.ajax({

type: 'GET',

url: '/secret.json',

headers: {

'Content-Type': 'application/x-www-form-urlencoded',

'Authorization': 'Bearer ' + window.localStorage.getItem('koa:token')

// 客户端设置 Authorization Token 令牌

},

success: function (data) {

if (data.data === 1) {

// 令牌认证后的操作

$('#pre').text(JSON.parse(data.msg).key)

}

}

})

})

});

OAuth

OAuth 是目前用的最多的登录认证方式,用户首先确认授权登录,通过一连串方法获取 access token,最后通过 token 请求各种受限的资源

阮一峰老哥的文章清除讲解了这种方法的工作方式:

要点:

用户首先确认授权

再获取 code 临时凭证

通过 code 临时凭证,换取 access token

最后由 token 再获取受限的资源

下面封装了一个基于微博的 OAuth 认证:

let axios = require('axios');

const Koa = require('koa')

const static = require('koa-static')

const router = require('koa-better-router')().loadMethods()

const koaBody = require('koa-body')

const jwt = require('jsonwebtoken')

const fs = require('fs')

const app = new Koa()

app.listen(8080)

app.use(koaBody())

app.use(router.middleware())

app.use(static('public'))

app.keys = ['appid', 'secretid']

class WeiboApi {

// 获取 code 临时兑换券

constructor(query) {

this.code = query.code

}

// 根据 code 获取 token

getToken() {

return new Promise((resolve, reject) => {

axios({

method: 'POST',

url: `https://api.weibo.com/oauth2/access_token?client_id=${app.keys[0]}&client_secret=${app.keys[1]}&grant_type=authorization_code&redirect_uri=http://127.0.0.1:8080/auth&code=${this.code}`

}).then(d => { resolve(d) }).catch(e => { reject(e) })

})

}

// 根据 token 获取 相关的用户信息

getUserInfo(token) {

return new Promise((resolve, reject) => {

axios({

method: 'GET',

url: `https://api.weibo.com/2/users/show.json?access_token=${token.data.access_token}&uid=${token.data.uid}`

}).then(d => { resolve(d) }).catch(e => { reject(e) })

})

}

// 根据 token 获取 用户的关注人列表

getUserFriends(token) {

return new Promise((resolve, reject) => {

axios({

method: 'GET',

url: `https://api.weibo.com/2/friendships/friends.json?access_token=${token.data.access_token}&uid=${token.data.uid}`

}).then(d => { resolve(d) }).catch(e => { reject(e) })

})

}

}

router.get('/auth', async (ctx, next) => {

if (ctx.query.code) {

let weiboApi = new WeiboApi(ctx.request.query)

let token = await weiboApi.getToken()

let userInfo = await weiboApi.getUserInfo(token)

let userFriends = await weiboApi.getUserFriends(token)

// 根据用户信息,查询数据库,登录逻辑

ctx.body = { userInfo: userInfo.data, userFriends: userFriends.data }

} else {

ctx.status = 401

}

})

微博登录

1460000019853538?w=960&h=260

请关注我的订阅号,不定期推送有关 JS 的技术文章,只谈技术不谈八卦 ?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值