以通用角度向服务器发送令牌 [英] send token to server in angular universal

查看:54
本文介绍了以通用角度向服务器发送令牌的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个Angular 6+应用程序,该应用程序配置为使用 Angular Universal 利用服务器端渲染.br/>我还使用了 TransferState 以避免服务器和客户端应用上重复的API调用.

I have an Angular 6+ app which is configured to utilize server side rendering using Angular Universal.
I also used TransferState to avoid duplicate API calls on the server and client app.

我的角度应用程序中的身份验证基于令牌.

Authentication in my angular app is based on token.

问题是用户第一次打开我的Web应用程序,结果是为未经过身份验证的用户呈现index.html,而用户实际上已登录,但没有机会将令牌传输到服务器.因此,当客户端应用与服务器应用交换时,由于浏览器的localStorage/sessionStorage中存在令牌.

The problem is in the first time the user opens my web app, which results to rendering index.html for a user which is not authenticated whereas the user actually is logged in but there is no opportunity to transfer the token to the server. So, when the client app swapped with server app, it is required to call APIs again because of the existance of token in the browser's localStorage/sessionStorage.

我使用node.js和express.js来实现服务器端渲染.

I used node.js and express.js to implement server side rendering.

我认为解决方案是使用会话和cookie.这对我来说需要大量的工作,因为我不熟悉用于处理会话/cookie的node.js.有没有快速简便的解决方案?

I think the solution is to use session and cookies. This requires a lot of work for me since I'm not familiar with node.js to handle sessions/cookies. Is there any fast and easy solution?

推荐答案

对于其他面临相同问题的人,这是解决方案.

For others facing the same problem, here is the solution.

客户端应用应将服务器端呈现所需的状态数据(例如身份验证信息)保存在浏览器Cookie中.浏览器将在第一个请求的标头中自动发送cookie,以获取 index.html .然后,在 server.js 中,我们应该使用 renderModuleFactory 的 extraProviders 从请求标头中提取cookie,并将其传递给 server-app 代码>.

client-app should save the state data required for server side rendering (e.g. authentication info) in the browser cookies. Browser will automatically sends cookies in header of the first request to fetch index.html. Then in the server.js we should extract cookie from request header and pass it to server-app using extraProviders of renderModuleFactory.

我们需要的第一件事是处理浏览器cookie的服务.我宣布了一个灵感来自这篇文章( github存储库链接)

First thing we need is a service to deal with browser cookies. I declared one inspired from this post (github repo link)

import {Injectable} from '@angular/core';

@Injectable()
export class CookieManager {

    getItem(cookies, sKey): string {
        if (!sKey) {
            return null;
        }
        return decodeURIComponent(cookies.replace(new RegExp(
            '(?:(?:^|.*;)\\s*' +
            encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, '\\$&') +
            '\\s*\\=\\s*([^;]*).*$)|^.*$'), '$1'
            )
        ) || null;
    }

    setItem(cookies, sKey, sValue, vEnd?, sPath?, sDomain?, bSecure?): string {
        if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) {
            return cookies;
        }
        let sExpires = '';
        if (vEnd) {
            switch (vEnd.constructor) {
                case Number:
                    sExpires = vEnd === Infinity ? '; expires=Fri, 31 Dec 9999 23:59:59 GMT' : '; max-age=' + vEnd;
                    break;
                case String:
                    sExpires = '; expires=' + vEnd;
                    break;
                case Date:
                    sExpires = '; expires=' + vEnd.toUTCString();
                    break;
            }
        }
        return encodeURIComponent(sKey) + '=' + encodeURIComponent(sValue) + sExpires +
            (sDomain ? '; domain=' + sDomain : '') + (sPath ? '; path=' + sPath : '') + (bSecure ? '; secure' : '');
    }

    removeItem(cookies, sKey, sPath?, sDomain?): string {
        if (!this.hasItem(cookies, sKey)) {
            return cookies;
        }
        return encodeURIComponent(sKey) + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT' +
            (sDomain ? '; domain=' + sDomain : '') + (sPath ? '; path=' + sPath : '');
    }

    hasItem(cookies, sKey): boolean {
        if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) {
            return false;
        }
        return (new RegExp('(?:^|;\\s*)' +
            encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, '\\$&') + '\\s*\\='
        )).test(cookies);
    }

    keys(cookies) {
        const aKeys = cookies.replace(
            /((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, ''
        ).split(/\s*(?:\=[^;]*)?;\s*/);
        for (let nLen = aKeys.length, nIdx = 0; nIdx < nLen; nIdx++) {
            aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]);
        }
        return aKeys;
    }
}

接下来,我们将数据(我们要传递到 server-app 的数据)保存在浏览器的Cookie中

Next we save data (which we want to pass to the server-app) in cookie of browser

@Injectable()
export class AuthenticationService {
    constructor(private http: HttpClient, 
                private cookieManager: CookieManager, 
                @Inject(BROWSER) private browser: BrowserInterface) { }

    login(username: string, password: string) {
        return this.http.post<any>(`${apiUrl}/users/authenticate`, { username: username, password: password })
            .pipe(tap(user => {
                if (user && user.token) {
                    // store authentication details in local storage and browser cookie
                    this.browser.document.localStorage.setItem('authenticatedUser', JSON.stringify(user));
                    this.saveInCookies('authenticatedUser', user)
                }
            }));
    }

    private saveInCookies(key, data){
        const document = this.browser.document;
        let cookieStorage = this.cookieManager.getItem(document.cookie, 'storage');
        cookieStorage = cookieStorage ? JSON.parse(cookieStorage) : {};
        cookieStorage[key] = data;
        document.cookie = this.cookieManager.setItem(document.cookie, 'storage', JSON.stringify(cookieStorage));
    }
}

最后在 server.ts 中提取令牌并将其传递给 server-app :

Finally in server.ts extract token and pass it to server-app:

app.engine('html', (_, options, callback) => {
    // extract request cookie    
    const cookieHeader = options.req.headers.cookie;
    renderModuleFactory(AppServerModuleNgFactory, {
        document: template,
        url: options.req.url,
        extraProviders: [
            provideModuleMap(LAZY_MODULE_MAP),
            // pass cookie using dependency injection
            {provide: 'CLIENT_COOKIES', useValue: cookieHeader}  
        ]
    }).then(html => {
        callback(null, html);
    });
});

并在以下服务中使用提供的cookie:

and use the provided cookie in a service like this:

import {Inject} from '@angular/core';

export class ServerStorage {

    private clientCookies: object;

    constructor(@Inject('CLIENT_COOKIES') clientCookies: string,
                cookieManager: CookieManager) {
        const cookieStorage = cookieManager.getItem(clientCookies, 'storage');
        this.clientCookies = cookieStorage ? JSON.parse(cookieStorage) : {};
    }

    clear(): void {
        this.clientCookies = {};
    }

    getItem(key: string): string | null {
        return this.clientCookies[key];
    }

    setItem(key: string, value: string): void {
        this.clientCookies[key] = value;
    }
}

app.server.module.ts 的提供程序中,使用 StubBrowser

In the providers of app.server.module.ts use the ServerStorage in the StubBrowser

providers: [
    {provide: BROWSER, useClass: StubBrowser, deps: [ServerStorage]},
]



这是我使用的存根浏览器,窗口和文档:



Here is the stub browser, window and document I used:

@Injectable()
export class StubBrowser implements BrowserInterface {

    public readonly window;

    constructor(localStorage: ServerStorage) {
        this.window = new StubWindow(localStorage);
    }


    get document() {
        return this.window.document;
    }

    get navigator() {
        return this.window.navigator;
    }

    get localStorage() {
        return this.window.localStorage;
    }
}

class StubWindow {
    constructor(public localStorage: ServerStorage) {
    }

    readonly document = new StubDocument();
    readonly navigator = {userAgent: 'stub_user_agent'};
}

class StubDocument {
    public cookie = '';
}

这篇关于以通用角度向服务器发送令牌的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆