016 refresh

This commit is contained in:
2025-10-22 17:19:16 +03:00
parent 3e3fa0f7ca
commit 3e89a13883
3 changed files with 134 additions and 101 deletions
+10 -4
View File
@@ -1,9 +1,15 @@
<hr/> <hr/>
t test test
<hr/> <hr/>
<cftry> <cfflush/>
<!--- <cftry>
<cfdump var=#session#/> <cfdump var=#session#/>
<cfcatch type="any"><cfdump var=#cfcatch#/></cfcatch> <cfcatch type="any"><cfdump var=#cfcatch#/></cfcatch>
</cftry> </cftry> --->
<cfset jwt = new lib.jwt(this.client_secret)/>
<cfif structKeyExists(request, "logout_url")>
<cfoutput>
<a href="#request.logout_url#">Log Out: #request.logout_url#</a>
</cfoutput>
</cfif>
+115 -89
View File
@@ -19,6 +19,7 @@
<!--- SSO ---> <!--- SSO --->
<cfset this.client_id = "payg-report.deck.nubes.ru"/> <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.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.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.idpCertUrl = "https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/certs" />
<cfset this.client_secret = createObject("java", "java.lang.System").getEnv("IDP_CLIENT_SECRET")/> <cfset this.client_secret = createObject("java", "java.lang.System").getEnv("IDP_CLIENT_SECRET")/>
@@ -74,7 +75,8 @@
<!--- global settings ---> <!--- global settings --->
<cfset request.RECORDS_PER_PAGE=500/> <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()/> <cfset request.STAND=getStand()/>
<!--- application constants ---> <!--- application constants --->
@@ -138,6 +140,7 @@
<cfset request.APP_NAME=this.Name/> <cfset request.APP_NAME=this.Name/>
<cfset idpCertificateExists = structKeyExists(application,"idpCertificate")/> <cfset idpCertificateExists = structKeyExists(application,"idpCertificate")/>
</cflock> </cflock>
<cfif NOT idpCertificateExists> <cfif NOT idpCertificateExists>
<!--- Obtain IDP Certificate (onRequest) ---> <!--- Obtain IDP Certificate (onRequest) --->
<cfset var idpCertificate = getIdpCertificate(this.idpCertUrl) /> <cfset var idpCertificate = getIdpCertificate(this.idpCertUrl) />
@@ -150,12 +153,16 @@
<cfset this.redirect_uri = "https://#CGI.SERVER_NAME##CGI.SCRIPT_NAME#" /> <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="CFID" value="#session.CFID#"/>
<cfcookie name="CFTOKEN" value="#session.CFTOKEN#"/> <cfcookie name="CFTOKEN" value="#session.CFTOKEN#"/>
<cfinclude template="inc/functions.cfm"/>
<!--- SSO ---> <!--- SSO --->
<!--- Если без сессии. Проверяем наличие токена в заголовке (и в куках?) <!--- Если без сессии. Проверяем наличие токена в заголовке (и в куках?)
@@ -171,7 +178,7 @@
var requestData = GetHttpRequestData(); var requestData = GetHttpRequestData();
var headers = requestData.headers; var headers = requestData.headers;
//writedump(headers); //writedump(headers); flush;
if (structKeyExists(headers,"authorization")) { if (structKeyExists(headers,"authorization")) {
var authorizationHeader = headers.authorization; var authorizationHeader = headers.authorization;
@@ -219,100 +226,88 @@
} }
} else { } else {
//точно мы так не словим дедлок ВПР //точно мы так не словим дедлок ВПР
lock scope="session" type="readonly" timeout="3" {
lock scope="session" type="exclusive" timeout="3" {
//writedump(session);
//writeoutput(getTickCount()-request.startTickCount);
//Это первая проба, с сессией
//Когда протухнет сессия, мы можем пойти к IDP и повторно аутентифицироваться. Поскольку у нас сохраняются куки IDP, его сессия, возможно, будет жива. Если мы обновляли сессию IDP в другом приложении SSO, по всей видимости, она будет продолжаться. Если мы видим, что сессия IDP состарилась и хочет протухнуть, мы можем ее освежить и получить новый access token (который мы пока не используем - мы пользуемся ответом IDP) Наверное, правильно обходиться для этих целей вообще без сессии *** --->
//проверить sso
//? сделать рефреш
//? сделать логаут
//? сделать отображение ошибки IDP
//+ сделать получение сертификата
if (structKeyExists(session, "auth")) { if (structKeyExists(session, "auth")) {
request.auth = structCopy(session.auth); request.auth = structCopy(session.auth);
} else { }
//<!--- сессии нет ---> }
session.auth_state=createGUID().toString() if (structKeyExists(request, "auth") AND Now() < request.auth.exp) {
if (dateDiff("s", request.auth.iat, Now()) > request.auth.refresh_expires_in/1.5 AND CGI.REQUEST_METHOD EQ "GET") {
// доля времени, после которой происходит рефреш, прибита гвоздями
// По всей видимости, рефреш будет ломать обращения POSТ
// В связи с чем предлагается дождаться GET
// Приклеивать query_string как-то не пришлось, браузер сам справляется
jwt = new lib.jwt(this.client_secret); // refresh
idp = new lib.oauth2(this.client_id, this.client_secret, this.auth_endpoint, this.access_token_endpoint, this.redirect_uri); 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" {
часто появляется ошибка: после неудачного запроса к IDP idpCertificate = application.idpCertificate;
в адресной строке браузера остается все, с чем его редиректил 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);
} }
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;
} }
} // lock session exclusive 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> </cfscript>
@@ -452,7 +447,7 @@
//https://keycloak.nubes.ru/realms/cloud/.well-known/openid-configuration тут ссылка //https://keycloak.nubes.ru/realms/cloud/.well-known/openid-configuration тут ссылка
//https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/certs тут серт //https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/certs тут серт
//только надо его обрамить -----BEGIN CERTIFICATE----- //только надо его обрамить -----BEGIN CERTIFICATE-----
function getIdpCertificate(certUrl) { private function getIdpCertificate(certUrl) {
var httpService = new http(); var httpService = new http();
httpService.setMethod( "get" ); httpService.setMethod( "get" );
@@ -477,6 +472,37 @@
} }
} }
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> </cfscript>
<cffunction name="rethrow" returntype="void"> <cffunction name="rethrow" returntype="void">
+1
View File
@@ -69,6 +69,7 @@
debug=#isDefined("DEBUG")# debug=#isDefined("DEBUG")#
/> />
<cfset out={ <cfset out={
"appVersion"=#request.APP_VERSION#,
"hours"=#hours#, "hours"=#hours#,
"dtStart="=#iso8601dtFormat(dt_start)#, "dtStart="=#iso8601dtFormat(dt_start)#,
"dtFinish"=#iso8601dtFormat(dt_finish)#, "dtFinish"=#iso8601dtFormat(dt_finish)#,