016 refresh
This commit is contained in:
+123
-97
@@ -19,6 +19,7 @@
|
||||
<!--- SSO --->
|
||||
<cfset this.client_id = "payg-report.deck.nubes.ru"/>
|
||||
<cfset this.auth_endpoint = "https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/auth" />
|
||||
<cfset this.logout_endpoint = "https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/logout" />
|
||||
<cfset this.access_token_endpoint = "https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/token" />
|
||||
<cfset this.idpCertUrl = "https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/certs" />
|
||||
<cfset this.client_secret = createObject("java", "java.lang.System").getEnv("IDP_CLIENT_SECRET")/>
|
||||
@@ -74,7 +75,8 @@
|
||||
|
||||
<!--- global settings --->
|
||||
<cfset request.RECORDS_PER_PAGE=500/>
|
||||
<cfset request.APP_VERSION="0.00.015"/>
|
||||
<cfset request.APP_VERSION="0.00.016"/>
|
||||
<cfheader name="X-Application-Version" value=#request.APP_VERSION#/>
|
||||
<cfset request.STAND=getStand()/>
|
||||
|
||||
<!--- application constants --->
|
||||
@@ -138,6 +140,7 @@
|
||||
<cfset request.APP_NAME=this.Name/>
|
||||
<cfset idpCertificateExists = structKeyExists(application,"idpCertificate")/>
|
||||
</cflock>
|
||||
|
||||
<cfif NOT idpCertificateExists>
|
||||
<!--- Obtain IDP Certificate (onRequest) --->
|
||||
<cfset var idpCertificate = getIdpCertificate(this.idpCertUrl) />
|
||||
@@ -150,12 +153,16 @@
|
||||
|
||||
<cfset this.redirect_uri = "https://#CGI.SERVER_NAME##CGI.SCRIPT_NAME#" />
|
||||
|
||||
<cfinclude template="inc/functions.cfm"/>
|
||||
<cfif structKeyExists(url,"logout")>
|
||||
<cflock scope="session" type="exclusive" timeout="3">
|
||||
<cfset structDelete(session, "auth")/>
|
||||
</cflock>
|
||||
</cfif>
|
||||
|
||||
<cfcookie name="CFID" value="#session.CFID#"/>
|
||||
<cfcookie name="CFTOKEN" value="#session.CFTOKEN#"/>
|
||||
|
||||
|
||||
<cfinclude template="inc/functions.cfm"/>
|
||||
|
||||
<!--- SSO --->
|
||||
<!--- Если без сессии. Проверяем наличие токена в заголовке (и в куках?)
|
||||
@@ -171,7 +178,7 @@
|
||||
var requestData = GetHttpRequestData();
|
||||
var headers = requestData.headers;
|
||||
|
||||
//writedump(headers);
|
||||
//writedump(headers); flush;
|
||||
|
||||
if (structKeyExists(headers,"authorization")) {
|
||||
var authorizationHeader = headers.authorization;
|
||||
@@ -219,100 +226,88 @@
|
||||
}
|
||||
} else {
|
||||
//точно мы так не словим дедлок ВПР
|
||||
|
||||
lock scope="session" type="exclusive" timeout="3" {
|
||||
|
||||
//writedump(session);
|
||||
//writeoutput(getTickCount()-request.startTickCount);
|
||||
|
||||
//Это первая проба, с сессией
|
||||
//Когда протухнет сессия, мы можем пойти к IDP и повторно аутентифицироваться. Поскольку у нас сохраняются куки IDP, его сессия, возможно, будет жива. Если мы обновляли сессию IDP в другом приложении SSO, по всей видимости, она будет продолжаться. Если мы видим, что сессия IDP состарилась и хочет протухнуть, мы можем ее освежить и получить новый access token (который мы пока не используем - мы пользуемся ответом IDP) Наверное, правильно обходиться для этих целей вообще без сессии *** --->
|
||||
|
||||
//проверить sso
|
||||
//? сделать рефреш
|
||||
//? сделать логаут
|
||||
//? сделать отображение ошибки IDP
|
||||
//+ сделать получение сертификата
|
||||
|
||||
lock scope="session" type="readonly" timeout="3" {
|
||||
if (structKeyExists(session, "auth")) {
|
||||
request.auth = structCopy(session.auth);
|
||||
} else {
|
||||
//<!--- сессии нет --->
|
||||
session.auth_state=createGUID().toString()
|
||||
|
||||
|
||||
jwt = new lib.jwt(this.client_secret);
|
||||
idp = new lib.oauth2(this.client_id, this.client_secret, this.auth_endpoint, this.access_token_endpoint, this.redirect_uri);
|
||||
|
||||
/*
|
||||
часто появляется ошибка: после неудачного запроса к IDP
|
||||
в адресной строке браузера остается все, с чем его редиректил IDP,
|
||||
а повторный запрос с этим контентом уже не работает. Получаем 400 Bad Request -
|
||||
может быть, KeyCloak так защищается от циклических редиректов
|
||||
Не очень понятно, как предотвратить повторный запрос (можно, конечно, использовать сессию, но странно)
|
||||
*/
|
||||
//writeOutput(getTickCount()-request.startTickCount);
|
||||
if(structKeyExists(url, "code")) { //обрабатываем редирект от IDP *** наличие поля code в URL введет нас в заблуждение,
|
||||
//но эvar = data, label =то только без валидной сессии
|
||||
//dump(var = url.code, label = "URL.code");
|
||||
// Request access token from idp with the
|
||||
// authorization code that we got via the URL
|
||||
//aFormFields = [];
|
||||
//data = idp.makeAccessTokenRequest((url.code).content, aFormFields);
|
||||
try {
|
||||
writeOutput("idp.makeAccessTokenRequest");
|
||||
writeOutput(getTickCount()-request.startTickCount);
|
||||
resp = idp.makeAccessTokenRequest(url.code/*, aFormFields*/);
|
||||
writeOutput("idp.makeAccessTokenRequest Done");
|
||||
writeOutput(getTickCount()-request.startTickCount);
|
||||
//dump(resp);
|
||||
data = deserializeJson(resp.content);
|
||||
//writedump(data);
|
||||
// Print full response from idp
|
||||
//dump(var = data, label = "makeAccessTokenRequest - Response from idp");
|
||||
|
||||
//echo('<a href="./index.cfm?refresh=#data.refresh_token#">Refresh token #data.refresh_token#</a>');
|
||||
|
||||
session.auth.expires_in = data.expires_in;
|
||||
session.auth.refresh_expires_in = data.refresh_expires_in;
|
||||
session.auth.access_token = data.access_token;
|
||||
session.auth.refresh_token = data.refresh_token;
|
||||
session.auth.id_token = data.id_token;
|
||||
session.auth.token_type = data.token_type;
|
||||
|
||||
session.idp_response_content = structCopy(data);
|
||||
|
||||
//flush()
|
||||
lock scope="application" type="readonly" timeout="1" {
|
||||
token_data = (jwt.decode(data.access_token, application.idpCertificate,"RS256"));
|
||||
}
|
||||
writeOutput("jwt.decode Done");
|
||||
writeOutput(getTickCount()-request.startTickCount);
|
||||
writedump(token_data);
|
||||
session.auth.login = token_data.preferred_username;
|
||||
session.auth.wz = token_data.ClientID;
|
||||
session.auth.fullname = token_data.name;
|
||||
session.auth.groups = token_data.groups;
|
||||
session.auth.session_state = token_data.session_state;
|
||||
session.auth.sid = token_data.sid;
|
||||
session.auth.auth_time = token_data.auth_time;
|
||||
session.auth.exp = token_data.exp;
|
||||
session.auth.iat = token_data.iat;
|
||||
//session.sid = token_data.sid;
|
||||
session.token_data = structCopy(token_data);
|
||||
|
||||
location(this.redirect_uri,false);
|
||||
} catch (e) {echo('<br>****************** #e.message# : #e.detail# **********');}
|
||||
|
||||
} else {
|
||||
strURL = idp.buildRedirectToAuthURL({"scope":'openid profile email',"state":session.auth_state,"allow_signup":false});
|
||||
echo('<a href="#strURL#">self-made Auth link (idp) #strURL#</a> <br> <br>');
|
||||
location(strUrl,false);
|
||||
}
|
||||
|
||||
request.auth = structCopy(session.auth);
|
||||
}
|
||||
}
|
||||
if (structKeyExists(request, "auth") AND Now() < request.auth.exp) {
|
||||
|
||||
} // lock session exclusive
|
||||
if (dateDiff("s", request.auth.iat, Now()) > request.auth.refresh_expires_in/1.5 AND CGI.REQUEST_METHOD EQ "GET") {
|
||||
// доля времени, после которой происходит рефреш, прибита гвоздями
|
||||
// По всей видимости, рефреш будет ломать обращения POSТ
|
||||
// В связи с чем предлагается дождаться GET
|
||||
// Приклеивать query_string как-то не пришлось, браузер сам справляется
|
||||
|
||||
// refresh
|
||||
idp = new lib.oauth2(this.client_id, this.client_secret, this.auth_endpoint, this.access_token_endpoint, this.redirect_uri);
|
||||
resp = idp.refreshAccessTokenRequest(request.auth.refresh_token);
|
||||
|
||||
lock scope="application" type="readonly" timeout="1" {
|
||||
idpCertificate = application.idpCertificate;
|
||||
}
|
||||
|
||||
auth = parseIdpresponse(resp, idpCertificate, this.client_secret); //все эти сложности из-за опасений насчет многопоточности и блокировок
|
||||
|
||||
lock scope="session" type="exclusive" timeout="1" {
|
||||
session.auth = auth;
|
||||
}
|
||||
request.auth = auth;
|
||||
writeDump(auth);
|
||||
}
|
||||
} else {
|
||||
// сессии SSO нет
|
||||
// или токен протух
|
||||
request.auth_state = createGUID().toString();
|
||||
lock scope="session" type="exclusive" timeout="1" {
|
||||
structDelete(session, "auth"); // зачистили сессию от данных SSO
|
||||
session.auth_state = request.auth_state;
|
||||
}
|
||||
|
||||
idp = new lib.oauth2(this.client_id, this.client_secret, this.auth_endpoint, this.access_token_endpoint, this.redirect_uri);
|
||||
/*
|
||||
часто появляется ошибка: после неудачного запроса к IDP
|
||||
в адресной строке браузера остается все, с чем его редиректил IDP,
|
||||
а повторный запрос с этим контентом уже не работает. Получаем 400 Bad Request -
|
||||
может быть, KeyCloak так защищается от циклических редиректов
|
||||
Не очень понятно, как предотвратить повторный запрос (можно, конечно, использовать сессию, но странно)
|
||||
*/
|
||||
//writeOutput(getTickCount()-request.startTickCount);
|
||||
if(structKeyExists(url, "code")) { //обрабатываем редирект от IDP *** наличие поля code в URL введет нас в заблуждение,
|
||||
|
||||
try {
|
||||
//writeOutput("idp.makeAccessTokenRequest");
|
||||
//writeOutput(getTickCount()-request.startTickCount);
|
||||
resp = idp.makeAccessTokenRequest(url.code);
|
||||
|
||||
lock scope="application" type="readonly" timeout="1" {
|
||||
idpCertificate = application.idpCertificate;
|
||||
}
|
||||
|
||||
auth = parseIdpresponse(resp, idpCertificate, this.client_secret); //все эти сложности из-за опасений насчет многопоточности и блокировок
|
||||
|
||||
lock scope="session" type="exclusive" timeout="1" {
|
||||
session.auth = auth;
|
||||
}
|
||||
//request.auth = auth;
|
||||
location(this.redirect_uri, false);
|
||||
} catch (e) {echo('<br>****************** #e.message# : #e.detail# **************');}
|
||||
|
||||
} else { //отправляем браузер к IDP
|
||||
strURL =idp.buildRedirectToAuthURL({"scope":'openid profile email',"state":request.auth_state,"allow_signup":false});
|
||||
echo('<a href="#strURL#">self-made Auth link (idp) #strURL#</a> <br> <br>');
|
||||
location(strUrl,false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//request.logout_url = "";
|
||||
if (structKeyExists(request, "auth") AND structKeyExists(request.auth, "id_token")) {
|
||||
request.logout_url = "#this.logout_endpoint#?id_token_hint=#request.auth.id_token#&post_logout_redirect_uri=#this.redirect_uri#?logout";
|
||||
// внимание, здесь подразумевается, что redirect_uri не содержит query_string
|
||||
// после возвращения от IDP нам нужно будет зачистить сессию SSO, сделаем это по слову logout
|
||||
}
|
||||
</cfscript>
|
||||
|
||||
@@ -344,7 +339,7 @@
|
||||
</cfif> --->
|
||||
</cfif>
|
||||
|
||||
|
||||
|
||||
|
||||
<cfreturn />
|
||||
</cffunction>
|
||||
@@ -452,7 +447,7 @@
|
||||
//https://keycloak.nubes.ru/realms/cloud/.well-known/openid-configuration тут ссылка
|
||||
//https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/certs тут серт
|
||||
//только надо его обрамить -----BEGIN CERTIFICATE-----
|
||||
function getIdpCertificate(certUrl) {
|
||||
private function getIdpCertificate(certUrl) {
|
||||
|
||||
var httpService = new http();
|
||||
httpService.setMethod( "get" );
|
||||
@@ -475,6 +470,37 @@
|
||||
} else {
|
||||
throw (message="Cannot obtain IDP certificate, request failed", detail="Status_Code #status_code#");
|
||||
}
|
||||
}
|
||||
|
||||
private struct function parseIdpresponse(struct resp, string certificate, string jwt_secret) {
|
||||
|
||||
var auth = structNew("linked");
|
||||
var data = deserializeJson(arguments.resp.content);
|
||||
var jwt = new lib.jwt(arguments.jwt_secret);
|
||||
|
||||
auth.expires_in = data.expires_in;
|
||||
auth.refresh_expires_in = data.refresh_expires_in;
|
||||
auth.access_token = data.access_token;
|
||||
auth.refresh_token = data.refresh_token;
|
||||
auth.id_token = data.id_token;
|
||||
auth.token_type = data.token_type;
|
||||
|
||||
//idp_response_content = structCopy(data); //for debug
|
||||
var token_data = (jwt.decode(data.access_token, arguments.certificate,"RS256"));
|
||||
|
||||
auth.session_state = token_data.session_state;
|
||||
auth.sid = token_data.sid;
|
||||
auth.auth_time = token_data.auth_time;
|
||||
auth.exp = token_data.exp;
|
||||
auth.iat = token_data.iat;
|
||||
|
||||
auth.login = token_data.preferred_username;
|
||||
auth.wz = token_data.ClientID;
|
||||
auth.fullname = token_data.name;
|
||||
auth.groups = token_data.groups;
|
||||
//auth.token_data = structCopy(token_data); //for debug
|
||||
|
||||
return (auth);
|
||||
}
|
||||
|
||||
</cfscript>
|
||||
|
||||
Reference in New Issue
Block a user