Spring Security6配置类SecurityFilterChain方法理解学习

配置代码速览

protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception
    {
        return httpSecurity
            // CSRF禁用,因为不使用session
            .csrf(csrf -> csrf.disable())
            // 禁用HTTP响应标头
            .headers((headersCustomizer) -> {
                headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin());
            })
            // 认证失败处理类
            .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
            // 基于token,所以不需要session
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            // 注解标记允许匿名访问的url
            .authorizeHttpRequests((requests) -> {
                permitAllUrl.getUrls().forEach(url -> requests.requestMatchers(url).permitAll());
                // 对于登录login 注册register 验证码captchaImage 允许匿名访问
                requests.requestMatchers("/login", "/register", "/captchaImage").permitAll()
                    // 静态资源,可匿名访问
                    .requestMatchers(HttpMethod.GET, "/", "/*.html", "/**.html", "/**.css", "/**.js", "/profile/**").permitAll()
                    .requestMatchers("/swagger-ui.html", "/v3/api-docs/**", "/swagger-ui/**", "/druid/**").permitAll()
                    // 除上面外的所有请求全部需要鉴权认证
                    .anyRequest().authenticated();
            })
            // 添加Logout filter
            .logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))
            // 添加JWT filter
            .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
            // 添加CORS filter
            .addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
            .addFilterBefore(corsFilter, LogoutFilter.class)
            .build();
    }

常用参数解释

 * anyRequest          |   匹配所有请求路径
 * access              |   SpringEl表达式结果为true时可以访问
 * anonymous           |   匿名可以访问
 * denyAll             |   用户不能访问
 * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)
 * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
 * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
 * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
 * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
 * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
 * permitAll           |   用户可以任意访问
 * rememberMe          |   允许通过remember-me登录的用户访问
 * authenticated       |   用户登录后可访问

逐行逐段分析

            // CSRF禁用,因为不使用session
            .csrf(csrf -> csrf.disable())
            // 禁用HTTP响应标头
            .headers((headersCustomizer) -> {
                headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin());
            })
  • 关闭csrf攻击防御
    默认情况下SpringSecurity开启了csrf攻击防御的功能,这要求请求参数中必须有一个隐藏的_csrf字段,如下:
    在这里插入图片描述
  • 禁用缓存控制:
    通过 cacheControl(cache -> cache.disable()) 设置,确保浏览器不会缓存该响应。这意味着每次请求都会从服务器获取最新数据,而不是使用浏览器缓存。
  • 设置Frame Options为Same Origin:
    通过 frameOptions(options -> options.sameOrigin()) 配置,限制了只有同源页面可以将此页面嵌入到iframe中。这有助于防止点击劫持攻击(clickjacking attacks),提高安全性。

如何理解限制了只有同源页面可以将此页面嵌入到iframe中?

限制只有同源页面可以将此页面嵌入到iframe中意味着只有与当前页面具有相同协议(http或https)、相同域名和相同端口号的页面才能够将当前页面嵌入到它们的iframe中。
具体来说,“同源”指的是以下三个部分完全一致:
协议:比如都是http或者都是https。
域名:比如都是example.com。
端口:比如都是80或443或其他相同的端口号。
这个策略是由浏览器的安全机制——同源策略(Same-Origin Policy)所强制执行的。它是为了保护用户的安全,防止恶意网站通过iframe加载其他站点的内容来实施各种攻击,如点击劫持(clickjacking)。
当设置了frameOptions(options -> options.sameOrigin())后,如果尝试从不同源(如不同的域名、协议或端口)的页面中加载该页面到iframe内,则浏览器会阻止这种行为,并可能在控制台中记录一个安全错误。这样就确保了只有可信的、同源的页面能够显示该页面的内容。

iframe是什么?

iframe 是 HTML (Hypertext Markup Language) 中的一个标签,全称是 Inline Frame 或者
Inner Frame,即内联框架。它的主要功能是在当前网页中嵌入另一个网页或文档。使用
iframe标签可以将一个独立的HTML文档嵌入到另一个HTML文档中,形成一个“文档中的文档”的效果。 主要用途:

  • 在网页中嵌入视频、地图、广告等第三方内容。
  • 在一个页面中展示多个不同来源的内容。
  • 实现模块化设计,将页面的不同部分分离成单独的文件进行管理。
// 认证失败处理类
            .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))

认证失败后进行的处理,下面是ruoyi的处理类实现

@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable
{
    private static final long serialVersionUID = -8970718410437077606L;

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
            throws IOException
    {
        //http状态码401,表示未授权
        int code = HttpStatus.UNAUTHORIZED;
        //构建错误信息,包括尝试访问的URL
        String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI());
        //使用JSON格式向客户端返回错误信息
        ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg)));
    }
}
// 基于token,所以不需要session
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
  • 设置会话管理策略:对Spring Security中的会话管理进行配置。
  • 禁用传统会话创建:通过将sessionCreationPolicy设为STATELESS,指示框架不创建标准的HTTP会话。
  • 适用于无状态API:这种设置适合不需要维护客户端状态的RESTful服务,减少服务器资源消耗。
  • 返回空会话ID:对于每次请求,即使客户端发送了会话ID,服务器也会忽略并视为无状态请求处理。
            // 注解标记允许匿名访问的url
            .authorizeHttpRequests((requests) -> {
                permitAllUrl.getUrls().forEach(url -> requests.requestMatchers(url).permitAll());
                // 对于登录login 注册register 验证码captchaImage 允许匿名访问
                requests.requestMatchers("/login", "/register", "/captchaImage").permitAll()
                    // 静态资源,可匿名访问
                    .requestMatchers(HttpMethod.GET, "/", "/*.html", "/**.html", "/**.css", "/**.js", "/profile/**").permitAll()
                    .requestMatchers("/swagger-ui.html", "/v3/api-docs/**", "/swagger-ui/**", "/druid/**").permitAll()
                    // 除上面外的所有请求全部需要鉴权认证
                    .anyRequest().authenticated();
            })
  • 允许所有permitAllUrl设置的路径访问
    ruoyi的permitAllUrl配置如下:
/**
 * 设置Anonymous注解允许匿名访问的url
 * 
 * @author ruoyi
 */
@Configuration
public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware
{
    // 定义一个静态常量Pattern用于匹配格式化字符串中的占位符
    // 该占位符格式为{(.*?)},即一对大括号内部可以包含任意字符
    private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");

    private ApplicationContext applicationContext;

    private List<String> urls = new ArrayList<>();

    public String ASTERISK = "*";

    @Override
    public void afterPropertiesSet()
    {
        //RequestMappingHandlerMapping负责管理所有带有 @RequestMapping 注解的方法。
        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);

        /**
         * 这个 Map 的键 (key) 是 RequestMappingInfo 类型,表示每个 HTTP 请求的映射信息。
         * 值 (value) 是 HandlerMethod 类型,表示处理特定请求的具体方法及其所属的控制器类。
         * 示例:key  = {GET [/monitor/cache/getKeys/{cacheName}]}
         *     value = com.ruoyi.web.controller.monitor.CacheController#getCacheKeys(String)
         **/
        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();

        map.keySet().forEach(info -> {
            HandlerMethod handlerMethod = map.get(info);

            // 获取方法上边的注解 替代path variable 为 *
            Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class);
            Optional.ofNullable(method).ifPresent(anonymous -> Objects.requireNonNull(info.getPathPatternsCondition().getPatternValues())
                    .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));

            // 获取类上边的注解, 替代path variable 为 *
            Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class);
            Optional.ofNullable(controller).ifPresent(anonymous -> Objects.requireNonNull(info.getPathPatternsCondition().getPatternValues())
                    .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
        });
    }

    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException
    {
        this.applicationContext = context;
    }

    public List<String> getUrls()
    {
        return urls;
    }

    public void setUrls(List<String> urls)
    {
        this.urls = urls;
    }
}

实现为允许所有配置了Anonymous注解的方法访问

  • 对于登录login 注册register 验证码captchaImage 允许匿名访问
  • 静态资源,可匿名访问
  • 除上面外的所有请求全部需要鉴权认证
// 添加Logout filter
            .logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))
  • logout 方法启动了注销配置流程。
  • logoutUrl(“/logout”) 指定了处理注销请求的URL路径为 /logout。
  • logoutSuccessHandler(logoutSuccessHandler) 设置了一个自定义的成功注销处理程序,当用户成功注销后,会调用这个处理器来执行后续操作,例如重定向到登录页面或返回成功消息。
// 添加JWT filter
            .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)

该方法将自定义的JWT filter认证过滤器 authenticationTokenFilter 添加到 Spring Security 的过滤器链中,并确保其在 UsernamePasswordAuthenticationFilter 之前执行。

// 添加CORS filter
            .addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
            .addFilterBefore(corsFilter, LogoutFilter.class)
  • .addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
    将 corsFilter 添加到 JwtAuthenticationTokenFilter 之前。
    确保跨域请求处理在 JWT 认证过滤器执行前完成。
  • .addFilterBefore(corsFilter, LogoutFilter.class)
    再次将 corsFilter 添加到 LogoutFilter 之前。
    确保跨域请求处理在登出过滤器执行前完成。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

呼呼呼呼~~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值