博客系统用户会话管理


placeholder image
admin 发布于:2022-07-23 21:56:37
阅读:loading

背景介绍

用户会话管理的实现可以有Http Session、Spring Session和Jwt等多种方式,考虑到本站的前端与后端隶属两个项目,需要考虑用户会话的单点有效性以及应用的简单等特点,最终使用了Spring Session,再结合有限的云务器内存,最终使用了基于数据库表的JDBC存储的方式,也算是将Spring Session 项目的应用有了更多更深入的理解,借着博客系统2.0版本功能的实现介绍,特来补充一下Spring Session的实现。

前面在Spring Session的案例中基本的使用和进阶进行了具体的实践,仅仅只是demo级别的,实际更全面的应用应该算是在本站2.0的实现过程中得到了更科学的应用,也遇到了各种各样的问题,最终一一有效的解决,也特将此部分的知识进行整理。如下几种场景皆是个人遇到的问题后的解决总结,参考如下所示。

问题列表

1)过滤器默认拦截/*,设置过滤器过滤的路径地址跳过一些静态资源文件路径,如jpg/png/gif/css/js等等,仅仅按需进行拦截,经过分析最终使用的并非高大上的实现,而是将源码中的SessionRepositoryFilterConfiguration类放置在博客项目内部(创建了同名的包路径下的类)进行了改写,设置了过滤器过滤的路径为"*.html" , "*.zip" , "*.exe" , "/system/login" , "/" , "/index.html"等路径,主要是将一些需要获取用户数据的路径进行了拦截,以*.html为主,参考代码如下:

/*
 * Copyright 2012-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.autoconfigure.session;

import java.util.EnumSet;
import java.util.stream.Collectors;

import javax.servlet.DispatcherType;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.web.http.SessionRepositoryFilter;

/**
 * Configuration for customizing the registration of the {@link SessionRepositoryFilter}.
 *
 * @author Andy Wilkinson
 */
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(SessionRepositoryFilter.class)
@EnableConfigurationProperties(SessionProperties.class)
@Slf4j
public class SessionRepositoryFilterConfiguration {

    @Bean
    FilterRegistrationBean<SessionRepositoryFilter<?>> sessionRepositoryFilterRegistration(
            SessionProperties sessionProperties, SessionRepositoryFilter<?> filter) {
        log.warn("注意:已使用SessionRepositoryFilterConfiguration重载实现");
        FilterRegistrationBean<SessionRepositoryFilter<?>> registration = new FilterRegistrationBean<>(filter);
        registration.addUrlPatterns("*.html" , "*.zip" , "*.exe" , "/system/login" , "/" , "/index.html");
        registration.addInitParameter("excludeUrls", "/statics/**");
        registration.setDispatcherTypes(getDispatcherTypes(sessionProperties));
        registration.setOrder(sessionProperties.getServlet().getFilterOrder());
        return registration;
    }

    private EnumSet<DispatcherType> getDispatcherTypes(SessionProperties sessionProperties) {
        SessionProperties.Servlet servletProperties = sessionProperties.getServlet();
        if (servletProperties.getFilterDispatcherTypes() == null) {
            return null;
        }
        return servletProperties.getFilterDispatcherTypes().stream().map((type) -> DispatcherType.valueOf(type.name()))
                .collect(Collectors.collectingAndThen(Collectors.toSet(), EnumSet::copyOf));
    }

}

2)由于本站前端项目使用了Forest接口组件,使得web项目至admin项目发起了许多的接口调用,这部分接口并非用户主动发起的行为,理论上应该排除掉用户会话的请求,即过滤器拦截了*.html的路径,但是需要放行一些Api接口的路径,如/v1/user或/v2/user等以/v + 数字的路径匹配前缀,所以定义一个过滤器api接口放行的过滤器来设置参数的方式u解决,特别主意该过滤器的优先级应高于Spring Session默认的过滤器,在api过滤器中的request上下文设置参数后,在默认的过滤器会根据规则跳过此部分的请求,参考代码如下:

package cn.chendd.blog.base.filters;

import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 排除Api接口请求的spring session生成
 *
 * @author chendd
 * @date 2021/12/21 10:47
 */
public class RequestApiSessionFilter extends OncePerRequestFilter {

    private static final String API_PATH_MATCHER = "/v\\d+/.*?";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String uri = request.getRequestURI();
        if (uri.matches(API_PATH_MATCHER)) {
            request.setAttribute("org.springframework.session.web.http.SessionRepositoryFilter.FILTERED", Boolean.TRUE);
        }
        filterChain.doFilter(request , response);
    }

}

3)本站前端web项目的默认访问地址为“https://www.chendd.cn”,使用到了https协议,同时也支持http协议的地址访问(当访问http时跳转至https),后端admin项目默认访问地址为“http://www.chen.cn:8888”,后端并未启用https协议,用户可以任意从web和admin项目登录,将会涉及到http协议的地址至https,也会存在https协议的地址到http,所以涉及到自定义cookie设置,解决https向http中发送cookie实现spring session的登录,参考代码如下:

/**
 * 自定义cookie设置,解决https向http中发送cookie实现spring session的登录
 * 1.Cookie总是在跨站点请求中发送;
 * 2.不使用安全模式cookie;
 * @return cookie设置
 */
@Bean
public DefaultCookieSerializer defaultCookieSerializer() {
    DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
    //Cookie总是在跨站点请求中发送
    cookieSerializer.setSameSite(SameSiteCookies.NONE.getValue());
    cookieSerializer.setUseHttpOnlyCookie(false);
    cookieSerializer.setCookiePath("/");
    //不使用安全模式cookie
    cookieSerializer.setUseSecureCookie(false);
    ///cookieSerializer.setCookieName("SESSION");
    ///cookieSerializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$");
    return cookieSerializer;
}

实现总结(也许会存在重复啰嗦)

1)Spring Session的过滤器默认拦截/*所有的路径,包含*.css和*.js以及*.jpg/png等图片文件,导致较多的过滤了一些无意义的地址,造成项目页面的访问速度很慢(特别是本站引用Bootstrap后附带了许多的js和css等资源文件),需要编写过滤器跳过此类路径的实现,可以参考博客系统的“requestApiSessionFilter”解决方式,一方面是设置不过滤的路径,另一方面是若过滤了路径则设置跳过具体的过滤逻辑,该过滤器的优先级必须要高于Spring Session的优先级;

2)若Spring Session修改了默认的拦截路径,比如仅仅过滤*.html路径时,若想在过滤*.zip文件前的过滤器中获取用户信息是无法实现的,因为这个*.zip请求并不需要获取用户信息,故拿不到用户,必须使得Spring Session的过滤器也拦截*.zip后,同时*.zip的过滤器优先级要更低,否则当*.zip执行时还未执行Spring Session过滤器,同样拿不到用户信息;

3)小技巧,Spring Session的过滤器默认级别定义为SessionRepositoryFilter.DEFAULT_ORDER,可以考虑该类至项目源代码中增加打印输入请求的URL来分析总共被过滤的路径;可以拷贝SessionRepositoryFilterConfiguration类的代码进行改写过滤器的过滤和放行路径;

4)项目同时存在http和https两种协议的url地址互相访问,spring session需要同时跨协议间的session数据的同步,可参见博客代码中的具体实现(如配置的cookie的相关配置);


 点赞


 发表评论

当前回复:作者

 评论列表


留言区