Содержание
Авторизация ВКонтакте через WebView в Android приложении / Хабр
Здравствуй дорогой друг, в этой статье, на простом примере мы рассмотрим, каким образом можно реализовать авторизацию и использование api социальной сети «ВКонтакте» без подключения официального SDK. Пример приложения можно скачать на github по ссылке в конце статьи.
Создаем проект, подключаем зависимости
В проекте я буду использовать kotlin, mvvm, binding, navgraph подразумевается, что ты уже знаешь, что это такое 🙂
Создаем новый проект на основе Empty Activity, я назову его OAuthWithVK_Example
Создание нового проекта
Добавляем в зависимости.
Зависимости
implementation 'androidx.navigation:navigation-fragment-ktx:2.4.1' implementation 'androidx.navigation:navigation-ui-ktx:2.4.1' implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
Создаем необходимые классы и файлы
Создадим класс «App» расширяющий «Application», он будет представлять наше приложение и содержать экземпляр «AccountService» для хранения токена и экземпляр «Retrofit» с url для запросов к ВК api. Через companion object будем получать доступ к App и созданным экземплярам. По хорошему это нужно делать через DI, но для простоты примера сделаем так.
Класс App
/** * Представляет приложение. */ class App : Application() { /** * Возвращает или устанавливает сервис хранения настроект. */ lateinit var accountService: IAccountService /** * Возвращает или устанавливает экземпляр ретрофита. */ lateinit var retrofit: Retrofit companion object { lateinit var application: App } override fun onCreate() { super.onCreate() application = this accountService = VKAccountService(getSharedPreferences("vk_account", MODE_PRIVATE)) retrofit = Retrofit.Builder() .baseUrl("https://api.vk.com/method/") .addConverterFactory(ScalarsConverterFactory.create()) .build() } }
Создадим интерфейс «IAccountService» и его реализацию «VKAccountService», сервис будет предоставлять возможность сохранять и получать token и userId.
Интерфейс IAccountService
/** * Определяет интерфейс получения и установки параметров аккаунта. */ interface IAccountService { /** * Возвращает или устанавливает токен. */ var token: String? /** * Возвращает или устанавливает идентификатор пользователя. */ var userId: String? }
Класс VKAccountService
/** * Представляет сервис сохранения пользовательских настроек. * @param sharedPreference Класс записи пользовательских настроек. */ internal class VKAccountService( private val sharedPreference: SharedPreferences ) : IAccountService { private val TOKEN = "token" private val USER_ID = "userId" companion object { const val SCOPE = "friends,stats" } override var token: String? get() { return sharedPreference.getString(TOKEN, null) } set(value) { with(sharedPreference.edit()) { if (value == null) { remove(TOKEN) } else { putString(TOKEN, value) } apply() } } override var userId: String? get() { return sharedPreference. getString(USER_ID, null) } set(value) { with(sharedPreference.edit()) { if (value == null) { remove(USER_ID) } else { putString(USER_ID, value) } apply() } } }
Создадим класс активити с именем «MainActivity» и соответствующий ему файл разметки «activity_main». Он будет содержать FragmentContainerView для навигации.
Класс MainActivity
/** * Представляет основное активити приложения. */ class MainActivity : AppCompatActivity() { private lateinit var appBarConfiguration: AppBarConfiguration private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) setSupportActionBar(binding.toolbar) val navController = (supportFragmentManager. findFragmentById(R.id.nav_host_fragment) as NavHostFragment).navController appBarConfiguration = AppBarConfiguration(navController.graph) setupActionBarWithNavController(navController, appBarConfiguration) } override fun onSupportNavigateUp(): Boolean { val navController = findNavController(R.id.nav_host_fragment) return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp() } }
Файл разметки activity_main
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> <androidx.appcompat.widget.LinearLayoutCompat android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/Theme. OpenAuthWithVK_Example.AppBarOverlay"> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/Theme.OpenAuthWithVK_Example.PopupOverlay" /> </com.google.android.material.appbar.AppBarLayout> <androidx.fragment.app.FragmentContainerView android:id="@+id/nav_host_fragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" app:navGraph="@navigation/nav_graph" /> </androidx.appcompat.widget.LinearLayoutCompat> </layout>
Обновим файл манифеста, указав корневое активити.
Файл манифеста
<manifest xmlns:android="http://schemas. android.com/apk/res/android" package="com.alab.oauthwithvk_example"> <uses-permission android:name="android.permission.INTERNET" /> <application android:allowBackup="true" android:name="com.alab.oauthwithvk_example.App" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true"> <activity android:name="com.alab.oauthwithvk_example.MainActivity" android:exported="true" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
Для навигации по фрагментам понадобится файл «nav_graph».
Файл навигации
<navigation xmlns:android="http://schemas. android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/nav_graph" app:startDestination="@id/AuthFragment"> <fragment android:id="@+id/AuthFragment" android:name="com.alab.oauthwithvk_example.AuthFragment" android:label="@string/auth_fragment_label"> <action android:id="@+id/action_AuthFragment_to_InfoFragment" app:destination="@id/InfoFragment" /> </fragment> <fragment android:id="@+id/InfoFragment" android:name="com.alab.oauthwithvk_example.InfoFragment" android:label="@string/info_fragment_label" tools:layout="@layout/info_fragment"> <action android:id="@+id/action_InfoFragment_to_AuthFragment" app:popUpTo="@id/AuthFragment" /> </fragment> </navigation>
Теперь создадим первый класс фрагмента для авторизации, назовем его «AuthFragment». Здесь нам нужен только виджет WebView, который создадим программно. Для открытия окна авторизации нужен url с параметрами, создаем приватное поле с именем «_authParams», оно будет содержать строку с необходимой конфигурацией, далее передадим ее в WebView. В методе onViewCreated будем открывать окно аутентификации, реагировать на события ‘Подтверждение разрешений’, ‘Ошибка ввода логина/пароля’, ‘Успех’ и др. В коде я оставил TODO куда нужно будет вставить ваш client_id приложения, как его получить рассмотрим в конце статьи.
Класс AuthFragment
/** * Представляет фрагмент 'Войти в аккаунт'. */ class AuthFragment : Fragment() { private val webview by lazy { WebView(context!!) } private val _authParams = StringBuilder("https://oauth.vk.com/authorize?").apply { append(String.format("%s=%s", URLEncoder.encode("client_id", "UTF-8"), URLEncoder.encode(/*TODO Сюда вставить id приложения созданного в ВК в разделе "Developers"*/, "UTF-8")) + "&") append(String. format("%s=%s", URLEncoder.encode("redirect_uri", "UTF-8"), URLEncoder.encode("https://oauth.vk.com/blank.html", "UTF-8")) + "&") append(String.format("%s=%s", URLEncoder.encode("display", "UTF-8"), URLEncoder.encode("mobile", "UTF-8")) + "&") append(String.format("%s=%s", URLEncoder.encode("scope", "UTF-8"), URLEncoder.encode(VKAccountService.SCOPE, "UTF-8")) + "&") append(String.format("%s=%s", URLEncoder.encode("response_type", "UTF-8"), URLEncoder.encode("token", "UTF-8")) + "&") append(String.format("%s=%s", URLEncoder.encode("v", "UTF-8"), URLEncoder.encode("5.131", "UTF-8")) + "&") append(String.format("%s=%s", URLEncoder.encode("state", "UTF-8"), URLEncoder.encode("12345", "UTF-8")) + "&") append(String.format("%s=%s", URLEncoder.encode("revoke", "UTF-8"), URLEncoder.encode("1", "UTF-8"))) }.toString() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ) = webview override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super. onViewCreated(view, savedInstanceState) if (App.application.accountService.token == null) { webview.webViewClient = AuthWebViewClient(context!!) { status -> when(status) { AuthStatus.AUTH -> { } AuthStatus.CONFIRM -> { } AuthStatus.ERROR -> { Toast.makeText(context, "Не верный логин или пароль", Toast.LENGTH_LONG).show() } AuthStatus.BLOCKED -> { showAuthWindow() Toast.makeText(context, "Аккаунт заблокирован", Toast.LENGTH_LONG).show() } AuthStatus.SUCCESS -> { val url = webview.url!! val tokenMather = Pattern.compile("access_token=\\w+").matcher(url) val userIdMather = Pattern.compile("user_id=\\w+").matcher(url) // Если есть совпадение с патерном. if (tokenMather.find() && userIdMather.find()) { val token = tokenMather.group().replace("access_token=".toRegex(), "") val userId = userIdMather.group().replace("user_id=".toRegex(), "") // Если токен и id получен. if (token.isNotEmpty() && userId.isNotEmpty()) { App.application.accountService.token = token App.application.accountService.userId = userId navigateToInfo() } } } } } } else { navigateToInfo() } } override fun onStart() { super.onStart() if (App.application.accountService.token == null) { showAuthWindow() } } private fun showAuthWindow() { CookieManager. getInstance().removeAllCookies(null) webview.loadUrl(_authParams) } private fun navigateToInfo() { findNavController().navigate(R.id.action_AuthFragment_to_InfoFragment) } }
В зависимости от того какое событие сейчас происходит (ввод пароля, ошибка, заблокированный аккаунт), текущий url у WebView будет изменяться, на основе этого будем определять текущий статус аутентификации. Для этого создадим класс «AuthWebViewClient» расширяющий «WebViewClient», переопределим метод onPageFinished в котором будем парсить текущую открытую ссылку.
Класс AuthWebViewClient
/** * Представляет WebView клиент. * @param context Контекст. * @param onStatusChange Обработчик смены статуса аутентификации. */ class AuthWebViewClient( private val context: Context, private val onStatusChange: (status: AuthStatus) -> Unit ) : WebViewClient() { private var _currentUrl = "" override fun shouldOverrideUrlLoading(wv: WebView, url: String): Boolean { wv. loadUrl(url) return true } override fun onPageFinished(wv: WebView, url: String) { if (_currentUrl != url) { val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager //если открыто окно аутентификации. if (url.contains("https://oauth.vk.com/authorize")) { val scope = URLEncoder.encode(VKAccountService.SCOPE, "UTF-8") // Если открыто окно ввода логина и пароля. if (url.contains(scope)) { imm.showSoftInput(wv, 0) wv.visibility = View.VISIBLE onStatusChange(AuthStatus.AUTH) } // Если открыто окно подтверждения разрешений. if (url.contains("q_hash")) { onStatusChange(AuthStatus.CONFIRM) } // Если открыто окно с сообщением об неверно введеном пароле. if (url.contains("email")) { onStatusChange(AuthStatus. ERROR) } } // Если открыто окно заблокированного пользователя. if (url.contains("https://m.vk.com/login\\?act=blocked")) { onStatusChange(AuthStatus.BLOCKED) } // Если открыто окно для считывания токена. if (url.contains("https://oauth.vk.com/blank.html")) { wv.visibility = View.INVISIBLE onStatusChange(AuthStatus.SUCCESS) } } _currentUrl = url } }
Перечислим статусы аутентификации в enum, который назовем «AuthStatus», этот enum будем передаваться кэлбеком из класса AuthWebViewClient во фрагмент.
Класс AuthStatus
/** * Перечисляет статусы аутентификации клиента. */ enum class AuthStatus { /** * Статус ввода логина и пароля. */ AUTH, /** * Статус подтверждения разрешений. */ CONFIRM, /** * Статус завершения авторизации с ошибкой. */ ERROR, /** * Статус заблокированного пользователя. */ BLOCKED, /** * Статус успешного завершения авторизации. */ SUCCESS }
После верного ввода логина/пароля и подтверждения разрешений, будет получен и записан в память токен и идентификатор пользователя. С фрагментом аутентификации на этом все.
Приступим к созданию второго фрагмента, здесь мы сделаем всего 1 запрос на получение списка друзей. На экране покажем кнопку для выхода, textview для показа кол-ва друзей и скролящийся textview для показа списка друзей.
Создадим фрагмент с именем «InfoFragment» и соответствующий ему xml файл с разметкой «info_fragment».
Класс InfoFragment
/** * Представляет фрагмент 'Инфо'. */ class InfoFragment : Fragment() { private val _viewModel: InfoViewModel by viewModels() private var _binding: InfoFragmentBinding? = null private val binding get() = _binding!! override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = InfoFragmentBinding. inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) with(binding) { lifecycleOwner = [email protected] vm = _viewModel tvFriends.movementMethod = ScrollingMovementMethod() logout.setOnClickListener { App.application.accountService.token = null App.application.accountService.userId = null findNavController().navigate(R.id.action_InfoFragment_to_AuthFragment) } } } override fun onDestroyView() { super.onDestroyView() _binding = null } }
Файл разметки info_fragment
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <data> <variable name="vm" type="com. alab.oauthwithvk_example.InfoViewModel" /> </data> <androidx.appcompat.widget.LinearLayoutCompat android:layout_width="match_parent" android:layout_height="match_parent" android:padding="16dp" android:orientation="vertical" tools:context=".InfoFragment"> <Button android:id="@+id/logout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Logout"/> <androidx.appcompat.widget.AppCompatTextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text='@{"Друзей: " + vm.count}'/> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/tvFriends" android:layout_width="match_parent" android:layout_height="match_parent" android:text="@{vm.friends}" android:layout_marginVertical="16dp" android:scrollbars="vertical"/> </androidx. appcompat.widget.LinearLayoutCompat> </layout>
Запрос на список друзей будем делать во ViewModel, эту вью модель передадим в биндинг, LiveData сама будет устанавливать данные в TextView.
Класс InfoViewModel
/** * Определяет модель представления фрагмента 'Инфо'. */ class InfoViewModel: ViewModel() { private val _count = MutableLiveData<String>() private val _friends = MutableLiveData<String>() /** * Возвращает кол-во друзей. */ val count: LiveData<String> = _count /** * Возвращет список друзей. */ val friends: LiveData<String> = _friends init { viewModelScope.launch { val response = App.application.retrofit.create(FriendsGetRequest::class.java).friendsGet( App.application.accountService.token!!, "5.131", "name" ) val friendsList = StringBuilder() val items = JSONObject(response).getJSONObject("response").getJSONArray("items") for (i in 0 until items. length()) { friendsList.append( "${items.getJSONObject(i).getString("first_name")} ${items.getJSONObject(i).getString("last_name")}\n" ) } _count.postValue(JSONObject(response).getJSONObject("response").getString("count")) _friends.postValue(friendsList.toString()) } } }
Осталось написать интерфейс «FriendsGetRequest» с запросом для ретрофит и на этом с программной частью будем заканчивать 🙂
Интерфейс FriendsGetRequest
/** * Определяет запрос друзей пользователя. */ interface FriendsGetRequest { /** * Возвращает json со списком друзей. */ @GET("friends.get") suspend fun friendsGet( @Query("access_token") token: String, @Query("v") v: String, @Query("fields") fields: String ): String }
Теперь разберемся, как получить client_id, это один из параметров запроса на авторизацию, его выдает ВК для понимания, какое приложение собирается обращаться к его api. Что бы его получить зайдите на свою страницу ВК и найдите меню «Управление», если его нет в списке, нужно добавить его в настройках страницы.
Меню
Кликнув по меню «Управление» мы попадем в раздел «Мои приложения», для создания нового приложения нажмите кнопку «Создать»
Раздел «Мои приложения»
В открывшемся окне укажите название приложения и выберите тип «Standalone-приложение» далее жмем кнопку «Подключить приложение». После нажатия на кнопку, вам придет смс на подключенный к странице номер.
Создание приложения
Когда приложение будет создано перейдите в меню «Настройки», там будет указан client_id, который нужно вставить в код на место TODO, все остальные настройки по желанию 🙂
Меню настройки приложения
Скачать пример проекта можно здесь
Безопасна ли авторизация на сайте через ВКонтакте и другие соцсети?
Главная » Прочее » Безопасна ли авторизация на сайте через ВКонтакте и другие соцсети?
03/04/2019GNB-Gamer0 Comments
КатегорииПрочее
Буквально вчера добавил полезную для постоянных посетителей блога функцию – возможность зайти в качестве подписчика, воспользовавшись кнопками социальных сетей. Это значительно удобнее, чем внедрять отдельную форму регистрации. Если же сравнивать с анонимным комментированием (пока ещё доступным), то плюс кроется в удобстве отслеживания ответов на свои сообщения другими пользователями, в просмотре истории, смене аватара и т. д.
Первый вопрос, возникающий у не сталкивавшегося с этим человека: «Безопасна ли авторизация на сайте через ВКонтакте?». Согласно мировой статистике, наибольшее опасение за сохранность своих конфиденциальных данных выражают люди в возрасте 35-40+, а молодёжь уже давно привыкла к данному способу идентификации. Чтобы разобраться в нём, необходимо понять, какие сведения передаются.
Итак, мы жмём на кнопку «Войти через VK» (или с помощью любой другой соцсети, коих добавил множество), что происходит далее? На экране отображается подтверждение со стороны соцсети. После этого скрипт при помощи API-протокола спрашивает у социалки, действительно ли пользователь там зарегистрирован.
Ответ положительный? Всё, процедура завершена. Никакой передачи паролей и тому подобного. Присутствует сложное шифрование, исключающее перехват. В этом случае фишинг невозможен, поскольку речь о прямом запросе.
Обращайте внимание на адресную строку в новом окне, где подтверждается доступ к данным. Там должен висеть замок, характерный для HTTPS-сайтов. При нажатии на него отобразится уровень безопасности и видна информация о сервисе. Уж надеюсь, что отличить ВК или ОК от маскирующегося под него веб-сайта вы способны, достаточно взглянуть на адресную строку.
Принцип Application Programming Interface заключается в использовании библиотек, предоставляемых третьими лицами. Взаимодействие происходит методом запросов и ответов. Попросили вход на веб-сайт? Увидели интерфейс для подтверждения, затем получили «зелёный свет» на вход. Быстро, просто и безопасно.
Важно отметить, что для получения доступа к API-интерфейсу каждого отдельного сервиса необходимо пройти процедуру создания своего приложения, подписать соглашение разработчика и т. д. Сами порталы со всей ответственностью относятся к возможности идентификации через них, требуя от владельцев сайтов соблюдения требований.
Надеюсь в той краткой статье удалось ответить на вопрос, безопасна ли авторизация на сайте через ВКонтакте. Да, совершенно.
Подписывайтесь на наши каналы в Яндекс.Дзене и на YouTube! Копирование текстов с сайта GameNewsBlog.ru запрещено. Комментарии закрыты ввиду невозможности их модерации.
ТегиAPIбезопасностьВКонтактевход на сайт через соцсеть
Об авторе
GNB-Gamer
Продажа сайта или домена не интересует. Общий E-mail для связи: [email protected].
Больше публикаций (8427)
Android официальный клиент ВКонтакте. Как авторизоваться
спросил
Изменено
1 год, 8 месяцев назад
Просмотрено
1к раз
У меня на устройстве установлено официальное приложение от ВКонтакте.
И авторизировался там.
Я пытаюсь написать свое приложение, которое использует API ВКонтакте, но мне нужна авторизация.
Есть ли способ получить авторизованный статус пользователя из официального приложения с отправкой Intent с каким-либо действием?
- андроид
- андроид-намерение
- вк
2
Теперь это возможно!
Пожалуйста, просмотрите этот учебник
http://vk.com/dev/android_sdk
Также можно авторизоваться через webview
Авторизация в Вконтакте — это OAuth-авторизация. Таким образом, следующие шаги одинаковы для всех сервисов, использующих OAuth: Facebook, Instagram и т. д.
- Создать заявку на vk.com:
http://vk.com/editapp?act=create - Получить его ID.
- В вашей учетной записи откройте веб-просмотр с URL-адресом входа
https://oauth.vk.com/authorize?client_id=APP_ID&scope=SETTINGS&redirect_uri=REDIRECT_URI&display=DISPLAY&response_type=token
APP_ID — это идентификатор вашего приложения. Вы найдете его в ВКонтакте -> Приложения -> Настройки -> Приложения, которыми я управляю (внизу) -> Управление
Этот URL для входа покажет стандартный диалог авторизации ВКонтакте. Там пользователь введет свой логин и пароль.
После этого пользователь будет перенаправлен на ваш Redirect-Uri. Итак, вам нужно подготовить Redirect_Uri — это страница, которая будет открыта после успешного входа в систему.
К этому URL будет присоединен access_token. Access_token — единственный параметр, который необходимо сохранить после авторизации. В следующих запросах вам просто нужно будет предоставить access_token.
О других параметрах вы можете прочитать на сайте ВКонтакте:
http://vk.com/developers.php?oid=-17680044&p=Authorizing_Client_Applications
1
Зарегистрируйтесь или войдите в систему
Зарегистрируйтесь с помощью Google
Зарегистрироваться через Facebook
Зарегистрируйтесь, используя электронную почту и пароль
Опубликовать как гость
Электронная почта
Требуется, но никогда не отображается
Опубликовать как гость
Электронная почта
Требуется, но не отображается
spring oauth3 поток кода авторизации, конфигурация для ВК (Вконтакте)
спросил
Изменено
6 лет, 1 месяц назад
Просмотрено
4к раз
В качестве сервера авторизации Oauth3 использую социальную сеть Вконтакте. Итак, у меня есть несколько шагов:
1) получить код с запросом с request_type=code
2) получить accessToken, когда я отправляю запрос на токен доступа uri
Итак, я хочу использовать Spring Oauth3, но сначала я должен получить код авторизации, а затем токен доступа, я пытался добавить в application.yml:
авторизованные типы грантов: авторизация_код
это мое приложение.yml:
безопасность: oauth3: клиент: идентификатор клиента: [идентификатор клиента] секрет клиента: [секрет клиента] accessTokenUri: https://oauth.vk.com/access_token userAuthorizationUri: https://oauth.vk.com/authorize tokenName: access_token зарегистрированный-redirect-uri: http://localhost:8080/логин ресурс: token-info-uri: http://localhost:8080/user
но на самом деле это не помогает. Если кто сталкивался с этим и знает как настроить приложение Spring Oauth3 — буду признателен за помощь
- весна
- vk
- весна-oauth3
4
На самом деле, после нескольких дней расследования я понял, что Spring OAuth3, полностью реализующий все функции и настройки моего клиентского приложения, использует предоставление кода авторизации для получения токена доступа от Вконтакте (Сервер авторизации)
Единственное, что мне нужно сделать, если я возьму в качестве примера Spring Boot и вход в социальную сеть OAuth3, просто заполнить application.