基于Hapi基础认证与Angular2的登录系统开发求助
Hey there! I’ve been right where you are—fumbling through Angular + Hapi auth as a newbie, struggling to find complete, working code examples. Let’s fix that by walking through a full, functional setup step by step.
Hapi Server with hapi-auth-basic
First, let’s lock in your backend. I’ll include CORS setup (critical for Angular) and a basic user validation flow (with a reminder to use password hashing in production!).
Install dependencies:
npm install @hapi/hapi @hapi/basic bcryptCreate your
server.js:const Hapi = require('@hapi/hapi'); const Basic = require('@hapi/basic'); const bcrypt = require('bcrypt'); // In production, pull this from a database! const users = { demo: { username: 'demo', // Hash your passwords first! Run bcrypt.hashSync('demo123', 10) to generate this password: '$2b$10$Z8HxQrOvV8e8yL7X9kM0uOe7R5T3Y1U9I7K3J5H1G3F1D1S1A1', name: 'Demo User', id: 'user-1' } }; // Basic auth validation function const validateUser = async (request, username, password) => { const user = users[username]; if (!user) return { credentials: null, isValid: false }; // Compare submitted password to stored hash const passwordMatch = await bcrypt.compare(password, user.password); const credentials = { id: user.id, name: user.name }; return { isValid: passwordMatch, credentials }; }; const initServer = async () => { const server = Hapi.server({ port: 3000, host: 'localhost', routes: { cors: { origin: ['http://localhost:4200'], // Allow Angular dev server credentials: true } } }); // Register basic auth plugin await server.register(Basic); server.auth.strategy('simple-auth', 'basic', { validate: validateUser }); server.auth.default('simple-auth'); // Apply auth to all routes by default // Public route (for login page access) server.route({ method: 'GET', path: '/api/public', options: { auth: false }, handler: (req, h) => ({ message: 'Public endpoint—no auth needed!' }) }); // Protected route (only accessible after login) server.route({ method: 'GET', path: '/api/protected', handler: (req, h) => ({ message: `Welcome back, ${req.auth.credentials.name}! This is protected content.` }) }); await server.start(); console.log(`Server running at ${server.info.uri}`); }; process.on('unhandledRejection', err => { console.error(err); process.exit(1); }); initServer();
Angular Frontend Setup
Now let’s build the Angular side: auth service, login component, route guards, and protected pages.
1. Auth Service (auth.service.ts)
This handles sending credentials to the backend, persisting login state, and attaching auth headers to requests.
import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { BehaviorSubject, Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class AuthService { private loggedIn$ = new BehaviorSubject<boolean>(false); private apiBaseUrl = 'http://localhost:3000/api'; constructor(private http: HttpClient) { // Check localStorage for saved credentials on app load if (localStorage.getItem('auth-credentials')) { this.loggedIn$.next(true); } } get isLoggedIn(): Observable<boolean> { return this.loggedIn$.asObservable(); } // Send login request to backend login(username: string, password: string): Observable<any> { const encodedCreds = btoa(`${username}:${password}`); const headers = new HttpHeaders({ 'Authorization': `Basic ${encodedCreds}` }); // We hit a protected endpoint to validate credentials return this.http.get(`${this.apiBaseUrl}/protected`, { headers }); } // Update login state and save/remove credentials setLoginStatus(isLoggedIn: boolean, credentials?: string): void { this.loggedIn$.next(isLoggedIn); if (isLoggedIn && credentials) { localStorage.setItem('auth-credentials', credentials); } else { localStorage.removeItem('auth-credentials'); } } logout(): void { this.setLoginStatus(false); } // Get auth headers for protected requests getAuthHeaders(): HttpHeaders { const creds = localStorage.getItem('auth-credentials'); if (creds) { return new HttpHeaders({ 'Authorization': `Basic ${creds}` }); } return new HttpHeaders(); } }
2. Login Component (login.component.ts)
Handles the login form submission and error handling.
import { Component } from '@angular/core'; import { AuthService } from '../auth.service'; import { Router } from '@angular/router'; @Component({ selector: 'app-login', templateUrl: './login.component.html' }) export class LoginComponent { username = ''; password = ''; errorMsg = ''; constructor(private authService: AuthService, private router: Router) {} onSubmit(): void { this.authService.login(this.username, this.password).subscribe({ next: () => { const encodedCreds = btoa(`${this.username}:${this.password}`); this.authService.setLoginStatus(true, encodedCreds); this.router.navigate(['/dashboard']); }, error: () => { this.errorMsg = 'Invalid username or password'; } }); } }
Login component HTML (login.component.html):
<div class="login-form"> <h2>Log In</h2> <form (ngSubmit)="onSubmit()"> <div> <label>Username:</label> <input type="text" [(ngModel)]="username" name="username" required> </div> <div> <label>Password:</label> <input type="password" [(ngModel)]="password" name="password" required> </div> <button type="submit">Login</button> <p class="error" *ngIf="errorMsg">{{ errorMsg }}</p> </form> </div>
3. Route Guard (auth.guard.ts)
Prevents access to protected pages unless the user is logged in.
import { Injectable } from '@angular/core'; import { CanActivate, Router } from '@angular/router'; import { AuthService } from './auth.service'; import { map } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class AuthGuard implements CanActivate { constructor(private authService: AuthService, private router: Router) {} canActivate() { return this.authService.isLoggedIn.pipe( map(isLoggedIn => { if (!isLoggedIn) { this.router.navigate(['/login']); return false; } return true; }) ); } }
4. Routing (app-routing.module.ts)
Set up public and protected routes.
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { LoginComponent } from './login/login.component'; import { DashboardComponent } from './dashboard/dashboard.component'; import { AuthGuard } from './auth.guard'; const routes: Routes = [ { path: '', redirectTo: '/login', pathMatch: 'full' }, { path: 'login', component: LoginComponent }, { path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] }, { path: '**', redirectTo: '/login' } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule {}
5. Protected Dashboard Component
Example of a page that fetches protected data from the backend.
import { Component, OnInit } from '@angular/core'; import { AuthService } from '../auth.service'; import { HttpClient } from '@angular/common/http'; @Component({ selector: 'app-dashboard', templateUrl: './dashboard.component.html' }) export class DashboardComponent implements OnInit { protectedData: any; constructor(private authService: AuthService, private http: HttpClient) {} ngOnInit(): void { this.http.get('http://localhost:3000/api/protected', { headers: this.authService.getAuthHeaders() }).subscribe(data => { this.protectedData = data; }); } logout(): void { this.authService.logout(); this.router.navigate(['/login']); } }
Key Notes for Production
- Password Security: Never store plain text passwords! Use
bcrypt(as shown in the Hapi code) to hash passwords before saving them. - Persistent Login: The example uses
localStoragefor simplicity, but in production, use HttpOnly cookies for better security. - CORS: Make sure to restrict the CORS origin to your production Angular URL (not
*).
内容的提问来源于stack exchange,提问作者Marien

