在该链接中”OAuth2.0授权协议详细版,阮一峰老师的文章中已经写得比较详细了,

OAuth授权协议主要为了限制其他网站对自己资源服务器的调用,做到可控性,特定网站只开放特定的权限,所以以前是C<->S,现在加入了一层Auth认证层,C<->Auth<->S。

以下是文章的授权模式的简要流程,放上一张另外一个博客比较经典的豆瓣调用QQ授权服务器的通信时序图。

 

(A)用户豆瓣,点击qq登陆,豆瓣让用户浏览器携带豆瓣的回调地址去访问qq的授权页面。

(B)用户成功登陆后,认证页面询问授予哪些权限给豆瓣,勾选好后,点击登陆,qq认证服务器根据豆瓣的回调地址,携带code参数进行跳转。

(C)用户浏览器跳转到回调页面后,豆瓣系统再携带code对qq认证服务器发起获取token请求。

(D)qq认证服务器确定code有效,然后生成token值[或者放在B步骤的时候生成],返回给豆瓣系统。

(E)之后豆瓣携带token去qq资源服务器获得用户信息。

 

关于授权模式文字就讲得差不多了,接下来我们就开始一步一步来实现这个认证吧。

1. 新开一个项目文件夹OAuth2,里面放入两个站点文件夹site_1与site_2

文档结构:

–OAuth2

—-site_1

—-site_2

模拟的就是两个不同的站点。

2. site_1中新建一个登陆页site_1/index.html

<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>豆瓣</title>
</head>
<body>
    <div style="padding: 5px; border:2px solid #000; width:230px;">
        <h2>豆瓣登陆</h2>
        <label>用户名<input type="text"/></label>
        <label>密码<input type="text"/></label>
        <br>
        <a href="#" onclick="login()">QQ登陆</a>
        <script>
            // site_2的登录页
            var url="http://localhost/sso/OAuth2/site_2/auth.php?callback=";
            var login=function () {
                // 回调url地址
                var site_1_callback='http://localhost/sso/OAuth2/site_1/callback.php';
                // 因为携带的参数是url,将敏感字符 / 编码一下
                var decode=encodeURIComponent(site_1_callback);
                // 重定向到site_2的地址
                window.location.href=url+decode;
            }
        </script>
    </div>
</body>
</html>

预览:

3. site_2中新建一个登录页site_2/auth.php

<?php
// 第三方登录页
// 获取回调地址,加入到下面的callback隐藏表单字段中去
$callback=urldecode($_GET['callback']);
?>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>QQ</title>
</head>
<body>
<form action="login.php"  method="post" style="padding: 5px; border:2px solid #000; width:230px;">
    <h2>QQ登陆</h2>
    <label>用户名<input name="account" type="text" value="123"/></label>
    <label>密码<input name="password" type="text" value="123"/></label>
    <label><input hidden name="callback" value="<?php echo $callback;?>" ></label>
    <button type="submit" >登陆</button>
    <br>
</form>
</body>
</html>

预览:

4. site_2中新建一个认证中心site_2/login.php

<?php

// redis键名
const REDIS_TABLE='auth:test';

// 获取login.php传来的字段
$acc= $_POST['account'];
$password= $_POST['password'];
$callback= $_POST['callback'];


$errcode=1;
//一次性凭证,换取token
$code='';
//账号与密码是否正确
if($acc==='123'&&$password==='123'){
    $redis=new Redis();
    $redis->connect('localhost',6379);
    //生成code。生产环境中,应对code与token进行对称加密,这里就省略了。code的格式是[4位随机+当前时间戳+4位随机]
    $code=strGenerator(4).time().strGenerator(4);
    //我这里就先生成token了,对应上面B步骤,token的格式是[4位随机+当前时间戳+';'+账号+';'+4位随机]
    $token=strGenerator(4).time().';'.$acc.';'.strGenerator(4);
    //将code:token的键值对形式放入redis中
    $redis->hSet(REDIS_TABLE,$code,$token);
    $errcode=0;
}

//拼凑url
$callback.='?errcode='.$errcode.'&code='.$code;
//携带code请求回调地址,这里为了方便直接用get了
header('HTTP/1.1 302 Found');
header('Location: '.$callback);

function strGenerator($len = 0){
    $tmp='';
    $strlist='zxcvbnmasdfghjklqwertyuiopZXCVBNMASDFGHJKLQWERTYUIOP1234567890';
    for ($i=0;$i<$len;$i++)
        $tmp.=$strlist[mt_rand(0,61)];
    return $tmp;
}

这里认证完后用户浏览器转到回调地址

5. site_1中新建一个回调地址site_1/callback.php

<?php

// 若返回的errcode为0,则说明用户账号与密码匹配成功
if($_GET['errcode']=='0'){
    //拿code获取第三方凭证token
    $authResult=json_decode( file_get_contents('http://localhost/sso/OAuth2/site_2/getToken.php?code='.$_GET['code']));
    //因为定义返回的是json,json_deocde后就变成了对象,获取对象中的errocode,判断是否获取token成功
    if($authResult->errcode==0){
        $result='认证成功';
        //获取成功后,我们就能往site_2里面拿我们可以拿的数据了。
        $userInfo=json_decode( file_get_contents("http://localhost/sso/OAuth2/site_2/userInfo.php?token={$authResult->token}"));
        if(isset($userInfo)){
            $status=$userInfo->errcode;
            $userInfo=$userInfo->data;
        }
    }else
        $result='认证失败';
}else{
    $result='登陆失败';
}
?>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>豆瓣</title>
</head>
<body>
<div style="padding: 5px; border:2px solid #000; width:230px;">
    <?php echo $result;?>
    <br>
    <h2>用户信息</h2>
    <?php if(isset($status)){?>
    用户名:<?php echo $userInfo->name;?>
    地址:<?php echo $userInfo->addr;?>
    <?php }?>
</div>
</body>
</html>

预览放在最后,看完下面的获取token与获取资源代码

6. site_2中新建一个获取token地址site_2/getToken.php

<?php

const REDIS_TABLE='auth:test';
const REDIS_SET_TABLE='auth:set';
$redis=new Redis();
$redis->connect('localhost',6379);
//获取第5中的code
$code=$_GET['code'];

$data=array(
    'errcode'=>1
);
if($token=$redis->hGet(REDIS_TABLE,$code)){
    $data['errcode']=0;
    //若hash中有该键名,返回token键值
    $data['token']=$token;
    //清除该键值对
    $redis->hDel(REDIS_TABLE,$code);
    //将token加入认证通过集合中去
    $redis->sAdd(REDIS_SET_TABLE,$token);
}
echo  json_encode($data);

7. site_2中新建一个获取用户信息地址site_2/userInfo.php

<?php
//可换成其他方式

const REDIS_SET_TABLE='auth:set';
$redis=new Redis();
$redis->connect('localhost',6379);

// 模拟数据库
$mysql=[
    '123'=>array(
        'name'=>'幺九',
        'addr'=>'www.bayaojiu.com'
    )
];

$token=$_GET['token'];
$data=array('errcode'=>1);
if(!empty($token)&&$redis->sIsMember(REDIS_SET_TABLE,$token)){
    //分割token串
    $arr=explode(';',$token);
    //查询模拟数据库,返回数据
    $data['data']=$mysql[$arr[1]];
}
echo  json_encode($data);

最后的结果就是这样的:

此时的文档结构是:

 

生产环境中,往往会加多许多流程,比如加解密,seesion、redis与mysql搭配、审查网关、权限变动之类的。

对称与非对称加密这里有介绍哦,

👉对称加密

👉非对称加密

参考文档

https://www.cnblogs.com/flashsun/p/7424071.html

您或许感兴趣

[2018-08-20]egg 生产环境下日志文件输出两份问题
[2018-08-27]五分钟使用EasyWechat

发表评论

电子邮件地址不会被公开。