源代码和演示:
https://github.com/trungk18/angular-authentication-demo-with-lazy-loading
https://stackblitz.com/edit/angular-authentication-demo-with-lazy-loading
我将添加一个节以在我们第一次运行时实现所有其他模块的延迟加载。这意味着只有登录页面将在第一次加载时被加载。登录后,下一个模块将被加载。这将为我们节省大量带宽。
有一个用户,并且我会在成功登录后将整个对象存储到localStorage中。
流程可能是这样的:
打开应用程序
AuthGuard 将被触发。如果在 localStorage 中存在带令牌的用户对象,则激活该路由。否则,将路由返回到登录页面。
当路由被激活并开始向服务器发出 API 请求时,JWTInterceptor 将被触发以在每个后续请求上发送令牌。
ErrorInterceptor 检查是否为 401,如果是,则从 localStorage 中删除用户并重新加载页面。这个处理更多地关注于某人试图手动使用不同的令牌或对象更新 localStorage 的用例。如果令牌正确且没有来自服务器的修改器,则不应发生。
模型
export const ConstValue = {
ReturnUrl: "returnUrl",
CurrentUser: "currentUser",
}
export const ConstRoutingValue = {
Login: "login"
}
export interface AICreateUser {
firstName: string;
lastName: string;
email: string;
password: string;
roleIds: string[]
}
export interface PartnerUser extends AICreateUser {
id: string;
createdAt: string;
token: string;
featureSet: string[]
}
export interface AuthDto {
email: string;
password: string;
}
auth.service
export class AuthService {
private _currentUserSubject: BehaviorSubject<User>;
public currentUser: Observable<User>;
public get currentUserVal(): User {
return this._currentUserSubject.value;
}
get currentUserToken() {
return this.currentUserVal.token;
}
constructor(private http: HttpClient) {
this._currentUserSubject = new BehaviorSubject<User>(this.getUserFromLocalStorage());
this.currentUser = this._currentUserSubject.asObservable();
}
login(username, password) {
return this.http.post<any>(`/users/authenticate`, { username, password })
.pipe(map(user => {
localStorage.setItem(ConstValue.CurrentUser, JSON.stringify(user));
this._currentUserSubject.next(user);
return user;
}));
}
logout() {
localStorage.removeItem(ConstValue.CurrentUser);
this._currentUserSubject.next(null);
}
private getUserFromLocalStorage(): User {
try {
return JSON.parse(localStorage.getItem(ConstValue.CurrentUser)!);
} catch (error) {
return null!;
}
}
}
还有一个JwtInterceptor,可以在每个请求中将令牌附加到头文件中。
export class JwtInterceptor implements HttpInterceptor {
constructor(private authenticationService: AuthService) {}
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
let currentUser = this.authenticationService.currentUserVal;
if (currentUser && currentUser.token) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${currentUser.token}`
}
});
}
return next.handle(request);
}
}
export const JWTInterceptorProvider = {
provide: HTTP_INTERCEPTORS,
useClass: JwtInterceptor,
multi: true
};
定义一个ErrorInterceptor拦截器,检查是否存在401错误码,如果是则从localStorage中删除用户信息并重新加载页面。
export class ErrorInterceptor implements HttpInterceptor {
constructor(private authenticationService: AuthService) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(catchError(err => {
if (err.status === 401) {
this.authenticationService.logout();
location.reload(true);
}
const error = err.error.message || err.statusText;
return throwError(error);
}))
}
}
export const ErrorInterceptorProvider = { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }
还有一个AuthGuard,用于确保在激活路由之前您拥有令牌。 在配置Angular路由器时必须包含它在所有路由中,除了登录页面。
export class AuthGuard implements CanActivate {
constructor(
private router: Router,
private authenticationService: AuthService
) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const currentUserToken = this.authenticationService.currentUserToken;
if (currentUserToken) {
return true;
}
this.router.navigate(['/login'], { queryParams: { [ConstValue.ReturnUrl]: state.url }});
return false;
}
}
如果我想使用用户对象,我将在AuthService中获取currentUser的公共可观察对象。例如,我想在列表上显示用户的名字。
export class AppComponent {
currentUser: User;
constructor(
private router: Router,
private authenticationService: AuthService
) {
this.authenticationService.currentUser.subscribe(
x => (this.currentUser = x)
);
}
logout() {
this.authenticationService.logout();
this.router.navigate(["/login"]);
}
}
我希望你从中得到了想法。