016 refresh
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
+123
-97
@@ -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()
|
|
||||||
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
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>
|
</cfscript>
|
||||||
|
|
||||||
@@ -344,7 +339,7 @@
|
|||||||
</cfif> --->
|
</cfif> --->
|
||||||
</cfif>
|
</cfif>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<cfreturn />
|
<cfreturn />
|
||||||
</cffunction>
|
</cffunction>
|
||||||
@@ -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" );
|
||||||
@@ -475,6 +470,37 @@
|
|||||||
} else {
|
} else {
|
||||||
throw (message="Cannot obtain IDP certificate, request failed", detail="Status_Code #status_code#");
|
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>
|
</cfscript>
|
||||||
|
|||||||
@@ -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)#,
|
||||||
|
|||||||
Reference in New Issue
Block a user