以角度通用的方式将令牌发送到服务器 [英] send token to server in angular universal

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

问题描述

我有一个 Angular 6+ 应用,它被配置为使用 Angular Universal 来利用服务器端渲染.
我还使用了 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.

我的 Angular 应用中的身份验证基于令牌.

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.这需要我做很多工作,因为我不熟悉 node.js 来处理会话/cookie.有什么快速简便的解决方案吗?

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.

client-app 应将服务器端呈现所需的状态数据(例如身份验证信息)保存在浏览器 cookie 中.浏览器会自动在第一个请求的头部发送 cookie 来获取 index.html.然后在 server.js 中,我们应该从请求头中提取 cookie 并使用 renderModuleFactoryextraProviders 将其传递给 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 repo 链接)

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;
    }
}

接下来我们在浏览器的 cookie 中保存数据(我们想要传递给 server-app)

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天全站免登陆