序言:本文主要介绍了使用 Ionic 和 Cordova 开发混合应用时如何添加用户身份认证。教程简易,对于 Ionic 入门学习有一定帮助。因为文章是去年发表,所以教程内关于 Okta 的一些使用步骤不太准确,但是通过 Okta 的官网也可以找到对应的内容。另外,使用 npm 安装 Ionic starter 模板可能会有安装失败的情况,建议不要在这方面浪费太多时间,可以直接在 Ionic 的 GitHub 仓库 中下载 starter 模板。 原文:How to Sprinkle ReactJS into an Existing Web Application 译者:nzbin
使用 Okta 和 OpenID Connect (OIDC),可以很轻松的在 Ionic 应用中添加身份认证,完全不需要自己实现。 OIDC 允许你直接使用 Okta Platform API 进行认证,本文的目的就是告诉你如何在一个 Ionic 应用中使用这些 API。我将演示如何使用 OIDC 重定向、Okta 的 Auth SDK 以及基于 Cordova 内嵌浏览器的 OAuth 进行登录; 由于功能还在开发中,所以省略了用户注册。
Ionic 是一个用于开发原生及先进 web 应用的开源的移动端 SDK。它使用 Angular 和 Apache Cordova ,可以用 HTML、CSS、和 JavaScript 来开发移动应用。Apache Cordova 将 HTML 代码嵌入到一个设备上的原生 WebView 中, 通过外部功能接口来访问原生资源。你可能听说过 PhoneGap —— 这是 Adobe Cordova 的商业版本。
Cordova 和 PhoneGap 允许你使用一套代码开发多个平台的应用 (比如 Android 和 iOS) 。除此之外,应用程序和原生程序相差无尽并且和原生体验一样好。如果你需要开发原生功能,使用 web 技术是无法实现的,但是有些原生插件可以实现。 Ionic Native 是这些插件的精选集。
我第一次使用 Ionic 是在 2013 年底。当时我做的项目是开发一款原生应用,但是打算使用 HTML 来开发适配多个屏幕的应用,这样 web 开发者也可以参与开发。我在 2014 年的三月写了我的经历。我喜欢使用 Ionic,我发现使用 Ionic 移植现有的应用程序更多的就是修改 HTML 和调整 CSS。
Ionic 2 在 一月份发布, 可以使用 Angular 开发 Ionic 应用。 Ionic 3 在 四月份发布,允许使用 Angular 4 进行开发。
注意: "Angular" 是 Angular 2+ 的通用名称。AngularJS 是 1.x 版本的名称。之所以用 Angular 命名是因为在 2017 年的三月发布了 Angular 4 。可以查看 Branding Guidelines for Angular and AngularJS 了解更多信息。
本文会演示如何创建一个简单的 Ionic 应用以及如何添加用户身份认证。大多数的应用都需要身份认证,这样才能知道用户是谁。一旦 app 知道你的身份,它就可以保存你的信息及个性化的功能。
为了设置 Ionic 的开发环境,需要完成以下几步:
npm install -g cordova ionic
在 terminal 窗口中,使用以下命令创建一个新的应用程序:
ionic start ionic-auth
命令行会提示选择一个 starter 项目并且可以选择是否将应用连接到 Ionic Dashboard。对于本教程,选择 tabs starter 项目,不需要将项目连接到 Ionic Dashboard。
项目创建需要花费一到两分钟,这取决于你的网络连接速度。运行以下命令来打开你的 Ionic 应用。
cd ionic-auth
ionic serve
这个命令默认打开浏览器的 http://localhost:8100。你可以使用 Chrome 的设备模式查看应用程序在 iPhone 6 中的效果。
使用 Ionic serve
命令的特点是它会在浏览器中显示编译错误,而不是(有时会隐藏)在开发控制台。比如,给 app.component.ts
组件中的 rootPage
变量设置一个非法类型,你将看到以下错误。
Ionic Cloud 提供了免费的 Auth 服务。它允许使用邮箱及密码验证身份,也可以使用社交提供商比如 Facebook、Google 和 Twitter 登录。你可以使用 @ionic/cloud-angular
依赖中提供的类创建身份认证。它也支持 自定义身份认证,但是 "需要你自己的服务器处理身份认证"。
目前还没有太多关于这方面的教程,不过从去年开始有了一些。
你可能注意到所有的教程都需要很多的代码。另外,关于如何在后端的 Auth 服务中验证用户身份的文档也不多。
OpenID Connect (OIDC) 基于 OAuth 2.0 协议。它允许客户端验证用户的身份并获得他们的基本配置文件信息。为了将 Okta 的身份认证平台整合到用户身份认证中,需要以下步骤:
http://localhost:8100
作为重定向的 URI 并点击 Finish。你会看到以下设置信息:为了创建身份认证的登录页,先创建 src/pages/login.ts
和 src/pages/login.html
。在 login.html
中,添加一个具有 username 和 password 的表单。
<ion-header>
<ion-navbar>
<ion-title>
Login
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<form #loginForm="ngForm" (ngSubmit)="login()" autocomplete="off">
<ion-row>
<ion-col>
<ion-list inset>
<ion-item>
<ion-input placeholder="Email" name="username" id="loginField"
type="text" required [(ngModel)]="username" #email></ion-input>
</ion-item>
<ion-item>
<ion-input placeholder="Password" name="password" id="passwordField"
type="password" required [(ngModel)]="password"></ion-input>
</ion-item>
</ion-list>
</ion-col>
</ion-row>
<ion-row>
<ion-col>
<div *ngIf="error" class="alert alert-danger">{{error}}</div>
<button ion-button class="submit-btn" full type="submit"
[disabled]="!loginForm.form.valid">Login
</button>
</ion-col>
</ion-row>
</form>
</ion-content>
你可以利用几个开源库来完成实际的身份验证。第一个是 Manfred Steyer's angular-oauth2-oidc. 这个库可以很容易的与 identity tokens 和 access tokens 交互。第二个是 Okta Auth SDK。由于 OIDC 和 OAuth 不是身份认证协议,所以这是使用 JavaScript 完成身份验证所必需的,不必重定向到 Okta 。
使用 npm 安装 angular-oauth2-oidc
npm install angular-oauth2-oidc --save
Okta Auth SDK 目前不支持 TypeScript,可以将以下代码添加到 src/index.html
底部。
<script src="https://ok1static.oktacdn.com/assets/js/sdk/okta-auth-js/1.5.0/OktaAuth.min.js"></script>
在 src/pages/login/login.ts
中, 添加 LoginPage
类的基本结构,在构造器函数中使用 OAuthService
(来自于 angular-oauth2-oidc) 配置了 OIDC 的设置。 你需要使用 Okta OIDC 设置中的 Client ID 替换 "[client-id]" 以及你账户的当前 URI 替换 "[dev-id]"。
import { Component, ViewChild } from '@angular/core';
import { NavController } from 'ionic-angular';
import { OAuthService } from 'angular-oauth2-oidc';
declare const OktaAuth: any;
@Component({
selector: 'page-login',
templateUrl: 'login.html'
})
export class LoginPage {
@ViewChild('email') email: any;
private username: string;
private password: string;
private error: string;
constructor(private navCtrl: NavController, private oauthService: OAuthService) {
oauthService.redirectUri = window.location.origin;
oauthService.clientId = '[client-id]';
oauthService.scope = 'openid profile email';
oauthService.oidc = true;
oauthService.issuer = 'https://dev-[dev-id].oktapreview.com';
}
ionViewDidLoad(): void {
setTimeout(() => {
this.email.setFocus();
}, 500);
}
}
修改 src/app/app.component.ts
验证用户是否登录。如果没有,将 LoginPage
设置为 rootPage。
import { OAuthService } from 'angular-oauth2-oidc';
import { LoginPage } from '../pages/login/login';
@Component({
templateUrl: 'app.html'
})
export class MyApp {
rootPage: any = TabsPage;
constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen,
oauthService: OAuthService) {
if (oauthService.hasValidIdToken()) {
this.rootPage = TabsPage;
} else {
this.rootPage = LoginPage;
}
platform.ready().then(() => {
statusBar.styleDefault();
splashScreen.hide();
});
}
}
更新 src/app/app.module.ts
,在 declarations
和 entryComponents
中添加 LoginPage
。你也要将 OAuthService
添加到 providers
中。
@NgModule({
declarations: [
...
LoginPage
],
...
entryComponents: [
...
LoginPage
],
providers: [
OAuthService,
...
]
})
运行 ionic serve
,确认 LoginPage
在 app 首次加载后可以展示出来。app 加载时会有以下报错:
No provider for Http!
出现这个错误是因为 OAuthService
需要依赖 Angular 的 Http
模块,但是还没有将该模块导入到项目中。在 src/app/app.module.ts
中导入 HttpModule
。
import { HttpModule } from '@angular/http';
@NgModule({
...
imports: [
BrowserModule,
HttpModule,
IonicModule.forRoot(MyApp)
],
...
})
现在登录页已经展示出来了。你可以使用 Chrome 的设备模式查看在 iPhone 6 上的效果。
为了解决缺少 TypeScript 支持的问题,你需要在 src/app/pages/login/login.ts
的顶部添加以下代码。
declare const OktaAuth: any;
TIP: 要了解更多关于在 TypeScript 项目引用外部 JavaScript 库的知识,可以阅读 Nic Raboy 写的关于这方面的文章。
在 src/app/pages/login/login.ts
中添加一个 login()
方法,它使用 Okta Auth SDK 进行: 1) 登录; 2) 将 session token 转换成 identity 和 access token。 一个 ID token 类似于身份证,它是标准的 JWT 格式,由 OpenID 提供者签名。Access tokens 是 OAuth 规范的一部分。一个 access token 可以是一个 JWT。它们用于访问被保护的资源,通常是在发送请求时将它们添加到 Authentication
请求头中。
login(): void {
this.oauthService.createAndSaveNonce().then(nonce => {
const authClient = new OktaAuth({
clientId: this.oauthService.clientId,
redirectUri: this.oauthService.redirectUri,
url: this.oauthService.issuer
});
authClient.signIn({
username: this.username,
password: this.password
}).then((response) => {
if (response.status === 'SUCCESS') {
authClient.token.getWithoutPrompt({
nonce: nonce,
responseType: ['id_token', 'token'],
sessionToken: response.sessionToken,
scopes: this.oauthService.scope.split(' ')
})
.then((tokens) => {
// oauthService.processIdToken doesn't set an access token
// set it manually so oauthService.authorizationHeader() works
localStorage.setItem('access_token', tokens[1].accessToken);
this.oauthService.processIdToken(tokens[0].idToken, tokens[1].accessToken);
this.navCtrl.push(TabsPage);
})
.catch(error => console.error(error));
} else {
throw new Error('We cannot handle the ' + response.status + ' status');
}
}).fail((error) => {
console.error(error);
this.error = error.message;
});
});
}
通过 identity token 你可以了解用户的更多信息。通过 access token 你可以访问需要 Bearer token 的受保护的 API。比如, 在 在 Angular PWA 中添加身份认证中,有一个 BeerService
,它用于在发送 API 请求时携带 access token 。
import { Injectable } from '@angular/core';
import { Http, Response, Headers, RequestOptions } from '@angular/http';
import 'rxjs/add/operator/map';
import { Observable } from 'rxjs';
import { OAuthService } from 'angular-oauth2-oidc';
@Injectable()
export class BeerService {
constructor(private http: Http, private oauthService: OAuthService) {
}
getAll(): Observable<any> {
const headers: Headers = new Headers();
headers.append('Authorization', this.oauthService.authorizationHeader());
let options = new RequestOptions({ headers: headers });
return this.http.get('http://localhost:8080/good-beers', options)
.map((response: Response) => response.json());
}
}
您可以(可选)在表单上方添加图标来美化登录页。下载 这张图片,将它拷贝到 src/assets/image/okta.png
,在 login.html
的 <form>
标签中添加以下代码。
<ion-row>
<ion-col text-center>
<img src="assets/image/okta.png" width="300">
</ion-col>
</ion-row>
当你尝试使用 Okta 的用户证书登录应用程序,你将在浏览器的控制台看到跨域报错。
XMLHttpRequest cannot load https://dev-158606.oktapreview.com/api/v1/authn. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8100' is therefore not allowed access.
为了修复这一问题,在 Okta 修改 Trusted Origins (在 Security > API 下面), 将你的 client's URL 添加进去 (比如 http://localhost:8100
)。检查 CORS 和重定向的 origin 类型。
现在登录可以正常工作了,但是 UI 界面并没有提示。在首页的右上角添加一个 "Logout" 按钮。用以下 HTML 替换 src/pages/home/home.html
中的 <ion-header>
。
<ion-header>
<ion-navbar>
<ion-title>Home</ion-title>
<ion-buttons end>
<button ion-button (click)="logout()">
Logout
</button>
</ion-buttons>
</ion-navbar>
</ion-header>
在 src/pages/home/home.ts
中,添加一个 logout()
方法, 用于在 identity token 中获取姓名及 claims 。ID token 中的 claims 是关于颁发者、用户、目标受众、过期时间及颁发时间的信息。你可以阅读 OIDC 规范中的标准 claims。
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { LoginPage } from '../login/login';
import { OAuthService } from 'angular-oauth2-oidc';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
constructor(public navCtrl: NavController, public oauthService: OAuthService) {
}
logout() {
this.oauthService.logOut();
this.navCtrl.setRoot(LoginPage);
this.navCtrl.popToRoot();
}
get givenName() {
const claims = this.oauthService.getIdentityClaims();
if (!claims) {
return null;
}
return claims.name;
}
get claims() {
return this.oauthService.getIdentityClaims();
}
}
为了在 home 标签页上展示信息,将以下 HTML 添加到 src/app/home/home.html
文件的第二段之后。
<div *ngIf="givenName">
<hr>
<p>You are logged in as: <b>{{ givenName }}</b></p>
<div class="claims">
<strong>Claims from Identity Token JWT:</strong>
<pre>{{claims | json}}</pre>
</div>
</div>
更新 src/app/home/home.scss
,添加一些 CSS 让原始的 JSON 看起来舒服一点。
page-home {
.claims {
pre {
color: green;
}
}
pre {
border: 1px solid silver;
background: #eee;
padding: 10px;
}
}
现在登录之后你会看到你的姓名及声明信息。
你可以退出之后看一下带标识的登录页。
注意: 你可能注意到退出之后标签页并没有消失。我正在查找 没有正常工作 的原因。
使用 Ionic 在浏览器中开发移动应用是非常酷的事情。很高兴你能看到自己的劳动成果以及优秀的手机应用。但是它的外观和表现还不是原生应用。
为了查看应用程序在不同设备上的效果,你可以运行 ionic serve --lab
。--lab
标识会在浏览器中打开一个页面让你查看在不同设备中的效果。
LoginPage
在加载时会自动聚焦到 email
输入框。为了自动激活键盘,你需要告诉 Cordova 没有用户交互的情况下显示键盘是可以的。你可以在根路径的 config.xml
中添加以下代码。
<preference name="KeyboardDisplayRequiresUserAction" value="false" />
为了模拟或者部署到 iOS 设备上,你需要一个 Mac 以及一个新安装的 Xcode。如果你喜欢在 Windows 中创建 iOS 应用,Ionic 提供了一个 Ionic Package 服务。
确保打开 Xcode 完成安装 ,然后运行 ionic cordova emulate ios
在模拟器中打开应用。
可能会提示你安装 @ionic/cli-plugin-cordova
插件。当出现提示时输入 "y",按回车。
TIP: 我发现在模拟器中运行应用程序时的最大问题是键盘很难弹出。为了解决这一问题,当我需要在输入框输入文本时,我使用 Hardware > Keyboard > Toggle Software Keyboard 。
如果你在登录页输入凭证,可能什么也不会发生。打开 Safari 转到 Develop > Simulator > MyApp / Login,你会看到控制台有一条错误信息。如果你看不到开发菜单,重新执行 这篇文章 中的方法使其生效。
如果打开 Network 标签,你会看到只发送了一条请求 (to /authn
),它和在浏览器中发送的两条请求 (to /authn
and /authorize
) 有所不同。
我相信使用 Cordova 打包 app 之后并不会正常工作,因为通过内嵌的 iframe 向服务端发送请求,然后使用 postMessage 将结果返回当前窗口。Ionic/Cordova 似乎并不支持这种方式。为了解决这个问题,你可以使用 Cordova 提供的 in-app 浏览器直接与 Okta 的 OAuth 服务通信。Nic Raboy 演示了在 Facebook 中的操作方法,他在 Ionic 2 移动 App 中使用了 OAuth 2.0 服务。
使用以下命令安装 Cordova In-App Browser plugin :
ionic cordova plugin add cordova-plugin-inappbrowser
打开 src/app/pages/login/login.html
,用一个 <div>
包裹 <form>
,为了只在浏览器中运行时显示登录表单。添加一个新的 <div>
,它会在模拟器或设备上运行时显示。
<ion-content padding>
<ion-row>
<!-- optional logo -->
</ion-row>
<div showWhen="core">
<form>
...
</form>
</div>
<div hideWhen="core">
<button ion-button full (click)="redirectLogin()">Login with Okta</button>
</div>
</ion-content>
打开 src/pages/login/login.ts
,在 imports 下面添加一个 window
的引用。
declare const window: any;
为了更容易的使用 OAuth 登录,可以添加以下方法。
redirectLogin() {
this.oktaLogin().then(success => {
localStorage.setItem('access_token', success.access_token);
this.oauthService.processIdToken(success.id_token, success.access_token);
this.navCtrl.push(TabsPage);
}, (error) => {
this.error = error;
});
}
oktaLogin(): Promise<any> {
return this.oauthService.createAndSaveNonce().then(nonce => {
let state: string = Math.floor(Math.random() * 1000000000).toString();
if (window.crypto) {
const array = new Uint32Array(1);
window.crypto.getRandomValues(array);
state = array.join().toString();
}
return new Promise((resolve, reject) => {
const oauthUrl = this.buildOAuthUrl(state, nonce);
const browser = window.cordova.InAppBrowser.open(oauthUrl, '_blank',
'location=no,clearsessioncache=yes,clearcache=yes');
browser.addEventListener('loadstart', (event) => {
if ((event.url).indexOf('http://localhost:8100') === 0) {
browser.removeEventListener('exit', () => {});
browser.close();
const responseParameters = ((event.url).split('#')[1]).split('&');
const parsedResponse = {};
for (let i = 0; i < responseParameters.length; i++) {
parsedResponse[responseParameters[i].split('=')[0]] =
responseParameters[i].split('=')[1];
}
const defaultError = 'Problem authenticating with Okta';
if (parsedResponse['state'] !== state) {
reject(defaultError);
} else if (parsedResponse['access_token'] !== undefined &&
parsedResponse['access_token'] !== null) {
resolve(parsedResponse);
} else {
reject(defaultError);
}
}
});
browser.addEventListener('exit', function (event) {
reject('The Okta sign in flow was canceled');
});
});
});
}
buildOAuthUrl(state, nonce): string {
return this.oauthService.issuer + '/oauth2/v1/authorize?' +
'client_id=' + this.oauthService.clientId + '&' +
'redirect_uri=' + this.oauthService.redirectUri + '&' +
'response_type=id_token%20token&' +
'scope=' + encodeURI(this.oauthService.scope) + '&' +
'state=' + state + '&nonce=' + nonce;
}
把在构造器中设置的 redirectUri
替换成硬编码 http://localhost:8100
。如果省略这一步,当 app 在设备上运行时, window.location.origin
会跳转到 file://
。为了将它设置成已知的 URL,我们可以通过 in-app browser 的 "loadstart" 事件查找它。
constructor(private navCtrl: NavController, private oauthService: OAuthService) {
oauthService.redirectUri = 'http://localhost:8100';
...
}
更改之后,需要将 app 重新部署到手机上。
ionic cordova emulate ios
现在可以点击 "Login with Okta" 按钮,然后输入合法的凭证进行登录。
使用这项技术的好处就是 Okta 的登录页具有“记住我”和“忘记密码”的功能,所以不需要自己编写代码。
为了将 app 部署到 iPhone,首先将手机插到电脑上。然后运行以下命令安装 ios-deploy、构建 app 并在你的设备上运行。
npm install -g ios-deploy
ionic cordova run ios
如果你之前没有为应用程序设置代码签名,则此命令可能会失败。
Signing for "MyApp" requires a development team. Select a development team in the project editor.
Code signing is required for product type 'Application' in SDK 'iOS 10.3'
在 Xcode 中打开你的项目,运行以下命令。
open platforms/ios/MyApp.xcodeproj
Ionic's 开发文档 有解决这一问题的说明。
选择你的手机作为 Xcode 的目标,然后点击 play 按钮运行 app。如果你是第一次做,Xcode 可能会加载一段时间,上方会显示一条 "Processing symbol files" 的信息。
只要你已经设置了你的手机、电脑以及 Apple ID,你就可以打开应用并登录。以下是在我的手机上的展示效果。
为了模拟或者部署到 Android 设备上,你首先要安装 Android Studio。在安装过程中,它会提示你将 Android SDK 安装到哪里。将这个路径设置为 ANDROID_HOME 的环境变量。在 Mac 上,it should be ~/Library/Android/sdk/
。
如果你已经安装了Android Studio,请确保打开它以完成安装。
为了部署到 Android 模拟器,运行 ionic cordova emulate android
。这个命令将安装 Android 支持并打印关于如何创建模拟图像的说明。
Error: No emulator images (avds) found.
1. Download desired System Image by running:
/Users/mraible/Library/Android/sdk/tools/android sdk
2. Create an AVD by running: /Users/mraible/Library/Android/sdk/tools/android avd
HINT: For a faster emulator, use an Intel System Image and install the HAXM device driver
运行第一条建议并下载您想要的系统映像。然后运行第二个命令并用以下设置创建一个 AVD(Android 虚拟设备):
AVD Name: TestPhone
Device: Nexus 5
Target: Android 7.1.1
CPU/ABI: Google APIs Intel Axom (x86_64)
Skin: Skin with dynamic hardware controls
警告: 这些设置不适用于 Mac 上的 Android Studio 2.3.2 版本。当你尝试运行第一条命令时,它会显示以下内容:
*************************************************************************
The "android" command is deprecated.
For manual SDK, AVD, and project management, please use Android Studio.
For command-line tools, use tools/bin/sdkmanager and tools/bin/avdmanager
*************************************************************************
为了解决这个问题,打开 Android Studio,选择 "Open an existing Android Studio project",然后选择 ionic-auth/platforms/android
的路径。如果提示升级,选择 "OK",然后继续创建一个新的 AVD ,和 Android Studio 文档描述的那样.
执行完这些步骤之后,你可以运行 ionic cordova emulate android
查看运行在 AVD 中的 app。
注意: 如果应用程序显示错误 "连接服务器失败 (file:///android/www/index.html
)",在 config.xml
中添加以下代码。这行代码将默认超时时间设置为 60 秒 (默认 20)。感谢 Stack Overflow 社区 对此问题的解答。
<preference name="loadUrlTimeoutValue" value="60000"/>
Ionic 支持创建 progressive web apps (PWAs)。这意味着你可以将 Ionic app 部署成 web app (不是移动端 app) ,它可以在离线的 支持 service workers 的浏览器 中运行。
想要了解如何使用 service workers 并把 app 转换成 PWA ,可以阅读 如何使用 Ionic 和 Spring Boot 开发移动应用 的 PWAs 部分 。PWA 是可以安装在系统中的 web 应用程序。它可以在离线情况下工作,使用的是你最后一次与 app 交互的数据缓存。添加 PWA 功能可以让 app 加载更快,提供更好的用户体验。想要了解更多关于 PWA 的知识,可以阅读 The Ultimate Guide to Progressive Web Applications.
我希望你喜欢这篇关于 Ionic、Angular 及 Okta 的教程。我喜欢 Ionic 是因为它可以将你的 web 开发技能提升一个档次,并且它可以快速创建仿原生的移动应用。
你可以在 GitHub 上查看本教程的完整代码。如果你有问题,可以通过 Twitter @mraible 或者在 Okta's Developer Forums 上联系我。
想要了解更多关于 Ionic、Angular 或者 Okta 的知识,可以查看以下资源: