這是我們的根路由表
const routes: Routes = [
{
path: 'login',
loadChildren: () => import('./login/login.module').then(m => m.LoginModule)
},
{
path: 'manage',
loadChildren: () => import('./manage/manage.module').then(m => m.ManageModule)
},
{ path: '', redirectTo: '/login', pathMatch: 'full' },
{ path: '**', component: NotfoundComponent },
];
一般而言login頁面的功能不需要任何權限就能使用,而manage要登入後才能使用
那這個時候我們可以做一個路由守衛來做判斷,所以先產生一個路由守衛
ng g g auth
接著會產生路由守衛的基本格式檔
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot,
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return true;
}
這個裡面就可以寫你想要判斷的邏輯,那我們這邊就來判斷使用者是否有登入
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot,
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
const jwt = localStorage.getItem('jwt');
if (jwt) {
const payload = JSON.parse(window.atob(jwt.split('.')[1]));
const exp = new Date(Number(payload.exp) * 1000);
if (new Date() > exp) {
alert('JWT已過期,請重新登入');
return false;
}
} else {
alert('尚未登入');
return false;
}
return true;
}
這邊的邏輯是,我先判斷localStorage是否存有jwt,如果沒有代表沒登入,跳個提示,並回傳false
如果回傳false將不會進到該頁面,反之true才可以正常進入
那如果有jwt,我們就將它base64解碼,並取出到期時間,如果大於現在時間代表過期了,就跳個提示,一樣回傳false
接著我們將我們做好的路由守衛,擺在我們想判斷的路由位置todo-routing.module.ts
const routes: Routes = [
{
path: 'list',
component: TodoListComponent,
resolve: { dataList: TodoListResolver }
},
{
path: 'content/:id',
children: [
{
path: ':action',
component: TodoContentComponent,
canActivate: [AuthGuard],
resolve: { todoList: TodoResolver }
},
{ path: '', redirectTo: 'All', pathMatch: 'full' }
]
},
{ path: '', redirectTo: 'list', pathMatch: 'full' }
];
我們將canActivate: [AuthGuard]
放在TodoContentComponent
這邊,此時可以在沒有權限下,試試登入這一頁
這時候會發現一件事,雖然有正確的做出判斷,跳出了提示,但我頁面就卡在原地無法進去
所以通常我們會有UrlTree
的寫法來寫
constructor(private router: Router) { }
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot,
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
const jwt = localStorage.getItem('jwt');
if (jwt) {
const payload = JSON.parse(window.atob(jwt.split('.')[1]));
const exp = new Date(Number(payload.exp) * 1000);
if (new Date() > exp) {
alert('JWT已過期,請重新登入');
return this.router.createUrlTree(['/login']);
}
} else {
alert('尚未登入');
return this.router.createUrlTree(['/login']);
}
return true;
}
我們注入Router
,並在本來回傳false的地方,改回傳this.router.createUrlTree(['/login']);
這樣就會變成轉址到這個路由,也就是說如果回傳false,將會停止進入路由,但這效果可能不是我們這邊想要的
因為使用者看起來會覺得程式當掉的感覺,所以我們改成將沒有權限的人返回登入頁
接著我們登入的路由守衛,應該是判斷整個manage才對,所以我們將守衛移到app-routing.module.ts
const routes: Routes = [
{
path: 'login',
loadChildren: () => import('./login/login.module').then(m => m.LoginModule)
},
{
path: 'manage',
canActivate: [AuthGuard],
loadChildren: () => import('./manage/manage.module').then(m => m.ManageModule)
},
{ path: '', redirectTo: '/login', pathMatch: 'full' },
{ path: '**', component: NotfoundComponent },
];
但其實這樣並不是正確的設置,因為這樣設置它其實只判斷第一次載入manage
我們進來頁面後,子路由頁面的切換,路由守衛是不會進行判斷
canActivate
只會判斷設置的那個位置的進入,兒子的切換是不會做判斷的
所以會變成,當你沒了權限,你依然可以切換子路由,大家可以自己試一試看看,我這邊的登入權限大概一分鐘至兩分鐘會失效
所以我們要改一個設定方式,這邊angular有提供另一個canActivateChild的設定方式
這樣的設定,可以在子路由切換時都做判斷,但我們這邊要回頭修改一下auth.guard.ts
canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> {
return this.canActivate(childRoute, state);
}
增加一個canActivateChild
,開頭也要多implements一個CanActivateChild
export class AuthGuard implements CanActivate, CanActivateChild{}
那如果你子路由沒有額外的判斷,我們return的地方就只要回傳跟canActivate
一樣的判斷就好時,就可以直接回傳this.canActivate(childRoute, state)
即可
最後我們將路由表修改一下設置
const routes: Routes = [
{
path: 'login',
loadChildren: () => import('./login/login.module').then(m => m.LoginModule)
},
{
path: 'manage',
canActivateChild: [AuthGuard],
loadChildren: () => import('./manage/manage.module').then(m => m.ManageModule)
},
{ path: '', redirectTo: '/login', pathMatch: 'full' },
{ path: '**', component: NotfoundComponent },
];
這樣進入後再等個一分鐘試試看,就會發現一換頁就會被導回登入的頁面
如此就簡單完成了一個是否有登入權限的判斷,那未來可能會有更多的權限判斷,就可以用這個方法,依照自己的需求去打造對應路由守衛
這邊要特別提一下,如果是剛接觸web應用或前後端分離的新手,可能會有個誤區
就是,這是權限驗證的功能是吧?我們是否就是用這個方式禁止沒有權限的人進入我的管理頁面?
答案是否定的,簡單來說,前端所有的驗證,都不是在禁止使用者進入或使用某個沒有權限的功能頁面
充其量其實只是提升使用者體驗的一環,因為前端的程式都是在你電腦上跑的
所以我想要修改什麼都可以直接修改,前端程式並沒有能力真正禁止使用者的行為
也就是說上面的路由守衛,其實只是讓正常的使用者知道,喔~我現在沒有登入,然後我們程式就貼心的幫使用者導到登入畫面
不會說今天誤入的沒有權限的頁面,然後就卡死在這邊,而是會貼心地告訴使用者原因,並做導向到正確的頁面
所以說今天路由守衛並不是防止你進入這頁,而是要貼心的提醒你沒這頁的權限,並引導你至正確的頁面的功用
也就是為什麼,我上面的jwt僅僅只判斷有沒有到期而已,因為我並不是真的要判斷這個jwt是否是真的有效的
只是在判斷正常使用者的情況下,jwt如果到期就代表沒權限了
至於自己修改一個假的但沒有到期的jwt,雖然能過得了路由守衛,但過不了後端的權限驗證
此時,可能因為沒寫對應的判斷,畫面可能會卡在那裡,不過我們也沒必要提升"非正常使用者"的體驗,因此不用理這塊
結論,所有的權限驗證都是後端在負責的,前端的驗證,只是在提升使用者體驗而已
前端只要負責,在正常使用者使用的情況下,可以順順的操作即是好的設計
範例檔:下載