转载:Ionic 4 JWT Authentication Tutorial: Using Angular HttpClient with Node & Express.js Server
本教程演示如何使用ionic4和angular7创建登陆注册module。
将学习如何使用HttpClient发生Post请求到使用Node+Express.js创建的鉴权后台,使用RxJS Observable跟踪授权状态;
如何使用Ionic Storage module保存Express.js 返回的JWT信息,如token、过期时间等;
开始之前,需要确定Node.js和NPM安装到开发电脑上;
设置安装 Ionic CLI 4
npm install -g ionic
全局安装ionic一般需要管理员权限,win使用管理员权限的命令行,类linux使用sudo
创建Ionic 4 项目
命令行运行下面命令
ionic start ionic-auth-demo blank --type=angular
ionic4使用type指定前端框架为angular,后面可能会有vue,react,现在反正没有。ionic4的目标是不限制框架,甚至是原生js,jquery都可以用ionic创建混合移动app或者pwa
blank指定模板类型,这个模板只带一个叫home的页面,其他还有tabs。。。
如果你想指定其他cli还能干的事情,如安装cordova,安装免费Ionic Appflow SDK这些后面教程再讲。
等cli安装完依赖后,你就可以进入创建的目录,使用ionic serve运行体验app了,ionic serve会自动打开浏览器访问localhost:8100,如果愿意,加个-l,运行起来更酷哦
创建 Angular Module
modules是用来组织你的程序代码的。创建一个module封装鉴权功能的service和pages。
ionic generate module auth
运行上面命令将自动生成src/app/auth/auth.module.ts,修改代码如下:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
declarations: [],
imports: [
CommonModule
]
})
export class AuthModule { }
auth Module只需要引入CommonModule,这个angular内建模块包含angular基本的一些 directives、pipes如:ngif,decimalPipe等
还需要再我们的root application模块(src/app/app.module.ts)引入auth模块。打开src/app/app.module.ts,再导入数组中添加AuthModule:
import { AuthModule } from './auth/auth.module';
@NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule,
AuthModule
],
providers: [
StatusBar,
SplashScreen,
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
],
bootstrap: [AppComponent]
})
export class AppModule {}
再导入HttpClient,发送http请求,FormsModule表单,Ionic Storage module本地存储
导入HttpClient
HttpClient是angular官方http客户端,所有我们需要在基于angular的ionic里导入。打开src/app/auth/auth.module.ts导入如下:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
@NgModule({
declarations: [],
imports: [
CommonModule,
HttpClientModule
]
})
export class AuthModule { }
设置表单模块
angular为表单提供了强大的api,如基于模板的表单和reactive表单,这里我们用基于模板的表单,添加src/app/auth/auth.module.ts如下:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';
@NgModule({
declarations: [],
imports: [
CommonModule,
HttpClientModule,
FormsModule
]
})
export class AuthModule { }
设置存储模块
ionic team提供的存储模块可以在移动设备的浏览器本地存储,但是使用前必须先安装并引入
安装使用命令:
npm install --save @ionic/storage
写这教程的时候,版本是2.2.0.
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';
import { IonicStorageModule } from '@ionic/storage';
@NgModule({
declarations: [],
imports: [
CommonModule,
HttpClientModule,
FormsModule,
IonicStorageModule.forRoot()
]
})
export class AuthModule { }
创建Angular鉴权服务
设置了所有需要的设置后,我们可以创建鉴权服务了,用来封装HttpClient与Express server的通讯,如下命令创建:
ionic generate interface auth/user
生成src/app/auth/user.ts,编辑如下:
export interface User {
id: number;
name: string;
email: string;
password: string;
}
注意:在命令行里的auth/前缀告诉CLI在auth module里生成。
还需要生成服务器响应的接口,
ionic generate interface auth/auth-response
export interface AuthResponse {
user: {
id: number,
name: string,
email: string
} ,
access_token: string,
expires_in: number
}
具体怎么响应,在下一个部分,用Node+Express.js构建后台的时候再说。
ionic generate service auth/auth
生成auth服务,会产生两个文件:src/app/auth/auth.service.ts,src/app/auth/auth.service.spec.ts,多个spec的是用来自动化测试的,可以删除
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { tap } from 'rxjs/operators';
import { Observable, BehaviorSubject } from 'rxjs';
import { Storage } from '@ionic/storage';
import { User } from './user';
import { AuthResponse } from './auth-response';
定义变量
AUTH_SERVER_ADDRESS: string = 'http://localhost:3000';authSubject = new BehaviorSubject(false);
AUTH_SERVER_ADDRESS是后台地址,authSubject是用来订阅鉴权状态的Observable类型变量。
下一步构造函数中注入HttpClient和Storage
constructor(private httpClient: HttpClient, private storage: Storage) { }
使用HttpClient发送Post请求
register(user: User): Observable<AuthResponse> {
return this.httpClient.post<AuthResponse>(`${this.AUTH_SERVER_ADDRESS}/register`, user).pipe(
tap(async (res: AuthResponse ) => {
if (res.user) {
await this.storage.set("ACCESS_TOKEN", res.access_token);
await this.storage.set("EXPIRES_IN", res.expires_in);
this.authSubject.next(true);
}
})
);
}
login(user: User): Observable<AuthResponse> {
return this.httpClient.post(`${this.AUTH_SERVER_ADDRESS}/login`, user).pipe(
tap(async (res: AuthResponse) => {
if (res.user) {
await this.storage.set("ACCESS_TOKEN", res.access_token);
await this.storage.set("EXPIRES_IN", res.expires_in);
this.authSubject.next(true);
}
})
);
}
async logout() {
await this.storage.remove("ACCESS_TOKEN");
await this.storage.remove("EXPIRES_IN");
this.authSubject.next(false);
}
获取鉴权状态
最后添加isLoggedIn()方法用来检查用户登陆与否
isLoggedIn() {
return this.authSubject.asObservable();
}
创建Ionic Pages
创建页面使用如下命令:
ionic generate page auth/register
命令会自动更新src/app/app-routing.module.ts,添加如下路由:
{ path: 'register', loadChildren: './auth/register/register.module#RegisterPageModule' },
意思是可以直接通过http://localhost:8100/register访问注册页面
打开src/app/auth/register/register.page.ts文件,注入Authservice和Router
import { Component, OnInit } from '@angular/core';
import { Router } from "@angular/router";
import { AuthService } from '../auth.service';
@Component({
selector: 'app-register',
templateUrl: './register.page.html',
styleUrls: ['./register.page.scss'],
})
export class RegisterPage implements OnInit {
constructor(private authService: AuthService, private router: Router) { }
ngOnInit() {
}
}
添加register()方法
register(form) {
this.authService.register(form.value).subscribe((res) => {
this.router.navigateByUrl('home');
});
}
修改src/auth/register/register.page.html页面:
<ion-contentcolor="primary"><form#form="ngForm"(ngSubmit)="register(form)"><ion-grid><ion-rowcolor="primary"justify-content-center><ion-colalign-self-centersize-md="6"size-lg="5"size-xs="12"><divtext-center><h3>Create your account!</h3></div><divpadding><ion-item><ion-inputname="name"type="text"placeholder="Name"ngModelrequired></ion-input></ion-item><ion-item><ion-inputname="email"type="email"placeholder="your@email.com"ngModelrequired></ion-input></ion-item><ion-item><ion-inputname="password"type="password"placeholder="Password"ngModelrequired></ion-input></ion-item><ion-item><ion-inputname="confirm"type="password"placeholder="Password again"ngModelrequired></ion-input></ion-item></div><divpadding><ion-buttonsize="large"type="submit"[disabled]="form.invalid"expand="block">Register</ion-button></div></ion-col></ion-row></ion-grid></form></ion-content>
编辑src/auth/register/register.page.scss,自定义UI:
ion-item{--background:#3880ff;--color:#fff;}ion-button{--background:#062f77;}
登陆页面略,详见:Ionic 4 Forms Tutorial: Login & Register UI Example with Theming
创建并运行后台服务
创建express-auth-demo文件夹,命令行进入当前文件夹后,运行
npm init -y
初始化package.json文件,然后运行下面命令添加依赖:
npm install --save express body-parser sqlite3 bcryptjs jsonwebtoken cors
然后创建index.js文件,添加如下代码:
"use strict";
const express=require('express');
const bodyParser=require('body-parser');
const cors=require('cors');
const sqlite3=require('sqlite3').verbose();
const jwt=require('jsonwebtoken');
const bcrypt=require('bcryptjs');
const SECRET_KEY="secretkey23456";
const app=express();
constrouter=express.Router();app.use(cors())router.use(bodyParser.urlencoded({extended:false}));router.use(bodyParser.json());constdatabase=newsqlite3.Database("./my.db");constcreateUsersTable=()=>{constsqlQuery=`
CREATE TABLE IF NOT EXISTS users (
id integer PRIMARY KEY,
name text,
email text UNIQUE,
password text)`;returndatabase.run(sqlQuery);}constfindUserByEmail=(email,cb)=>{returndatabase.get(`SELECT * FROM users WHERE email = ?`,[email],(err,row)=>{cb(err,row)});}constcreateUser=(user,cb)=>{returndatabase.run('INSERT INTO users (name, email, password) VALUES (?,?,?)',user,(err)=>{cb(err)});}createUsersTable();router.get('/',(req,res)=>{res.status(200).send('This is an authentication server');});router.post('/register',(req,res)=>{constname=req.body.name;constemail=req.body.email;console.log(req.body);constpassword=bcrypt.hashSync(req.body.password);createUser([name,email,password],(err)=>{if(err)returnres.status(500).send("Server error!");findUserByEmail(email,(err,user)=>{if(err)returnres.status(500).send('Server error!');constexpiresIn=24*60*60;constaccessToken=jwt.sign({id:user.id},SECRET_KEY,{expiresIn:expiresIn});res.status(200).send({"user":user,"access_token":accessToken,"expires_in":expiresIn});});});});router.post('/login',(req,res)=>{constemail=req.body.email;constpassword=req.body.password;findUserByEmail(email,(err,user)=>{if(err)returnres.status(500).send('Server error!');if(!user)returnres.status(404).send('User not found!');constresult=bcrypt.compareSync(password,user.password);if(!result)returnres.status(401).send('Password not valid!');constexpiresIn=24*60*60;constaccessToken=jwt.sign({id:user.id},SECRET_KEY,{expiresIn:expiresIn});res.status(200).send({"user":user,"access_token":accessToken,"expires_in":expiresIn});});});app.use(router);constport=process.env.PORT||3000;constserver=app.listen(port,()=>{console.log('Server listening at http://localhost:'+port);});
具体代码意思,查看Node Express JWT Authentication — jsonwebtoken and bcryptjs
编辑package.json,添加如下:
"scripts": {
"start": "node index.js"
},
运行 npm start启动后台服务