瀏覽器訪問的url:
http://192.168.1.207:8887/ui/wls_portal.html?nasip=192.168.1.249 發生302
302 到http://192.168.1.207:8887/api/open/nps/v1/get_qr_portal_source?nasip=192.168.1.249&auth_type=employee_wireless 發生302
https://idp.pre.xxxxxxxxxx.cn/idp/app/login?app_id=xxxxx&ins_id=xxxxxxxxxxxxx5f84ba1e65e7&portal_type=xxxxxxxxxxx&redirect_url=http%3A%2F%2F192.168.1.207%3A8887%2Fapi%2Fopen%2Fnps%2Fv1%2Flogin%2Fverify_authtickets%3Fnasip%3D192.168.1.249%26auth_type%3Demployee_wireless
302 https://idp.pre.xxxxx.cn/ui/idp-auth.html?app_id=xxxxx&redirect_url=http%3A%2F%2F192.168.1.207%3A8887%2Fapi%2Fopen%2Fnps%2Fv1%2Flogin%2Fverify_authtickets%3Fnasip%3D192.168.1.249%26auth_type%3Demployee_wireless&portal_type=nac_portal&ins_id=sxxxxx
使用cookie 認證https://idp.xx.xxxxx.cn/idp/app/authenticate?app_id=xxxx&redirect_url=http%3A%2F%2F192.168.1.207%3A8887%2Fapi%2Fopen%2Fnps%2Fv1%2Flogin%2Fverify_authtickets%3Fnasip%3D192.168.1.249%26auth_type%3Demployee_wireless&portal_type=nac_portal&ins_id=xxxxx返回200ok (沒有認證過只有200 沒有重定向到提交賬密環節)
獲取身份源https://idp.pre.xxxxxx.cn/idp/app/get_iden_source_list?app_id=xxxxxx&redirect_url=http%3A%2F%2F192.168.1.207%3A8887%2Fapi%2Fopen%2Fnps%2Fv1%2Flogin%2Fverify_authtickets%3Fnasip%3D192.168.1.249%26auth_type%3Demployee_wireless&portal_type=xxxx&ins_id=xxxxxxxxx
選擇身份源出現二維碼;https://idp.xxxx.xxxxxxxx.cn/idp/app/login?app_id=xxxxxx&redirect_url=http%3A%2F%2F192.168.1.207%3A8887%2Fapi%2Fopen%2Fnps%2Fv1%2Flogin%2Fverify_authtickets%3Fnasip%3D192.168.1.249%26auth_type%3Demployee_wireless&portal_type=xxxx&ins_id=xxxxx&source_id=xxx此時傳送重定向302
302 到https://idp.pre.xxxx.cn/ui/nac-login.html?osType=windows&requestId=xxxxxx8&idp_type=4&appid=xxxxx&corp_code=&portal_type=xxxxx&redirect_uri=https%3A%2F%2Fidp.pre.xxx.cn%2FdingtalkLogin&authentication_error=
瀏覽器定時https://idp.pre.xxxxx.cn/idp/app/check_nac_auth?requestId=xxxxxxxx8&requestType=refresh
如果refresh 返回的data 資料為true則訪問https://idp.pre.xxxxx.cn/idp/app/check_nac_auth?requestId=xxxxxxxc8&requestType=xx否則訪問以前的requestType=refresh
掃碼成功後;https://idp.pre.xxxxx.cn/idp/app/check_nac_auth?requestId=xxxxx&requestType=submit 後會302
302到http://192.168.1.207:8887/api/open/nps/v1/login/verify_authtickets?nasip=192.168.1.249&auth_type=employee_wireless&xxxx_ticket=axxxxx
- 此時到管控置換秘鑰 根據ticket置換 掃描是idp 服務儲存的身份資訊使用者名稱密碼
- 拿到使用者名稱密碼後 發起標準的portal 認證
手機端掃碼邏輯:
掃碼後第一個訪問的是:
https://idp.pre.eagleyun.cn/ui/nac-qrcode-confirm.html?osType=windows&requestId=xxxxxx&idp_type=4&appid=xxxxx&corp_code=&portal_type=xxx&redirect_uri=https%3A%2F%2Fidp.pre.eagleyun.cn%xxxxxx&authentication_error=
https://login.dingtalk.com/oauth2/auth?redirect_uri=https://idp.pre.xxx.cn/dingtalkLogin&response_type=code&client_id=xxxx&scope=openid&state=sxxxxx&prompt=consent
https://login.dingtalk.com/oauth2/challenge.htm?redirect_uri=https://idp.pre.xxxxxx.cn/dingtalkLogin&response_type=code&client_id=xxxx&scope=openid&state=xxxxxx&prompt=consent
https://login.dingtalk.com/oauth2/confirm_auth
- https://idp.pre.eagleyun.cn/dingtalkLogin?code=xxxx&authCode=xxxxx&state=xxxxxxx
- https://idp.pre.eagleyun.cn/ui/loginSuccess.html
- 訪問/ui/loginSuccess.html 彈出成功頁面
idp-server中監聽dingtalkLogin ;並且根據code 獲取user 身份資訊
上述跟蹤資料的時候requestId 和 state 的值都是一樣的(擷取資料時 分兩次擷取的)
目前釘釘提供的頁面登入授權最新為https://open.dingtalk.com/document/orgapp/logon-free-third-party-websites
授權登入地址:
https://login.dingtalk.com/oauth2/auth?
redirect_uri=https%3A%2F%2Fwww.aaaaa.com%2Fauth
&response_type=code
&client_id=dingxxxxxxx //應用的AppKey
&scope=openid //此處的openId保持不變
&state=dddd
&prompt=consent
對應手機掃碼授權如下:
https://login.dingtalk.com/oauth2/auth?redirect_uri=https://idp.pre.xxxx.cn/dingtalkLogin&response_type=code&client_id=xxxx
&scope=openid&state=saml_idp_xxxxxx4c6&prompt=consent
//原始url為:
//https://login.dingtalk.com/oauth2/auth?redirect_uri=https://idp.pre.xxxx.cn/dingtalkLogin&response_type=code
//&client_id=xxxxx&scope=openid&state=saml_idp_xxxxxx&prompt=consent
https://login.dingtalk.com/oauth2/confirm_auth
post
//payload資料為:
redirect_uri: https://idp.pre.xxxx.cn/dingtalkLogin
response_type: code
client_id: xxxxxxxx
scope: openid
state: saml_idp_xxxxxxx
prompt: consent
corpId:
secondaryValidationResult:
pdmTitle: Windows Web
pdmModel: Windows
pdmToken: T2gAuk9w68QMeg5zmmi2e46Pun3D00T9BkAGb1uK5kDoPPqhJK3-bqNYJJm69pYlLcA=
正常的掃碼授權登入,
授權後:
https://xxxx/callback?code=fb6a88dc09e843b33f 釘釘透過此回撥通知伺服器 給一個臨時code;
伺服器透過臨時code ,POST 請求
獲取令牌 token/當獲取到授權碼 code 後,客戶端需要用它獲取訪問令牌:
// 獲取登入使用者的訪問憑證; 獲取使用者token; 獲取使用者資訊
dingTalkUserToken, err := GetDingTalkTokenByCode(idenSource.SyncUser, idenSource.SyncPwd, code)
if err != nil {
log.Errorf("[LoginWithDingtalkCode]authByDingtalk failed, code:%s, idenSource:%+v, err:%+v", code, idenSource, err)
return nil, err
}
var dingtalkGetAppAccessTokenResp dingtalkGetAppAccessTokenRespInfo
var dingtalkGetAppAccessTokenUrl = "https://oapi.dingtalk.com/gettoken"
err = gout.GET(dingtalkGetAppAccessTokenUrl).SetQuery(gout.H{
"appkey": idenSource.SyncUser,
"appsecret": idenSource.SyncPwd,
}).BindJSON(&dingtalkGetAppAccessTokenResp).SetTimeout(30 * time.Second).Do()
if err != nil {
log.Errorf("[LoginWithDingtalkCodeNac] appkey:%s, appsecret:%s, error %+v", idenSource.SyncUser, idenSource.SyncPwd, err)
return nil, errors.New(ErrAppSsoUserAuthForDingtalk)
}
userId, err := GetUserByCode(dingtalkGetAppAccessTokenResp.AccessToken, dingTalkUserToken)
// 掃碼免登Demo回撥,完整的訪問路徑:http://127.0.0.1:3000/callback-for-dingtalk
router.get('/callback-for-dingtalk', function(req, res, next) {
// Step 1:參考如下文件,獲取當前登入使用者的授權Token
// 文件:https://open.dingtalk.com/document/orgapp-server/obtain-user-token
DingtalkClient.request({
path:'/v1.0/oauth2/userAccessToken',
method:'POST',
body:{
clientId:'<這裡替換成你自己應用的App Key>',
clientSecret: '<這裡替換成你自己應用的App Secret>',
code:req.query.authCode,
grantType:'authorization_code'
}
}).then(data => {
// Step 2:參考如下文件,獲取當前登入使用者的個人資訊
// 文件:https://open.dingtalk.com/document/orgapp-server/dingtalk-retrieve-user-information
return DingtalkClient.request({
path:'/v1.0/contact/users/me',
headers: {
'x-acs-dingtalk-access-token': data.accessToken
}
});
}).then(data => {
// Step 3:根據獲取到的使用者授權資訊,做相關業務操作
res.setHeader("Content-Type", "application/json;charset=utf-8");
res.end(JSON.stringify({
message:'下面這些內容就是透過免登獲取到的個人詳細資訊了,如果當前已經有賬號登入了,可以直接獲取到資料',
data:data // 這裡就是拿到的結果了!!
},null,4));
});
});
你可能注意到了上門示例程式碼中的DingtalkClient.request ,不知道這是什麼,這實際上是釘釘OpenApi的簡單訪問工具,工具定義如下:
// 釘釘OpenApi的訪問工具
// 釘釘OpenApi的訪問工具
// github: https://github.com/zxlie/dingtalk-client
let DingtalkClient = {
/**
* 釘釘OpenAPI的統一呼叫入口
* @param {object} configs 請求配置
* @p-config {boolean} isOldApi 是否老版本介面,預設 false
* @p-config {string} method 請求方式,GET或者POST
* @p-config {string} path 介面地址,如:/v1.0/sso/getToken
* @p-config {object} params 需要在介面地址上進行URL拼接的引數
* @p-config {object} body 請求方式為POST時,指定提交的內容
* @p-config {object} headers 請求頭
* @p-config {function} callback 請求結果的處理回撥:function(error,data)
* @returns {promise} 返回一個Promise物件
**/
request : function(configs) {
var url = ((isOldApi, path, params) => {
let _hostConfig = {
topHost: 'https://oapi.dingtalk.com', // 老版API
popHost: 'https://api.dingtalk.com' // 新版API
};
let oUrl = new URL((isOldApi ? _hostConfig.topHost : _hostConfig.popHost) + path);
if (Object.keys(params || {}).length > 0) {
Object.keys(params, key => oUrl.searchParams.append(key,params[key]));
}
return oUrl.toString();
})(configs.isOldApi, configs.path, configs.params);
var obj = {
method: (configs.method || 'GET').toUpperCase(),
url: url,
json: true
};
if(obj.method === 'POST' && configs.body) {
obj.body = configs.body;
}
if(configs.headers) {
obj.headers = configs.headers;
}
return new Promise((resolve,reject) => {
request(obj, (err, response, body) => {
resolve(body);
});
});
}
};
也就是伺服器可以拿到此時一些資料
此時瀏覽器定時的向伺服器查詢是否授權成功; 授權成功會返回ticket給前端;
前端將ticket給准入portal server,
portal server 透過ac radius 到管控走認證流程。最後管控拿ticket呼叫查詢介面獲取使用者資訊去過策略;獲取使用者資訊的介面:idpHost:/idp/app/get_user_info
過策略成功 給radius返回200ok---->radius-----ac----portal-server-顯示成功
部分參考:https://help.aliyun.com/practice_detail/419774