Добро пожаловать в мой третий и последний пост в блоге о ColdFusion и OAuth. (Вы можете найти более ранние записи ниже.) Я извиняюсь за задержку, но я путешествовал на прошлой неделе, поэтому я немного отстал. Если вы еще не прочитали предыдущие записи ( часть 1 и часть 2 ), сделайте это, поскольку я не буду повторять информацию, о которой писал ранее.
Итак — надеюсь, вы уже поняли, насколько простым может быть OAuth. После того, как у меня все заработало в первый раз, второе демо оказалось довольно простым. Для этой демонстрации я решил немного повеселиться. У Google есть OAuth API, который позволяет вам проходить аутентификацию на своих серверах. Что, если вы хотите использовать Google для своей пользовательской системы? Другими словами, пропустите весь пользовательский процесс регистрации и перенесите управление пользователями в Google. Это не только возможно, но и является одним из рекомендуемых вариантов использования в их документации .
Прежде чем мы начнем, обратите внимание, что вам необходимо зарегистрировать свое приложение в Google. Это в точности как то, что вы делали с Facebook и LinkedIn, за исключением того, что вместо этого это делается в консоли API Google . К настоящему времени этот процесс должен быть достаточно легко , где мне не нужно , чтобы объяснить вам, только не забудьте сделать примечание идентификатора клиента и секрет клиента .
Поскольку наше приложение будет использовать Google для входа в систему, я создал простой Application.cfc, который ищет переменную сеанса и, если она не существует, автоматически подталкивает пользователя на страницу входа.
component {
this.name="googleoauthlogin4";
this.sessionManagement=true;
public boolean function onApplicationStart() {
application.clientid="noyb";
application.clientsecret="stillnoyb";
application.callback="http://localhost/testingzone/googleoauthlogin/callback.cfm";
application.google = new google(application.clientid, application.clientsecret);
return true;
}
public boolean function onRequestStart(required string req) {
if(!findNoCase("/login.cfm", arguments.req) && !findNoCase("/callback.cfm", arguments.req) && !session.loggedin) {
location(url="./login.cfm", addToken="false");
}
return true;
}
public boolean function onSessionStart() {
session.loggedin = false;
return true;
}
}
OnApplicationStart является практически точной копией предыдущих примеров, но onRequestStart является новым. Он проверяет, вошли ли мы в систему, и запрещает это, проверяет, запрашиваем ли мы страницу входа или страницу обратного вызова. Когда пользователь впервые нажимает на приложение, они отправляются на страницу входа:
Обратите внимание, что здесь нет формы. Помните, что вместо этого мы отправляем пользователя в Google. Я мог бы автоматически толкнуть их, но я чувствовал, что это было более дружелюбно. Вот код для этого шаблона.
<cfset authurl = application.google.generateAuthUrl(application.callback, session.urltoken)> <h1>Login Required</h1> <p> In order to use this app, you must login with your Google account. Click to login below. </p> <cfoutput> <p> <a href="#authurl#">LOGIN!</a> </p> </cfoutput>
Я поместил код Google OAuth в CFC, чтобы немного абстрагироваться, но пока не беспокойтесь об этом. Генерация ссылок очень похожа на предыдущие два примера. Как только пользователь нажимает кнопку входа, они отправляются в Google. В этом случае Google распознал мою учетную запись и предварительно установил логин, но у меня также есть возможность переключать пользователей.
Как и прежде, пользователь отправляется на страницу обратного вызова. Вот этот шаблон. Еще раз обратите внимание, что я вложил гораздо больше в CFC, так что это несколько проще.
<!--- Validate the result ---> <cfparam name="url.code" default=""> <cfparam name="url.state" default=""> <cfparam name="url.error" default=""> <cfset result = application.google.validateResult(url.code, url.error, url.state, session.urltoken)> <cfif not result.status> <!--- Imagine a nicer error here. ---> <cfoutput> <h1>Error!</h1> #result.message# </cfoutput> <cfabort> </cfif> <cfset session.token = result.token> <cfset session.loggedin = true> <cflocation url="index.cfm" addtoken="false">
Наконец, пользователь направляется на домашнюю страницу. Как часть Google API, я могу получить информацию о пользователе. Я так и сделал и выкинул это:
Вот этот шаблон:
<cfset me = application.google.getProfile(session.token.access_token)> <h1>Home</h1> <cfdump var="#me#" label="me">
Теперь давайте посмотрим на CFC:
component {
public function init(clientid, clientsecret) {
variables.clientid = arguments.clientid;
variables.clientsecret = arguments.clientsecret;
return this;
}
public string function generateAuthURL(redirecturl, state) {
/*
Scope is what you want to do with your access. Since this demo is ONLY for
auth and user info, we have one hard coded value.
*/
return "https://accounts.google.com/o/oauth2/auth?" &
"client_id=#urlEncodedFormat(variables.clientid)#" &
"&redirect_uri=#urlEncodedFormat(arguments.redirecturl)#" &
"&scope=https://www.googleapis.com/auth/userinfo.profile&response_type=code" &
"&state=#urlEncodedFormat(arguments.state)#";
}
public function getProfile(accesstoken) {
var h = new com.adobe.coldfusion.http();
h.setURL("https://www.googleapis.com/oauth2/v1/userinfo");
h.setMethod("get");
h.addParam(type="header",name="Authorization",value="OAuth #accesstoken#");
h.addParam(type="header",name="GData-Version",value="3");
h.setResolveURL(true);
var result = h.send().getPrefix();
return deserializeJSON(result.filecontent.toString());
}
/*
I handle validating the code result from Google and automatically getting the auth token.
I should be able to handle any bad result from Google or the user not allowing crap.
I also validate the state.
*/
public struct function validateResult(code, error, remoteState, clientState) {
var result = {};
//If error is anything, we have an error
if(error != "") {
result.status = false;
result.message = error;
return result;
}
//Then, ensure states are equal
if(remoteState != clientState) {
result.status = false;
result.message = "State values did not match.";
return result;
}
var token = getGoogleToken(code);
if(structKeyExists(token, "error")) {
result.status = false;
result.message = token.error;
return result;
}
result.status = true;
result.token = token;
return result;
}
//Credit: http://www.sitekickr.com/blog/http-post-oauth-coldfusion
private function getGoogleToken(code) {
var postBody = "code=" & UrlEncodedFormat(arguments.code) & "&";
postBody = postBody & "client_id=" & UrlEncodedFormat(application.clientid) & "&";
postBody = postBody & "client_secret=" & UrlEncodedFormat(application.clientsecret) & "&";
postBody = postBody & "redirect_uri=" & UrlEncodedFormat(application.callback) & "&";
postBody = postBody & "grant_type=authorization_code";
var h = new com.adobe.coldfusion.http();
h.setURL("https://accounts.google.com/o/oauth2/token");
h.setMethod("post");
h.addParam(type="header",name="Content-Type",value="application/x-www-form-urlencoded");
h.addParam(type="body",value="#postBody#");
h.setResolveURL(true);
var result = h.send().getPrefix();
return deserializeJSON(result.filecontent.toString());
}
}
По большей части я предполагаю, что это говорит само за себя, но если у кого-то есть вопросы, дайте мне знать
Наконец, интересный поворот. А как насчет компаний, которые используют Google Apps? Оказывается, есть недокументированное решение для этого. Посмотрите на функцию generateAuthURL выше. Если вы добавите аргумент «hd», вы можете указать домен Служб Google:
public string function generateAuthURL(redirecturl, state) {
/*
Scope is what you want to do with your access. Since this demo is ONLY for
auth and user info, we have one hard coded value.
*/
return "https://accounts.google.com/o/oauth2/auth?hd=camdenfamily.com&" &
"client_id=#urlEncodedFormat(variables.clientid)#" &
"&redirect_uri=#urlEncodedFormat(arguments.redirecturl)#" &
"&scope=https://www.googleapis.com/auth/userinfo.profile&response_type=code" &
"&state=#urlEncodedFormat(arguments.state)#";
}
Это прекрасно работает, но, как я уже сказал, это не задокументировано. Мой друг является платным клиентом Служб Google и обратился в службу технической поддержки, но, к сожалению, никто не даст ему точного ответа. Его последнее письмо с ними привело к этому:
В основном, по словам представителя, его техническая команда сказала, что использование hd = cbtec.com является функцией, поэтому они не указали это в своей документации. Он также сказал, что переменная hd также используется для аутентификации пользователей в организации с бизнес-единицами, разделенными доменами.
Честно говоря, это первое предложение не имеет смысла. «Это особенность, и поэтому мы не документировали ее». Я надеюсь, что это была просто опечатка.


