- gitee开发文档: https://gitee.com/api/v5/oauth_doc#/list-item-1
- gitee官网地址: https://gitee.com/
OAuth2 认证基本流程
创建应用流程
1.创建应用
2.填写资料
3.获取到ClientId
以及Client Secret
ClientId
和Client Sercret
的主要作用是通过拼接得到请求地址,将地址重定向至授权登录页面。
OAuth2 获取AccessToken——授权码模式
授权码(authorization code)方式,指的是第三方应用先申请一个授权码,然后再用该码获取令牌。
第一步 更新数据库的信息
- 目的: 我们需要在Player表中添加
giteeid
列,相当于AcWing的openid
。 - 作用: 作为唯一标识该用户的信息,如果通过可变信息(如:用户名)进行标记,那么我们在第三方平台上修改用户名时,就会重新创建一个用户,导致数据库存储无效的信息(因为后台逻辑判定重新需要创建一个用户导致数据冗余)。
- 缺点: 因为我们不能判断多个第三方的授权登录是否为一个用户,故会出现冗余现象。这涉及到数据库表的设计和第三方平台信息的获取。比如:阿里的第三方登录大部分都是自己旗下的产品,故有权限使用用户信息,那就比较好办了,可以用身份证、电话号码等进行识别是否为同一个用户。
#不知道码云的id的最大长度是多少,不过50应该够用。
giteeid = models.CharField(default="", max_length=50, blank=True, null=True)
#更新数据库的两条命令。
python3 manage.py makemigrations
python3 manage.py migrate
第二步 申请授权码code
- 从官方角度来说,应用通过 浏览器 或者 Webview 将用户引导到码云第三方认证页面上(GET请求)
- 从开放者角度来说,用户通过前端向后端发起请求,后端重定向到第三方服务器。然后用户在第三方平台验证登录授权后,返回地址并携带code参数。
- 理想情况是这样的。万一黑客知道了你的
receive_code
函数的路由并添加相应的参数请求给你的后端,这样就导致你的服务遭受一定地破坏。
那么如何避免csrf攻击呢?
我们同第三方平台约定一个暗号(state
),用户发送请求时,生成一个8位数的暗号,然后作为参数发送到第三方平台授权之后,第三方平台会原封不动的发送给你,然后你只需要判断state
跟你缓存中的state
是否相同,如果不同,后续逻辑看你自己只要不进行获取令牌操作即可。(造成不同的原因有黑客攻击、用户在授权页面停留时间过长导致state
过期等)
- 码云的请求地址:
https://gitee.com/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code
- 请求方式: GET
- 这是官方文档给的地址,但是为了安全性考虑,我们传递参数时添加
state
参数。 - 客户端传
state
参数:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。 - 认证服务器返回
state
参数:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。
参数说明
参数 | 是否必须 | 说明 |
---|---|---|
client_id | 是 | 码云应用的唯一id,可以在gitee编辑应用的界面里看到 |
redirect_uri | 是 | 回调地址,就是创建应用时填写的回调地址 |
response_type | 是 | 请求类型,表示要求返回授权码(code) |
state | 否 | 与第三方面军的暗号。用于判断请求和回调的一致性,授权成功后后原样返回。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数 |
返回说明
用户同意授权后会重定向到redirect_uri
,返回参数为code
和state
。链接格式如下:redirect_uri?code=CODE&state=STATE
。
如果用户拒绝授权,根据后台逻辑进行判断,通常两种情况重定向和不发生冲向。返回链接格式如下:https://app260.acapp.acwing.com.cn/game/settings/oauth/gitee/receive_code/?error=access_denied&error_description=用户或服务器拒绝了请求&state=49300298
第三步 申请授权令牌access_token
- 从官方角度来说,应用服务器 或 Webview 使用
access_token API
向 码云认证服务器发送post请求传入 用户授权码 以及 回调地址( POST请求 ) - 从开发者角度来说,我们现在在服务器的后端中,我们发送请求根据
code
请求第三方服务器进行校验,码云认证服务器返回access_token
。 - 然后后端通过
access_token
访问Open API使用用户数据。 - 码云请求地址:
https://gitee.com/oauth/token?grant_type=authorization_code&code={code}&client_id={client_id}&redirect_uri={redirect_uri}&client_secret={client_secret}
- 请求方式: POST
参数说明
参数 | 是否必须 | 说明 |
---|---|---|
grant_type | 是 | grant_type 参数的值是authorization_code |
code | 是 | code 参数是上一步拿到的授权码 |
client_id | 是 | 看下方 |
redirect_uri | 是 | redirect_uri 参数是令牌颁发后的回调网址。 |
client_secret | 是 | 看下方 |
- client_id参数和client_secret参数用来让 第三方平台 确认 我们的服务器 的身份(
client_secret
参数是保密的,因此只能在后端发请求)
返回说明
返回参数access_token
授权令牌,有效期1天,后端通过 access_token
访问 Open API 使用用户数据。
申请成功示例:
{
'access_token': '456a1d2d90b915750bec8562168a8',
'token_type': 'bearer',
'expires_in': 86400,
'refresh_token': 'ba8404d006ff744d445d4a0482ad88996b91c72d2ef0b7ef06550bd8e54bfc',
'scope': 'user_info',
'created_at': 16388549
}
申请失败示例:
#不知道,输出不出来。
第四步 申请用户信息
首先学会使用Open API。
- 码云的请求地址:
https://gitee.com/api/v5/user/?access_token={access_token}
- 请求方式: GET
返回说明
#这里列出来一些常用的名称。因为json格式:名称/值 进行存储
{
"id": #gitee
"login": "" #登录时用户名
"name": "" #登录后的用户名
"avatar_url": "" #头像
"email": null #邮箱
}
django实现码云第三方登录代码
- 创建两个函数
request_code()
和receive_code()
。 - 添加两个函数的路由(路由相信大家常写,我这里就不列出来了)
- 实现前端代码。
添加logo和css样式,然后将对应class抠出来,然后添加监听函数和实现ajax发起请求。
前端代码
gitee_login(){
$.ajax({
url: "https://app260.acapp.acwing.com.cn/game/settings/oauth/gitee/request_code/",
type: "GET",
success: function(resp){
console.log(resp);
if (resp.result === "success"){
window.location.replace(resp.request_code_url);
}
}
});
}
首先,我们先实现request_code()
函数。
功能:用户通过前端向我们创建的路由发起请求,然后通过路由找到该函数并执行该函数。
touch __init__.py
vim request_code.py
----------------------------------------------------
from django.http import JsonResponse
from urllib.parse import quote
from random import randint
from django.core.cache import cache
def get_state():
res = ""
for i in range(8):
res += str(randint(0, 9))
return res
def request_code(request):
client_id = "创建码云后的应用id"
redirect_uri = "receive_code的路由"
state = get_state()
cache.set(state, True, 7200) #有效期是2小时。
request_code_url = "https://gitee.com/oauth/authorize/"
return JsonResponse({
'result': "success",
'request_code_url': request_code_url + "?client_id=%s&redirect_uri=%s&response_type=code&state=%s" % (client_id, redirect_uri, state)
})
----------------------------------------------------
然后,我们再实现receive_code请求
功能:作为回调地址对应的函数。用于处理第三方平台传递的参数、获取令牌、以及获取参数的处理逻辑。
touch receive_code
vim receive_code
----------------------------------------------------
from django.shortcuts import redirect
from django.core.cache import cache
import requests
from django.contrib.auth.models import User
from game.models.player.player import Player
from django.contrib.auth import login
from random import randint
def receive_code(request):
data = request.GET
code = data.get('code', '')
state = data.get('state', '')
#如果请求失败,code拿不到值,就用空字符串代替,故用户拒绝后也能重定向到回调地址处。
if not cache.has_key(state) or code == '':
return redirect("index")
cache.delete(state)
request_access_token_url = "https://gitee.com/oauth/token/"
params = {
'grant_type': "authorization_code",
'code': code,
'client_id': "",
'redirect_uri': "",
'client_secret': ""
}
#注意码云这里是通过Post方式进行请求。
access_token_res = requests.post(request_access_token_url, params=params).json()
print(access_token_res)
access_token = access_token_res['access_token']
get_userinfo_url = "https://gitee.com/api/v5/user/"
params = {
'access_token': access_token,
}
message = requests.get(get_userinfo_url, params=params)
userinfo_res = message.json()
giteeid = userinfo_res.get('id')
username = userinfo_res.get('name')
photo = userinfo_res.get('avatar_url')
#这里判断用户之前是否授权注册过,防止数据冗余
players = Player.objects.filter(giteeid=giteeid)
if players.exists():
#每次授权并将头像更新一遍。
Player.objects.filter(giteeid=giteeid).update(photo=photo)
login(request, players[0].user)
return redirect("index")
while User.objects.filter(username=username).exists():
username += str(randint(0, 9))
user = User.objects.create(username=username)
player = Player.objects.create(user=user, photo=photo, giteeid=giteeid)
login(request, user)
return redirect("index")
----------------------------------------------------
优质小作文
🧐