基于Oauth2.0协议授权码模式的认证流程
授权码模式工作流程:
- 浏览器访问某个受保护的资源,客户端自动将网页重定向到认证服务器(/oauth/authorize),携带clientid等信息
- 用户选择是否给予客户端授权(可自动授权)
- 认证服务器将浏览器重定向到”重定向URI”(redirection URI),同时附上一个授权码
- 浏览器拿到授权码,附上早先的”重定向URI”,向认证服务器申请令牌(/oauth/token)
- 认证服务器核对了授权码和重定向URI,向客户端发送访问令牌(access token)和更新令牌(refresh token)
步骤1中,客户端申请认证的URI,包含以下参数:
- response_type:表示授权类型,必选项,此处的值固定为”code”
- client_id:表示客户端的ID,必选项
- redirect_uri:表示重定向URI,可选项
- scope:表示申请的权限范围,可选项
- state:一般随机生成,标识客户端的当前状态,认证服务器会原样返回这个值,
通过JS ajax拦截器获取token及刷新token示例,适用于前后端分离项目中前端的授权。
auth.js
const FULL_CHARTER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopgrstuvwxyz';
const oauth_server='http://localhost:9000/server';
const redirect_uri='http://localhost:8002/client-front/';
const client_id='demo';
const client_secret='demo';
const token_storage = localStorage;//sessionStorage
function ajaxSetup() {
$.ajaxSetup({
timeout : 30000,
beforeSend : function(xhr) {
if(this.url.endsWith('/oauth/token')){
return true;
}
if (getAuth() == null){
fetchToken();
}
var auth = getAuth();
if(auth != null){
xhr.setRequestHeader("Authorization", auth.token_type + ' ' + auth.access_token);
} else {
return false;
}
return true;
},
complete : function(xhr, ts) {
if (xhr.status == 401 && xhr.responseJSON.error =='invalid_token') {
refreshToken();
}
}
});
}
function getAuth(){
let auth = token_storage.getItem('auth');
return JSON.parse(auth);
}
function saveAuth(sResponse){
token_storage.setItem("auth", JSON.stringify(sResponse));
}
function clearAuth(){
token_storage.removeItem('auth');
}
function logout(){
token_storage.removeItem('auth');
window.location.href = oauth_server+"/logout?redirect_uri="+redirect_uri;
}
function getCode(){
var state='';
for (var a=0;a<6;a++){
state+=FULL_CHARTER[Math.floor(Math.random() * 52)];
}
var url = oauth_server+"/oauth/authorize?client_id="+client_id+"&client_secret="+client_secret+
"&response_type=code&state="+state+"&redirect_uri="+redirect_uri;
window.location = url;
//window.open(url);
}
function fetchToken(){
let url = window.location.toString();
if(!url.includes('code')){
getCode();
}
if(url.includes('code')) {
let code=url.substr(url.indexOf('code=')+5,6);
let state=url.substr(url.indexOf('state=')+6,6);
var data={
'code':code,
'state':state,
'grant_type':'authorization_code',
'redirect_uri':redirect_uri
};
$.ajax({
url: oauth_server+"/oauth/token",
type:"post",
data:data,
async: false,
contentType: 'application/x-www-form-urlencoded',
beforeSend:function(xhr){
xhr.setRequestHeader("Authorization", 'Basic ' + Base64.encode(client_id+':'+client_secret));
},
success: function (sResponse) {
saveAuth(sResponse);
console.log('fetch_token ok: ' + sResponse.access_token+' expires_in:'+sResponse.expires_in);
//window.location.href = redirect_uri;
},
error:function(a,b,c){
console.log(a, b, c);
}
});
}
}
function refreshToken(){
var auth = getAuth();
var data={
'client_id': client_id,
'client_secret': client_secret,
'grant_type':'refresh_token',
'refresh_token':auth.refresh_token
};
$.ajax({
url: oauth_server+"/oauth/token",
type:"post",
data:data,
async: false,
contentType: 'application/x-www-form-urlencoded',
success: function (sResponse) {
saveAuth(sResponse);
console.log('refresh_token ok: ' + sResponse.access_token+' expires_in:'+sResponse.expires_in);
},
error:function(a,b,c){
if (a.status==400 && a.responseJSON.error=='invalid_grant'){
console.log('refresh token invalid');
clearAuth();
}
}
});
}
function checkToken(){
$.ajax({
url: oauth_server+"/oauth/check_token",
type:"get",
async: false,
data: {'token': getAuth().access_token},
contentType: 'application/x-www-form-urlencoded',
success: function (sResponse) {
console.log('check_token : ' + sResponse);
},
error:function(a,b,c){
console.log(a.responseJSON);
}
});
}
function getServerdata(){
$.get(oauth_server+"/msg", function(data) {
$("#user").html(data);
});
}
$(function() {
ajaxSetup();
});
其中使用了base64.js.
界面如下,点击GET Data按钮将发送/msg请求到授权服务器去获取一段文本:
文本获取成功后浏览器所有请求如下:
首先访问/msg,返回401无权限,然后浏览器转到/oauth/authorize去获取code,然后授权服务器返回302重定向到授权登录页,用户填写用户名密码后post到/login, 然后授权服务器重定向到rediret_url,末尾拼接了code,取出code,post发送/oauth/token请求,授权服务器返回token信息如下,并保存在LocalStorage中,如图所示:
获取token后/msg请求中携带了token信息,bearer为token类型:
由于是跨域请求数据,所以先发送的是OPTIONS请求,需要服务器支持跨域:
刷新token:
当服务器返回401,并且responseMsg为invalid_token时表示token已失效,post以下信息到/oauth/token,刷新token的失效时间
checktoken:需要服务器permitAll()该请求
登出:
略
在授权服务器中可查看所有生成的token:
--------------------------END--------------------------