前言
一个网站,通常都会包含公开页面和保护页面两种,如果是OA或者企业应用网站,甚至可能全部都是保护页面,访问者需要在进行身份认证后,才能正常的浏览相关页面。
路由进阶应用
在上一篇 Angular 2.0 SPA应用 - 从脚手架开始 (1) 文章中,我们介绍了如何从一个脚手架Angular 2.0开始,添加一个首页和登录页面,并实现了相关的路由功能。
本文中,我们将会添加一个邮件发送页面,同时,希望只有登录用户可以访问此页面。
- 路由守卫(Route Guard)
添加AuthGuard,随机返回True/False(分别为50%概率)。
import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private router: Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
console.log('AuthGuard#canActivate called');
if (this.checkLogin()) {
// l已登录,返回Ture
console.log("AuthGuard: 用户已登陆。");
return true;
}
// 未登陆,重定向URL到登录页面,包含返回URL参数,然后返回False
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }});
return false;
}
private checkLogin(): boolean {
//随机返回Ture /False
let loggedIn:boolean = Math.random() < 0.5;
if(!loggedIn){
console.log("AuthGuard: 用户未登陆。");
}
return loggedIn;
}
}
- 邮件组件 (MailComponent)
//mail.component.ts
import {Component} from '@angular/core'
@Component({
selector: 'mail',
moduleId: __moduleName,
template: `
<div class="container" style="margin-top:100px;">
<h1>Mail Page</h1>
<div>
`
})
export class MailComponent {
}
- 修改app.ts
添加MailComponent和AuthGuard引用,添加MailComponent路由并对MailComponent使用AuthGuard守护。
......
import { MailComponent } from './mail/mail.component';
import { AuthGuard } from './login/auth.guard.ts';
const appRoutes: Routes = [
......
{ path: 'mail', component: MailComponent, canActivate: [AuthGuard] },
......
];
@NgModule({
......
declarations: [ App, HomeComponent, LoginComponent, MailComponent ],
providers: [ AuthGuard ],
......
})
新增或修改的代码主要功能是:
- canActivate属性声明路由守卫(Route Guard)
- providers属性提供依赖注入(Dependency Injection)。
- 修改app.template.html
在原来的Home菜单旁边,添加Mail菜单
<ul class="nav navbar-nav">
<li><a routerLink="home" routerLinkActive="active">首页</a></li>
<li><a routerLink="mail" routerLinkActive="active">Mail</a></li>
</ul>
- canActivate属性声明路由守卫(Route Guard)
- providers属性声明依赖注入(Dependency Injection)。
身份验证
在上面的AuthGuard中,我们并没有真正实现用户身份的验证,只是随机返回True/False来模拟用户已登录或未登陆状态下,访问守护页面时,路由导航应有的反应。
将验证逻辑从AuthGuard分离,实现一个authenticationService,应该包含以下功能:
- 是否已通过身份验证 isAuth
- 登录身份验证 Login(username, password)
- 注销当前登录 Logout()
- ** 添加 src/auth/authentication.service.ts **
import { Injectable } from '@angular/core';
@Injectable()
export class AuthenticationService {
isAuth() {
if (localStorage.getItem('currentUser')) {
return true;
}
else { return false; }
}
login(username: string, password: string) {
if (username=='admin' && password=="admin") {
localStorage.setItem('currentUser', username);
return true;
}
else {
return false;
}
}
logout() {
// remove user from local storage to log user out
localStorage.removeItem('currentUser');
}
}
好吧,我必须承认我偷懒,现在还是假的验证逻辑,登陆用户名和密码都是"admin",就通过验证。这样一来,我们可以先不管复杂的后端验证逻辑,先修改并测试前端登录界面。
- ** 修改 src/login/authGuard.ts **
import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AuthenticationService } from '../auth/authentication.service.ts'
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private router: Router, private authService: AuthenticationService) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
console.log('AuthGuard#canActivate called');
if (this.authService.isAuth()) {
// l已登录,返回Ture
return true;
}
// 未登陆,重定向URL到登录页面,包含返回URL参数,然后返回False
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }});
return false;
}
}
......
- ** Login 组件**
login.template.html
<div class="container">
<div id="loginbox" style="margin-top:100px;" class="mainbox col-md-6 col-md-offset-3 col-sm-8 col-sm-offset-2">
<div class="panel panel-info">
<div class="panel-heading">
<div class="panel-title">Login</div>
</div>
<form name="form" (ngSubmit)="f.form.valid && login()" #f="ngForm" novalidate>
<div style="padding:30px" class="panel-body">
<div class="form-group" [ngClass]="{ 'has-error': f.submitted && !username.valid }">
<label for="username">Username</label>
<input type="text" class="form-control" name="username" [(ngModel)]="model.username" #username="ngModel" required />
<div *ngIf="f.submitted && !username.valid" class="help-block">Username is required</div>
</div>
<div class="form-group" [ngClass]="{ 'has-error': f.submitted && !password.valid }">
<label for="password">Password</label>
<input type="password" class="form-control" name="password" [(ngModel)]="model.password" #password="ngModel" required />
<div *ngIf="f.submitted && !password.valid" class="help-block">Password is required</div>
</div>
<div class="form-group">
<button [disabled]="loading" class="btn btn-primary">Login</button>
<img *ngIf="loading" src="data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA=="
/>
</div>
</div>
</form>
</div>
</div>
</div>
login.component.ts
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { AuthenticationService } from '../auth/authentication.service';
@Component({
selector: 'login',
moduleId: __moduleName,
templateUrl: './login.template.html'
})
export class LoginComponent {
model: any = {};
loading = false;
returnUrl: string;
constructor(private route: ActivatedRoute, private router: Router, private authService: AuthenticationService) {}
ngOnInit() {
// reset login status
this.authService.logout();
// get return url from route parameters or default to '/'
this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
}
login() {
this.loading = true;
if (this.authService.login(this.model.username, this.model.password)) {
this.router.navigate([this.returnUrl]);
}
else {
this.loading = false;
}
}
}
由于LoginComponent.ts的构造函数需要 AuthenticationService 依赖注入,我们需要回过头去,修改app.ts文件,加入相关代码,这儿就不详细写出来,请读者自行摸索一下。
总结
在本文中,学习了Route Guard,加入身份认证,登录界面做了修改,基本可以使用了,还多次使用依赖注入。如果你对这些知识点还有不清楚的地方,建议可以到 Angular 2.0 查阅文档。
Plunker Demo
下篇预告
在两篇文章中,基本完成了Angular SPA常用的功能介绍,貌似太快了一点点。
下篇写啥呢,有点失去方向,身份认证继续发展,就是引入后端服务的时候,要么介绍一下Interception和mockBackendService技术,如果你有什么建议和意见,不妨告诉我,谢谢!