OAuth-аутентификация через ВКонтакте
В одном из проектов появилась задача обеспечить вход пользователей на сайт через социальные сети. Начало было положено с помощью модуля DotNetOpenAuth extensions for ASP.NET (WebPages). В результате появилась возможность использовать такие сети:
- Google+
- Live.com
- LinkedId
На борту имелся клиент еще и для Yahoo, но в связи с его малой популярностью в Рунете решили его не подключать. А вот для популярных в Рунете сетей ВКонтакте и Одноклассники встроенной поддержки нет. Решил сделать это самостоятельно, тем более что, на первый взгляд, задача казалась не сложной.
Как оказалось - только на первый взгляд. У каждой социальной сети есть свои нюансы. Так как по натуре я не жадный, кроме того - хочу чтобы вы подсказали мне ошибки, вот код для ВКонтакте:
Как оказалось - только на первый взгляд. У каждой социальной сети есть свои нюансы. Так как по натуре я не жадный, кроме того - хочу чтобы вы подсказали мне ошибки, вот код для ВКонтакте:
https://gist.github.com/4556009
Примечание: Я убрал встроенный код и добавил ссылку на GitHub, так как тут код показывается плохо.
Дополнение: Коллега прислал ссылочку на готовые модули для ВКонтакте и Одноклассники:
http://promorepublic.github.io/oauth/
Чтобы подключить этот код надо:
Примечание: Я убрал встроенный код и добавил ссылку на GitHub, так как тут код показывается плохо.
Дополнение: Коллега прислал ссылочку на готовые модули для ВКонтакте и Одноклассники:
http://promorepublic.github.io/oauth/
Чтобы подключить этот код надо:
- Добавить его в проект (оба файла).
- Зарегистрировать приложение на сайте ВКонтакте и получить Id приложения и секретный ключ.
- Зарегистрировать провайдер где-то в Global.asax.cs таким вот вызовом:
OAuthWebSecurity.RegisterClient(new VKontakteOAuthProvider("*****", "********"), "ВКонтакте", new Dictionary());
Коментарі
Надо конечно поправить темплиту...
Если Можно
OAuthWebSecurity.RegisterClient(new VKontakteOAuthProvider("*****", "********"), "ВКонтакте", new Dictionary());
OpenAuth.Login(authResult.Provider,...
ошибка- To call this method, the "Membership.Provider" property must be an instance of "ExtendedMembershipProvider".
В инете куча мусора по этому поводу. У вас есть идеи?
Я использую авторизацию через AspNetSQL и мне нада прилепить пользователей через OAuth, к слову они хорошо лепятся и записываются всбазу но эта ошибка вылазить после попытки залогинить нового узера. Мои сайты (bazarodessa.com, bazarkiev.com, ukrainebazar.com) работают пока через AspNetSQL авторизацию, и подключить авторизацию через DotNetOAuth непроблема, но дополнительные провайдеры - это гемор (хотя ваш код реально помог для VK) но осталась одна ступенька и тогда можно будет подключать всех.
Рассмотрите вариант не реализовывать MembershipProvider самостоятельно, а воспользоваться реализацией, которую предоставляет Microsoft в nuget-пакете Microsoft.AspNet.WebPages.WebData. Там их собственно 2: ExtendedMembershipProvider и SimpleMembershipProvider (унаследован от предыдущего).
С ними все должно работать.
Return New OAuthClients.VKontakteOAuthProvider("12345", "dsfvdgdfgfd")
End Function
*****************************
OpenAuth.AuthenticationClients.Add("VKontakte", AddressOf getVKontakte)
И главное работает.
Вопрос - почему решили отказать от проверки state?Просто не стали заморчиваться?
Спасибо
Вот если бы еще кто-то в ответ поделился кодом для Одноклассников. Что-то у меня никак не получается сгенерировать сообщений, которое их сервер признает...
Если Вы расширили реализацию поддержкой state, может поделитесь?
Imports System
Imports System.Collections.Generic
Imports System.Diagnostics.CodeAnalysis
Imports System.Net
Imports System.Web
Imports System.ComponentModel
Imports System.Linq
Imports System.IO
Imports System.Text
Imports System.Runtime.Serialization
Imports System.Runtime.Serialization.Json
Imports DotNetOpenAuth
Imports DotNetOpenAuth.AspNet.Clients
Imports DotNetOpenAuth.Messaging
Imports Microsoft.AspNet.Membership.OpenAuth
Imports OAuthLib
Namespace OAuthClients
Public Class OdnoklassnikiOAuthProvider
Inherits OAuth2Client
Private appId As String
Private appSecret As String
Public Sub New(ByVal appId As String, ByVal appSecret As String)
MyBase.New("odnoklassniki")
Me.appId = appId
Me.appSecret = appSecret
End Sub
'http://www.odnoklassniki.ru/oauth/authorize
'client_id - идентификатор приложения
'response_type - на данный момент поддерживается только тип ответа «code»
'redirect_uri - URI для переадресации пользователя после аутентификации с дополнительным параметром
'scope - перечень прав доступа, разделенных символом «;»:
' VALUABLE ACCESS - доступ к методам API, за исключением приведенных ниже (методы users.getLoggedInUser и users.getCurrentUser вызываются без параметра scope)
' SET STATUS
' PHOTO CONTENT
Protected Overrides Function GetServiceLoginUrl(returnUrl As Uri) As Uri
If returnUrl = Nothing Then Throw New ArgumentNullException("returnUrl")
Dim builder As UriBuilder = New UriBuilder("http://www.odnoklassniki.ru/oauth/authorize")
Dim args As New Dictionary(Of String, String)
args.Add("client_id", appId)
args.Add("response_type", "code")
args.Add("redirect_uri", NormalizeHexEncoding(returnUrl.AbsoluteUri))
builder.AppendQueryArgs(args)
Return builder.Uri
End Function
'http://api.odnoklassniki.ru/fb.do
'GET http://api.odnoklassniki.ru/fb.do?
'method=users.getCurrentUser&
'access_token={access_token}&
'application_key={public_key}&
'sig={sign}
' sign — трахнутая на все байты md5-подпись
'sign=hex_md5('application_key={public_key}method=users.getCurrentUser'+hex_md5({access_token}+{secret_key}))
'1.Методы API нужно вызывать с помощью параметра access_token вместо session_key
'2.Каждый параметр подписи запроса sig вычисляется немного иным способом, чем обычно:
' sig = md5( request_params_composed_string+ md5(access_token + application_secret_key) )
' Не включайте маркер доступа access_token в request_params_composed_string
...
Protected Overrides Function GetUserData(accessToken As String) As IDictionary(Of String, String)
If accessToken Is String.Empty Then Throw New ArgumentNullException("accessToken")
Dim data As OdnoklassnikiTokenResponse = HttpContext.Current.Session("OdnoklassnikiOAuthProvider.Token")
If data Is Nothing Or data.AccessToken <> accessToken Then
Return Nothing
End If
Dim res As New Dictionary(Of String, String)
Dim builder As UriBuilder = New UriBuilder("http://api.odnoklassniki.ru/fb.do")
Dim args As New Dictionary(Of String, String)
args.Add("application_key", "YCGYVGVYDFVH HDA")
args.Add("method", "users.getCurrentUser")
Dim sig As String = Helpers.GetSig(Helpers.GetMD5(accessToken & Me.appSecret), args)
args.Add("access_token", accessToken)
args.Add("sig", sig)
builder.AppendQueryArgs(args)
'send request
Dim httpWebRequest As HttpWebRequest = WebRequest.Create(builder.Uri)
httpWebRequest.Method = "GET"
httpWebRequest.AllowAutoRedirect = True
Dim response As String = ""
Try
Dim httpWebResponse As HttpWebResponse = httpWebRequest.GetResponse
Using sr As IO.Stream = httpWebResponse.GetResponseStream
'test response
'Dim str As String = New StreamReader(sr).ReadToEnd
'end test
Dim serializer As New DataContractJsonSerializer(GetType(OdnoklassnikiDataItem))
Dim item As OdnoklassnikiDataItem = serializer.ReadObject(sr)
res.Add("id", "odn_" & item.UserId.ToString())
res.AddItemIfNotEmpty("username", (item.Username))
res.AddItemIfNotEmpty("name", (((item.FirstName & "") + " " + (item.LastName & "")).Trim()))
res.AddItemIfNotEmpty("birthday", item.Birthdate)
res.AddItemIfNotEmpty("gender", item.Sex)
End Using
Catch ex As Exception
End Try
Return res
End Function
'http://api.odnoklassniki.ru/oauth/token.do
'code - код авторизации, полученный в ответном адресе URL пользователя
'redirect_uri - тот же URI для переадресации, который был указан при первом вызове
'grant_type - _на данный момент поддерживается только код авторизации authorization_code
'client_id - идентификатор приложения
'client_secret - секретный ключ приложения
Protected Overrides Function QueryAccessToken(returnUrl As Uri, authorizationCode As String) As String
Dim url As String = "http://api.odnoklassniki.ru/oauth/token.do"
Dim par As New Dictionary(Of String, String)
par.Add("client_id", Me.appId)
par.Add("client_secret", Me.appSecret)
par.Add("grant_type", "authorization_code")
par.Add("code", authorizationCode)
par.Add("redirect_uri", Helpers.UrlEncode(returnUrl.AbsoluteUri))
Dim parameters As String = getParametersString(par).Remove(0, 1)
Return sendPostRequest(url, parameters)
End Function
...
Private Function NormalizeHexEncoding(ByVal url As String) As String
Dim chArray As Char() = url.ToCharArray
Dim i As Integer = 0
For i = 0 To i < chArray.Length - 2
If chArray(i) = "%" Then
chArray(i + 1) = Char.ToUpperInvariant(chArray(i + 1))
chArray(i + 2) = Char.ToUpperInvariant(chArray(i + 2))
i += 2
End If
Next
Return New String(chArray)
End Function
'get ParametersString from Dictionary
Public Shared Function getParametersString(ByVal items As Dictionary(Of String, String)) As String
Dim ls As New List(Of String)
For Each it In items
ls.Add(String.Format("{0}={1}", it.Key, it.Value))
Next
Return "?" & String.Join("&", ls)
End Function
...
'Send POST request
Public Shared Function sendPostRequest(ByVal url As String, ByVal parameters As String) As String
Dim httpWebRequest As HttpWebRequest = httpWebRequest.Create(url)
httpWebRequest.Method = "POST"
httpWebRequest.Accept = "*/*"
httpWebRequest.AllowAutoRedirect = True
httpWebRequest.ContentType = "application/x-www-form-urlencoded"
Dim data As Byte() = Encoding.UTF8.GetBytes(parameters)
httpWebRequest.ContentLength = data.Length
Dim newStream As Stream = httpWebRequest.GetRequestStream()
newStream.Write(data, 0, data.Length)
Dim raw_token As String = String.Empty
Try
Dim httpWebResponse As HttpWebResponse = httpWebRequest.GetResponse
Using sr As New StreamReader(httpWebResponse.GetResponseStream)
Using stream As IO.Stream = sr.BaseStream
'test response
'Dim str As String = New StreamReader(stream).ReadToEnd
'end test
Dim serializer As New DataContractJsonSerializer(GetType(OdnoklassnikiTokenResponse))
Dim dataStr As OdnoklassnikiTokenResponse = serializer.ReadObject(stream)
HttpContext.Current.Session("OdnoklassnikiOAuthProvider.Token") = dataStr
raw_token = dataStr.AccessToken
End Using
End Using
Catch ex As WebException
Dim exception As String = String.Empty
Using sr As New StreamReader(ex.Response.GetResponseStream)
exception = sr.ReadToEnd
End Using
Throw New WebException(exception)
Finally
newStream.Close()
End Try
Return raw_token
End Function
Class OdnoklassnikiTokenResponse
'access_token: 'kjdhfldjfhgldsjhfglkdjfg9ds8fg0sdf8gsd8fg',
'token_type: 'session',
'refresh_token: 'klsdjhf0e9dyfasduhfpasdfasdfjaspdkfjp'
Public Property AccessToken As String
Public Property TokenType As String
Public Property RefreshToken As String
End Class
'•uid - идентификатор пользователя
'•birthday - дата рождения
'•age - возраст пользователя
'•first_name - имя пользователя
'•last_name - фамилия пользователя
'•name - совмещение имени и фамилии для отображения
'•has_email - true/false имеет или не имеет адрес эл. почты
'•gender - пол
'•pic_1 - малый значок профиля (50x50)
'•pic_2 - малое изображение профиля (128x128)
Class OdnoklassnikiDataItem
Public Property UserId As String
Public Property Birthdate As String
Public Property Sex As String
Public Property Username As String
Public Property FirstName As String
Public Property LastName As String
End Class
End Class
End Namespace
Иван, спасибо! Попробую использовать.