Angular 11 - 使用PHP的JWT认证实例和教程
用户通常通过输入用户名、电子邮件地址和/或密码进行认证,然后被赋予访问各种资源或服务的权限。由于它的存在,认证依赖于维护用户的状态。这似乎违背了HTTP作为一个无状态协议的基本属性。
你的Angular应用程序将与一个生成令牌的后端进行通信。然后Angular应用可以将令牌作为授权头发送给后端,以显示他们已经通过了认证。后台应检查JWT,并根据其有效性授予访问权。
本教程将引导你一步步完成在Angular 11应用程序中开发和实现基于JWT的认证过程。本教程通过在PHP中开发一个后端服务,让你更进一步。
实施
在这一部分,我将向你展示如何使用PHP结合Angular 11客户端来实现JWT认证。尽管原理很清楚,但实施起来却需要熟悉安全的最佳实践。
这里提供的例子并不完整,它缺乏生产服务器会有的几个功能。因此,我不建议将本教程中的源代码用于生产目的。
我将假设你熟悉MySQL、Angular和PHP。你还应该在开发环境中安装[composer]。
第1步:数据库准备
如果你有所有的先决条件,让我们开始建立一个MySQL数据库。我们将使用服务器自带的MySQL客户端。
打开一个终端,输入以下命令来启动客户端。
$ mysql -u root -p
根据你的MySQL配置,在提示下输入密码。
在出现的窗口中,运行以下命令来创建一个数据库。
mysql> create database jwt-database;
在我们先前创建的jwt-database ,创建一个表jwt-users ,如下所示。
mysql> use jwt-database;
mysql> create table `jwt-users` (
`user_id` int auto_increment primary key,
`fullname` varchar(40) ,
`username` varchar(40) ,
`email_address` varchar(40) unique,
`password` varchar(40) not null,
现在,cd ,进入我们先前创建的目录,运行下面的命令。
cd jwt-server
注意:根据你的开发环境,这个路径可能有所不同。
连接到你的数据库
在你的工作目录中,在tokens-api 目录内创建一个文件夹db_configurations 。
cd tokens-api && mkdir configurations
然后。
cd configurations
<?php
class DB_Connection
{
private $db_host = "localhost"; //change to your host
private $db_name = "jwt-database";//change to your db
private $db_username = "root"; //change to your db username
private $db_password = ""; //enter your password
private $conn;
public function db_connect(){
$this->conn = null;
try
{
$this->connection = new PDO("mysql:host=" . $this->db_host . ";dbname=" . $this->db_name, $this->db_user, $this->db_password);
$conn->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
}
catch(PDOException $e){
echo "Error " . $e->getMessage();
}
return $this->connect;
}
}
第2步:安装PHP令牌生成器包
PHP有一个库JWT库,可以用来生成auth令牌以识别访问后台服务的客户。
要在你的系统中安装这个PHP库,你需要安装一个composer。
你可以通过运行以下命令来验证其安装。
composer -v
现在,通过运行下面的命令继续并导入该库。
$ composer require firebase/php-jwt
为了允许我们的PHP后端和angular应用程序之间的通信,我们需要设置CORS头。
让我们继续,创建一个文件header.php ,并添加以下CORS脚本。
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Methods: POST, PUT, DELETE, UPDATE");
header("Access-Control-Allow-Origin: * ");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
第3步:用户注册API端点
现在我们的系统中有了php-jwt 库,让我们继续并创建一个简单的注册系统。在你的当前目录中,添加以下几行代码。
<?php
include_once './configurations/db.php';
include_once './header.php';
$full_name
$email_address = '';
$password1 = '';
$connection = null;
$db = new DB_Configuration();
$connection = $db->db_connect();
$api_data = json_decode(file_get_contents("php://input"));
$full_name = $api_data->full_name;
$email_address = $api_data->email;
$password = $api_data->password;
$query = "INSERT INTO " jwt_users . "
SET full_name = :fname,
email = :emailAdress,
password = :pwd";
$stmt = $conn->prepare($query);
$stmt->bindParam(':fname',$full_name)
$stmt->bindParam(':email', $email_address);
$stmt->bindParam(':password', $password1);
$stmt->execute();
?>
用户登录的API端点
在tokens-api 目录内,制作一个signin.php 文件,并添加下面的代码,以检查客户资格来访问我们的后端服务。
为了验证用户资格并向客户端返回一个JSON Web Token,在tokens-api 目录内建立一个signin.php 文件脚本,并添加以下代码。
<?php
include_once './config/database.php';
require "../vendor/autoload.php";
//dont forget to add header configurations for CORS
use \Firebase\JWT\JWT;
$email_address = '';
$password = '';
$dbService = new DB_Connection();
$connection = $dbService->db_connect();
$api_data = json_decode(file_get_contents("php://input"));
$user_email = $api_data->email_address;
$password = $api_data->password;
$table = 'Users';
$sql = "SELECT user_id,first_name, last_name,`password` FROM " . $table . " WHERE email_address =:email LIMIT 0,1";
$stmt = $conn->prepare( $query );
$stmt->bindParam(':email', $email_address);
$stmt->execute();
$numOfRows = $stmt->rowCount();
if($numOfRows) > 0){
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$user_id = $row['id'];
$first_name = $row['first_name'];
$last_name = $row['last_name'];
$pass = $row['password'];
if(password_verify($password, $pass))
{
$secret_key = "MillerJumaWilliam";
$issuer_claim = "localhost";
$audience_claim = "THE_AUDIENCE";
$issuedat_claim = time(); // time issued
$notbefore_claim = $issuedat_claim + 10;
$expire_claim = $issuedat_claim + 60;
$token = array(
"iss" => $issuer_claim,
"aud" => $audience_claim,
"iat" => $issuedat_claim,
"nbf" => $notbefore_claim,
"exp" => $expire_claim,
"data" => array(
"id" => $id,
"firstName" => $first_name,
"lastName" => $last_name,
"userEmail" => $email_address
));
$jwtValue = JWT::encode($token, $secret_key);
echo json_encode(
array(
"message" => "success",
"token" => $jwtValue,
"email_address" => $email_address,
"expiry" => $expire_claim
));
}
else{
echo json_encode(array("success" => "false"));
}
}
?>
你可以随心所欲地描述令牌的数据结构,但某些保留的JWT语句应该被正确指定,因为它们会影响令牌的有效性。
JWT::encode() 方法将PHP数组转换为JSON格式,对有效载荷进行签名,然后对最终的令牌进行编码,再将其发送给客户端即浏览器。
对于注册和登录用户,我们现在有两个RESTful端点。让我们通过在 "token-api "文件夹中运行以下程序来测试我们的端点是否工作。
cd tokens-api && php -S 127.0.0.1:8000 // to start our development server
现在我们有了一个带有JWT令牌的功能齐全的REST API,让我们继续创建我们的angular项目。
设置angular项目以消耗PHP auth端点
在你的新angular项目中,运行以下命令来创建authService 服务。
$ ng generate service auth
我们将使用这个服务来签署用户进出我们的angular应用程序。让我们把下面的代码添加到我们的auth服务中。
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class AuthService {
public baseUrl = "localhost:8000";
private loggedUserSubject: BehaviorSubject<User>;
public loggedInUser: Observable<any>;
constructor(private httpClient: HttpClient) {
getLoggedUser = JSON.parse(localStorage.getItem('loggedInUser'));
this.loggedUserSubject = new BehaviorSubject(this.getLoggedUser));
this.loggedInUser = this.loggedUserSubject.asObservable();
}
loginUser(emailAddress: string, password: string) {
return this.http.post<any>(`${baseUrl}/`, { emailAddress, password })
.pipe(map(response=> {
localStorage.setItem('loggedInUser', JSON.stringify(response));
this.loggedUserSubject.next(response);
console.log(response);
return response;
}));
}
logoutUser() {
localStorage.removeItem('loggedInUser');
this.loggedUserSubject.next(null);
}
public get loggedInUserValue(){
return this.loggedUserSubject.value;
}
}
在上面的auth服务中,当用户签入和签出系统时,RxJS Subjects 和Observables 被用来存储当前用户。
设置登录组件
现在我们已经有了一个向我们的PHP端点发送HTTP请求的服务,让我们继续,通过运行以下命令创建一个登录组件来测试我们的代码。
$ ng g c login
在你的新组件模板中,复制并粘贴以下代码。
<div class="col-md-6 offset-md-3 mt-5">
<div class="card">
<h4 class="card-header">Authentication Form</h4>
<div class="card-body">
<form [formGroup]="signinForm" (ngSubmit)="onSubmit()">
<div class="form-group">
<label for="email">Email Address</label>
<input type="text" formControlName="email" class="form-control"/>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" formControlName="password" class="form-control"/>
</div>
<button class="btn btn-danger">
Sign In
</button>
</form>
</div>
</div>
</div>
我们上面创建的表单使用了Angular's Reactive Forms Module 。当点击事件被触发时,用户信息会被发送到我们的组件。
我们的登录模板准备好了,在你的login.compnent.ts 文件中,添加以下代码片断来获得用户输入。
正是在这个脚本中,用户的值被捕获,然后通过我们的auth服务发送到我们先前创建的API服务。
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { AuthService } from './service/auth.service';
@Component({
templateUrl: 'login.component.html'
})
export class LoginComponent implements OnInit {
signinForm: FormGroup;
constructor(
private fb: FormBuilder,
private authService: AuthService
) { }
ngOnInit() {
this.signinForm = this.fb.group({
email: [null, [Validators.required, Validators.email]],
password: [null, Validators.required]
});
}
get form()
{
return this.signinForm.controls;
}
onSubmit() {
this.authService.loginUser(this.form.email.value, this.form.password.value)
.subscribe(
data => {
console.log(data);
},
error => {
console.log(error);
});
}
}
在本地存储中存储登录令牌
Angular带有HTTP拦截器。因此,任何请求都会被传递一个令牌,该令牌将被用于我们的后端,以验证用户的有效性。
让我们继续前进,为我们的应用程序创建一个拦截器,AuthInterceptor ,运行以下命令。
$ ng g interceptor auth
...
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable } from 'rxjs';
import { AuthService } from './service/auth.module';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let loggedInUser = this.authService.currentUserValue;
token = JSON.parse(localStorage.getItem(user.token));
if (token) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
}
return next.handle(request);
}
}
现在让我们继续前进,在我们的app.module.ts 中添加这个脚本,以确保我们发送的任何请求都是克隆的,并附上令牌。
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AppComponent } from './app.component';
import { appRoutingModule } from './app.routing';
import { AuthInterceptor} from 'helpers/AuthInterceptor';
import { DashboardComponent } from './dashboard';
import { LoginComponent } from './login';
@NgModule({
imports: [
BrowserModule,
ReactiveFormsModule,
HttpClientModule,
appRoutingModule
],
declarations: [
AppComponent,
DashboardComponent,
LoginComponent
],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true
],
bootstrap: [AppComponent]
})
export class AppModule { }
好了,让我们通过运行以下命令来启动我们的angular应用程序。
ng serve --open //starts on port 4200 by default unless you specify otherwise
现在你可以向我们的PHP端点发出请求并登录,同时生成的令牌被存储在浏览器的本地存储中。
总结
在本教程中,我们已经学会了如何在我们的Angular 11应用程序中使用JWT认证与PHP RESTful APIs。我们还实现了其他认证策略,如在你的Angular应用程序中使用令牌认证。