Angular 11 - JWT认证实例和PHP教程

249 阅读4分钟

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 SubjectsObservables 被用来存储当前用户。

设置登录组件

现在我们已经有了一个向我们的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应用程序中使用令牌认证。