1

JSESSIONID当用户关闭浏览器窗口时,我需要关闭由 cookie 持有的会话。此 cookie 由 Spring Security 在服务器端设置:

@Configuration
@EnableWebSecurity
public class SpringSecurityConfiguration {

    @Configuration
    public static class ApplicationApiSecurityConfiguration extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(final HttpSecurity http) {
        http
                    .antMatcher("/**")
          .csrf()
          .disable() // Disable Cross Site Request Forgery Protection
          .formLogin() // Enable form based/cookie based/session based login
          .loginPage(LOGIN_PAGE) // Set our custom login page
          .and()
            .authorizeRequests()
            .antMatchers("/login", "/logout")
            .permitAll() // Allow anyone to access the login and logout page
            .anyRequest()
            .authenticated() //All other request require authentication
          .and()
            .logout()
            .deleteCookies("JSESSIONID") // Delete JSESSIONID cookie on logout
            .clearAuthentication(true) // Clean authentication on logout
            .invalidateHttpSession(true); // Invalidate Http Session on logout
        }
    }
}

在客户端,我有 AngularJS 1.7 和 TypeScript,我已经beforeunload通过设置来捕获事件@module.ts

module app.myapp {

    import IModule = angular.IModule;

    import IStateProvider = angular.ui.IStateProvider;
    import IUrlRouterProvider = angular.ui.IUrlRouterProvider;

    import LockService = lib.common.LockService;

    export class MyApp {

        static NAME = 'app.myapp';

        static module(): IModule {
            return angular.module(MyApp.NAME);
        }
    }

    angular.module(MyApp.NAME, ['ui.router'])
        .config([
            '$stateProvider', '$urlRouterProvider',
            ($stateProvider: IStateProvider, $urlRouterProvider: IUrlRouterProvider) => {

                $urlRouterProvider.when('', '/home');

                $stateProvider.state('base', {
                    url: '',
                    abstract: true,
                    template: '<ui-view/>',
                    resolve: {
                        initEventListenerForGlobalUnlock: [
                            LockService.ID,
                            (lockService: LockService) => lockService.initListenerForGlobalUnlock()
                        ]
                    }
                });

                $stateProvider.state('personalProfile', {
                    url: '/profile',
                    parent: 'base',
                    component: PersonalProfile.ID
                });
            }
        ]);
}

然后对于我的 LockService 实现:

module lib.common {

    import IWindowService = angular.IWindowService;
    import IRootScopeService = angular.IRootScopeService;

    export class LockService {

        static ID = 'lockService';
        static $inject: string[] = ['$window', '$rootScope'];

        constructor(
            private readonly $window: IWindowService,
            private readonly $rootScope: IRootScopeService
        ) { }

        private readonly globalUnlockHandler = (ev: BeforeUnloadEvent) => {
            // This does not work with HttpOnly cookies
            document.cookie = 'JSESSIONID=;path=/;domain=' + window.location.hostname + ';expires=Thu, 01 Jan 1970 00:00:01 GMT';
            this.$rootScope.$digest();
            return null;
        }

        /**
         * This is called upon app initialisation,
         * an event listener for browser close is registered,
         * which will use an API call to unlock all locked sections before exiting
         */
        initListenerForGlobalUnlock(): void {
            this.$window.addEventListener('beforeunload', this.globalUnlockHandler);
        }
    }

    LibCommon.module().service(LockService.ID, LockService);
}

目前,有一个基于重定向页面的注销功能this.$window.location.href = 'logout'

但是我想要的是在通过单击[x]关闭浏览器窗口时删除会话 cookie(或者以某种方式使会话无效),这样即使用户通过在地址栏中粘贴 URL 回到同一页面,他被要求重新登录。

问题是JSESSIONIDcookie 设置为 HttpOnly,因此无法从 JavaScript 中删除。而且我不知道如何告诉服务器端的 Spring Security 使会话无效。

4

1 回答 1

0

回答我自己的问题。

我发现在浏览器窗口关闭时实现会话过期的唯一方法是对globalUnlockHandleronBeforeUnload 事件侦听器内的注销 URL 执行同步 XHR 请求。

AngularJS$http.get不起作用可能是因为它是异步的,并且调用在完成之前被取消。

下面是传统的跨浏览器 XHR 对象创建的 TypeScript 示例:

createXMLHttpRequest(): XMLHttpRequest {
    var req: XMLHttpRequest = null;
    if (this.$window.XMLHttpRequest) {
        try {
            req = new XMLHttpRequest();
        } catch (e) { }
    } else if (this.$window.ActiveXObject) {
        try {
            req = new ActiveXObject('Msxml2.XMLHTTP'); // tslint:disable-line
        } catch (e) {
            try {
                req = new ActiveXObject('Microsoft.XMLHTTP'); // tslint:disable-line
            } catch (e) { }
        }
    } // fi
    if (!req) {
        console.warn('createXMLHttpRequest() failed');
    }
    return req;
}

然后拨打电话:

var xhr = new XMLHttpRequest();
if (xhr) {
    xhr.open('GET', 'http://www.domain.name/logout', false);
    try {
        xhr.send(null);
    } catch (e) {
        console.warn(e);
    }
}

请注意,XHR 已被弃用,并且这种方法违反 HTTP 原则,因为用户可以打开一个新的浏览器选项卡并从现有的 URL 复制/粘贴。然后,如果第一个选项卡关闭,则会话将终止,但第二个选项卡仍将打开而没有有效会话。

于 2020-01-12T08:35:05.873 回答