احراز هویت و اعتبارسنجی کاربران در برنامههای Angular - قسمت دوم - سرویس اعتبارسنجی
نویسنده: وحید نصیری
تاریخ: ۱۳۹۶/۰۹/۲۶ ۸:۵
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
import { InjectionToken } from "@angular/core";
export let APP_CONFIG = new InjectionToken<string>("app.config");
export interface IAppConfig {
apiEndpoint: string;
loginPath: string;
logoutPath: string;
refreshTokenPath: string;
accessTokenObjectKey: string;
refreshTokenObjectKey: string;
}
export const AppConfig: IAppConfig = {
apiEndpoint: "http://localhost:5000/api",
loginPath: "account/login",
logoutPath: "account/logout",
refreshTokenPath: "account/RefreshToken",
accessTokenObjectKey: "access_token",
refreshTokenObjectKey: "refresh_token"
}; import { AppConfig, APP_CONFIG } from "./app.config";
@NgModule({
providers: [
{ provide: APP_CONFIG, useValue: AppConfig }
]
})
export class CoreModule {} ng g s Core/services/Auth
create src/app/Core/services/auth.service.ts (110 bytes)
import { AuthService } from "./services/auth.service";
@NgModule({
providers: [
// global singleton services of the whole app will be listed here.
BrowserStorageService,
AuthService,
{ provide: APP_CONFIG, useValue: AppConfig }
]
})
export class CoreModule {} import { BehaviorSubject } from "rxjs/BehaviorSubject";
@Injectable()
export class AuthService {
private authStatusSource = new BehaviorSubject<boolean>(false);
authStatus$ = this.authStatusSource.asObservable();
constructor() {
this.updateStatusOnPageRefresh();
}
private updateStatusOnPageRefresh(): void {
this.authStatusSource.next(this.isLoggedIn());
} export enum AuthTokenType {
AccessToken,
RefreshToken
} import { BrowserStorageService } from "./browser-storage.service";
export enum AuthTokenType {
AccessToken,
RefreshToken
}
@Injectable()
export class AuthService {
private rememberMeToken = "rememberMe_token";
constructor(private browserStorageService: BrowserStorageService) { }
rememberMe(): boolean {
return this.browserStorageService.getLocal(this.rememberMeToken) === true;
}
getRawAuthToken(tokenType: AuthTokenType): string {
if (this.rememberMe()) {
return this.browserStorageService.getLocal(AuthTokenType[tokenType]);
} else {
return this.browserStorageService.getSession(AuthTokenType[tokenType]);
}
}
deleteAuthTokens() {
if (this.rememberMe()) {
this.browserStorageService.removeLocal(AuthTokenType[AuthTokenType.AccessToken]);
this.browserStorageService.removeLocal(AuthTokenType[AuthTokenType.RefreshToken]);
} else {
this.browserStorageService.removeSession(AuthTokenType[AuthTokenType.AccessToken]);
this.browserStorageService.removeSession(AuthTokenType[AuthTokenType.RefreshToken]);
}
this.browserStorageService.removeLocal(this.rememberMeToken);
}
private setLoginSession(response: any): void {
this.setToken(AuthTokenType.AccessToken, response[this.appConfig.accessTokenObjectKey]);
this.setToken(AuthTokenType.RefreshToken, response[this.appConfig.refreshTokenObjectKey]);
}
private setToken(tokenType: AuthTokenType, tokenValue: string): void {
if (this.rememberMe()) {
this.browserStorageService.setLocal(AuthTokenType[tokenType], tokenValue);
} else {
this.browserStorageService.setSession(AuthTokenType[tokenType], tokenValue);
}
}
}
{"access_token":"...","refresh_token":"..."} export interface Credentials {
username: string;
password: string;
rememberMe: boolean;
} @Injectable()
export class AuthService {
constructor(
@Inject(APP_CONFIG) private appConfig: IAppConfig,
private http: HttpClient,
private browserStorageService: BrowserStorageService
) {
this.updateStatusOnPageRefresh();
}
login(credentials: Credentials): Observable<boolean> {
const headers = new HttpHeaders({ "Content-Type": "application/json" });
return this.http
.post(`${this.appConfig.apiEndpoint}/${this.appConfig.loginPath}`, credentials, { headers: headers })
.map((response: any) => {
this.browserStorageService.setLocal(this.rememberMeToken, credentials.rememberMe);
if (!response) {
this.authStatusSource.next(false);
return false;
}
this.setLoginSession(response);
this.authStatusSource.next(true);
return true;
})
.catch((error: HttpErrorResponse) => Observable.throw(error));
}
} @Injectable()
export class AuthService {
constructor(
@Inject(APP_CONFIG) private appConfig: IAppConfig,
private http: HttpClient,
private router: Router
) {
this.updateStatusOnPageRefresh();
}
logout(navigateToHome: boolean): void {
this.http
.get(`${this.appConfig.apiEndpoint}/${this.appConfig.logoutPath}`)
.finally(() => {
this.deleteAuthTokens();
this.unscheduleRefreshToken();
this.authStatusSource.next(false);
if (navigateToHome) {
this.router.navigate(["/"]);
}
})
.map(response => response || {})
.catch((error: HttpErrorResponse) => Observable.throw(error))
.subscribe(result => {
console.log("logout", result);
});
}
} > npm install jwt-decode --save
import * as jwt_decode from "jwt-decode";
getDecodedAccessToken(): any {
return jwt_decode(this.getRawAuthToken(AuthTokenType.AccessToken));
} {
"jti": "d1272eb5-1061-45bd-9209-3ccbc6ddcf0a",
"iss": "http://localhost/",
"iat": 1513070340,
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": "1",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "Vahid",
"DisplayName": "وحید",
"http://schemas.microsoft.com/ws/2008/06/identity/claims/serialnumber": "709b64868a1d4d108ee58369f5c3c1f3",
"http://schemas.microsoft.com/ws/2008/06/identity/claims/userdata": "1",
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role": [
"Admin",
"User"
],
"nbf": 1513070340,
"exp": 1513070460,
"aud": "Any"
} getDisplayName(): string {
return this.getDecodedAccessToken().DisplayName;
} getAccessTokenExpirationDateUtc(): Date {
const decoded = this.getDecodedAccessToken();
if (decoded.exp === undefined) {
return null;
}
const date = new Date(0); // The 0 sets the date to the epoch
date.setUTCSeconds(decoded.exp);
return date;
} isAccessTokenTokenExpired(): boolean {
const expirationDateUtc = this.getAccessTokenExpirationDateUtc();
if (!expirationDateUtc) {
return true;
}
return !(expirationDateUtc.valueOf() > new Date().valueOf());
} isLoggedIn(): boolean {
const accessToken = this.getRawAuthToken(AuthTokenType.AccessToken);
const refreshToken = this.getRawAuthToken(AuthTokenType.RefreshToken);
const hasTokens = !this.isEmptyString(accessToken) && !this.isEmptyString(refreshToken);
return hasTokens && !this.isAccessTokenTokenExpired();
}
private isEmptyString(value: string): boolean {
return !value || 0 === value.length;
} constructor() {
this.updateStatusOnPageRefresh();
} //const hasTokens = !this.isEmptyString(accessToken) && !this.isEmptyString(refreshToken); *
const hasTokens = !this.isEmptyString(accessToken); this.scheduleRefreshToken();
POST http://localhost:5000/api/account/login 500 (Internal Server Error) Failed to load http://localhost:5000/api/account/login: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:4200' is therefore not allowed access. The response had HTTP status code 500.
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
@Injectable()
export class AppConfigService {
private config: IAppConfig;
constructor(private http: HttpClient) { }
loadClientConfig(): Promise<any> {
return this.http.get<IAppConfig>("assets/client-config.json")
.toPromise()
.then(config => {
this.config = config;
console.log("Config", this.config);
})
.catch(err => {
return Promise.reject(err);
});
}
get configuration(): IAppConfig {
if (!this.config) {
throw new Error("Attempted to access configuration property before configuration data was loaded.");
}
return this.config;
}
}
export interface IAppConfig {
apiEndpoint: string;
loginPath: string;
logoutPath: string;
refreshTokenPath: string;
accessTokenObjectKey: string;
refreshTokenObjectKey: string;
adminRoleName: string;
} import { NgModule, Optional, SkipSelf, APP_INITIALIZER } from "@angular/core";
import { CommonModule } from "@angular/common";
import { RouterModule } from "@angular/router";
import { HTTP_INTERCEPTORS } from "@angular/common/http";
// import RxJs needed operators only once
import "./services/rxjs-operators";
import { HeaderComponent } from "./component/header/header.component";
import { AuthGuard } from "./services/auth.guard";
import { AuthInterceptor } from "./services/auth.interceptor";
import { AuthService } from "./services/auth.service";
import { AppConfigService } from "./services/app-config.service";
import { BrowserStorageService } from "./services/browser-storage.service";
@NgModule({
imports: [CommonModule, RouterModule],
exports: [
// components that are used in app.component.ts will be listed here.
HeaderComponent
],
declarations: [
// components that are used in app.component.ts will be listed here.
HeaderComponent
],
providers: [
// global singleton services of the whole app will be listed here.
BrowserStorageService,
AppConfigService,
AuthService,
AuthGuard,
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
},
{
provide: APP_INITIALIZER,
useFactory: (config: AppConfigService) => () => config.loadClientConfig(),
deps: [AppConfigService ],
multi: true
}
]
})
export class CoreModule {
constructor( @Optional() @SkipSelf() core: CoreModule) {
if (core) {
throw new Error("CoreModule should be imported ONLY in AppModule.");
}
}
} loadClientConfig(httpClient: HttpClient): Promise<any> { {
provide: APP_INITIALIZER,
useFactory: (config: AppConfigService, httpClient: HttpClient) => () => config.loadClientConfig(httpClient),
deps: [AppConfigService, HttpClient],
multi: true
}, constructor(
private injector: Injector,
@Inject(APP_CONFIG) private appConfig: IAppConfig
) {}
loadApiConfig(): Promise<any> {
const http = this.injector.get<HttpClient>(HttpClient);
const url = `${this.appConfig.apiEndpoint}/${
this.appConfig.apiSettingsPath
}`;
return http
.get<IApiConfig>(url)
.toPromise()
.then(config => {
this.config = config;
console.log('ApiConfig', this.config);
})
.catch(err => {
console.error(
`Failed to loadApiConfig(). Make sure ${url} is accessible.`,
this.config
);
return Promise.reject(err);
});
} {
provide: APP_INITIALIZER,
useFactory: (config: ApiConfigService) => () => config.loadApiConfig(),
deps: [ApiConfigService],
multi: true
} const http = this.injector.get<HttpClient>(HttpClient);
No provider for HttpClient هم یعنی تنظیمات اولیه ماژولها را کامل نکردید (در دو قسمت imports و exports).