Добро пожаловать в мой третий и последний пост в блоге о 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 также используется для аутентификации пользователей в организации с бизнес-единицами, разделенными доменами.
Честно говоря, это первое предложение не имеет смысла. «Это особенность, и поэтому мы не документировали ее». Я надеюсь, что это была просто опечатка.