Spring Security实战(七)—— 跨域请求伪造的防护及单点登录

一、CSRF

        CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种常见的网络攻击方式,它利用用户在已登录网站的情况下,通过伪造请求(例如在另一个网站中的图片或链接中),向该网站发送恶意请求,从而在用户不知情的情况下执行某些操作,例如修改密码、发送私信等。

为了防止 CSRF 攻击,您可以采取以下措施:

  1. 验证请求来源:在服务器端对请求来源进行验证,如果请求来源与预期不一致,可以拒绝该请求。例如,在 Spring Security 中,可以通过启用 CSRF 防护来自动验证请求来源。

  2. 使用随机的 CSRF 令牌:在每个表单或请求中包含一个随机的 CSRF 令牌,服务器端对该令牌进行验证,如果令牌与预期不一致,可以拒绝该请求。例如,在 Spring Security 中,可以使用 CSRF Token 来实现此功能。

  3. 限制敏感操作的访问:对于某些敏感操作,例如修改密码、发送私信等,可以限制只有已登录用户才能进行操作,或者对该操作进行二次验证,例如要求用户输入密码或短信验证码。

  4. 避免使用 GET 请求进行敏感操作:GET 请求通常被用来获取资源,而 POST 请求通常被用来提交数据。如果使用 GET 请求进行敏感操作,可能会因为浏览器或代理服务器缓存请求结果,导致攻击者可以通过预加载的缓存来伪造请求。

二、使用Spring Security防御CSRF攻击

        CSRF攻击完全是基于浏览器进行的,如果我们的系统前端并非在浏览器中运作,就应当关闭CSRF。

(1)添加 CSRF Token:Spring Security 可以自动生成 CSRF Token,并将其添加到所有的表单请求和非 GET 请求中。在服务器端进行验证,如果 Token 不匹配,则拒绝该请求。可以通过以下代码配置:

protected void configure(HttpSecurity http) throws Exception {
    http
        .csrf()
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}

(2)SameSite Cookie:SameSite Cookie 是一种可以限制 Cookie 跨站点访问的技术,可以有效地防止 CSRF 攻击。Spring Security 支持配置 SameSite Cookie,可以通过以下代码配置:

protected void configure(HttpSecurity http) throws Exception {
    http
        .csrf()
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            .requireCsrfProtectionMatcher(new RequestMatcher() {
                private final Pattern allowedMethods = Pattern.compile("^(GET|HEAD|TRACE|OPTIONS)$");
                private final RegexRequestMatcher unprotectedMatcher = new RegexRequestMatcher("/unprotected", null);

                @Override
                public boolean matches(HttpServletRequest request) {
                    if (allowedMethods.matcher(request.getMethod()).matches()) {
                        return false;
                    }

                    if (unprotectedMatcher.matches(request)) {
                        return false;
                    }

                    return true;
                }
            });
    http
        .headers()
            .httpStrictTransportSecurity()
                .includeSubDomains(true)
                .maxAgeInSeconds(31536000)
                .and()
            .xssProtection()
                .block(true)
                .and()
            .contentTypeOptions()
                .nosniff()
                .and()
            .cacheControl()
                .disable();
    http
        .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    http
        .authorizeRequests()
            .antMatchers("/unprotected")
                .permitAll()
            .anyRequest()
                .authenticated();
    http
        .exceptionHandling()
            .authenticationEntryPoint(authenticationEntryPoint());
}

(3)验证请求来源:Spring Security 提供了 CsrfFilter 过滤器,该过滤器可以验证请求来源是否与当前页面的源相同。如果请求来源与当前页面的源不同,则可能是 CSRF 攻击,应该拒绝该请求。可以通过以下代码启用 CsrfFilter

protected void configure(HttpSecurity http) throws Exception {
    http
        .csrf()
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            .requireCsrfProtectionMatcher(new RequestMatcher() {
                private final Pattern allowedMethods = Pattern.compile("^(GET|HEAD|TRACE|OPTIONS)$");
                private final RegexRequestMatcher unprotectedMatcher = new RegexRequestMatcher("/unprotected", null);

                @Override
                public boolean matches(HttpServletRequest request) {
                    if (allowedMethods.matcher(request.getMethod()).matches()) {
                        return false;
                    }

                    if (unprotectedMatcher.matches(request)) {
                        return false;
                    }

                    return true;
                }
            });
}

(4)添加验证码

这种方式是在表单中添加一个验证码字段,要求用户在提交表单之前输入验证码。这样可以防止 CSRF 攻击,因为攻击者无法知道正确的验证码。可以通过自定义过滤器实现。例如:

public class CaptchaFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 如果是登录请求,需要检查验证码
        if ("/login".equals(request.getRequestURI()) && "POST".equalsIgnoreCase(request.getMethod())) {
            String code = request.getParameter("code"); // 获取用户输入的验证码
            String sessionCode = (String) request.getSession().getAttribute("code"); // 获取正确的验证码
            if (!StringUtils.isEmpty(code) && code.equals(sessionCode)) {
                filterChain.doFilter(request, response); // 验证码正确,继续处理请求
                return;
            } else {
                response.sendRedirect("/login?error=invalidCode"); // 验证码不正确,跳转回登录页
                return;
            }
        }

        filterChain.doFilter(request, response); // 不是登录请求,直接继续处理
    }
}

然后再spring security的配置中添加:

http.addFilterBefore(new CaptchaFilter(), UsernamePasswordAuthenticationFilter.class);

 三、单点登录 

        单点登录(Single Sign-On,简称 SSO)是一种身份认证机制,允许用户使用一组凭证(如用户名和密码)登录到多个应用程序或系统,而无需多次输入凭证。

        在传统的身份认证机制中,每个应用程序或系统都要求用户输入一次用户名和密码。但是,对于使用多个应用程序或系统的用户来说,这样的流程很麻烦和不必要。使用单点登录可以让用户只需登录一次,就可以在多个应用程序或系统中使用,提高用户的使用体验和工作效率。

        单点登录通常由一个认证中心(Authentication Server)来实现。用户在认证中心进行身份验证,并获得一个令牌(Token)。用户访问其他应用程序或系统时,令牌将被发送给该应用程序或系统,以进行身份验证。如果令牌有效,则用户被授权访问该应用程序或系统,否则用户需要重新登录。

        使用单点登录可以提高安全性,因为用户只需要输入凭证一次,减少了凭证泄露的风险;也可以降低开发和维护成本,因为多个应用程序或系统可以共享认证机制和用户信息。

四、CAS

        CAS (Central Authentication Service) 是一种开源的单点登录协议和实现,也是一种常见的单点登录解决方案。CAS 由耶鲁大学开发,是基于 HTTP 协议实现的中心化身份认证系统。

        CAS 通过一个统一的认证中心(CAS Server)来管理用户的身份认证信息。用户在第一次访问需要进行身份认证的应用程序时,将被重定向到 CAS Server 进行身份认证。如果身份认证成功,CAS Server 会向用户颁发一个票据(Ticket),同时在本地保存一个 Session,并将票据返回给用户的浏览器。用户在访问其他需要身份认证的应用程序时,将使用浏览器中保存的票据向 CAS Server 进行认证。如果票据有效,则用户被授权访问该应用程序。

1. CAS 的特点包括:

  • 开源:CAS 是一种开源的单点登录解决方案,可以自由下载、修改和使用。
  • 安全:CAS 使用加密算法来保证用户的凭证安全,同时支持多种身份认证方式,如用户名密码、LDAP、Active Directory、OAuth 等。
  • 可扩展:CAS 支持多种认证协议,如 CAS、OAuth、OpenID 等,可以与不同的应用程序和系统集成。
  • 易于部署:CAS 可以与不同的 Web 容器和应用程序集成,同时提供了多种客户端库,便于开发人员集成 CAS。
  • 可靠性:CAS 支持多种负载均衡和故障转移机制,可以保证高可用性和可靠性。

总之,CAS 是一种成熟、稳定、安全、可扩展、易于部署和集成的单点登录解决方案。

        CAS由CAS Server和CAS Client两部分组成。CAS Server是一个单点的验证服务,CAS Client是共享CAS Server登录态的客户端。例如:阿里巴巴旗下的淘宝,天猫,在CAS结构中都属于客户端。

2. 三个术语:TGT,TGC,ST。

  • TGT (Ticket Granting Ticket) 是一种用于表示用户身份的票据,用于表示用户在 CAS Server 中的身份认证信息,它是 CAS Server 中唯一的一个票据。在用户进行身份认证成功后,CAS Server 会向用户的浏览器颁发一个 TGT,同时在服务器端保存一份,用于后续的票据颁发和验证。
  • TGC (Ticket Granting Cookie) 是一个用于保存 TGT 的 Cookie,当用户在 CAS Server 中进行身份认证成功后,CAS Server 会将 TGT 返回给用户的浏览器,并在浏览器中设置一个 TGC。TGC 的作用是用于在浏览器和 CAS Server 之间建立会话,并将 TGT 与浏览器关联起来,从而避免在每次请求时都需要重新进行身份认证。
  • ST (Service Ticket) 是一种用于表示用户对某个应用程序的访问权限的票据,用于表示用户已经经过身份认证,并被授权访问某个应用程序。在用户访问某个需要进行身份认证的应用程序时,CAS Server 会向用户颁发一个 ST,用户可以使用 ST 向应用程序进行身份认证。在用户进行身份认证后,应用程序会将 ST 与 CAS Server 进行验证,从而确认用户的身份和授权信息。

3. CAS 单点登录的完整步骤如下:

  1. 用户访问某个需要进行身份认证的应用程序。
  2. 应用程序判断用户未登录,则跳转到 CAS Server 的登录页面,CAS Server 会向用户展示登录表单,要求用户输入用户名和密码进行身份认证。
  3. 用户输入用户名和密码进行身份认证。
  4. CAS Server 验证用户的身份信息是否正确,如果验证通过,则在服务器端生成一个 TGT,并将 TGT 返回给用户的浏览器,并在浏览器中设置一个 TGC(Ticket Granting Cookie),用于在浏览器和 CAS Server 之间建立会话,并将 TGT 与浏览器关联起来,从而避免在每次请求时都需要重新进行身份认证。
  5. 用户访问另一个需要进行身份认证的应用程序。
  6. 应用程序判断用户未登录,则跳转到 CAS Server 的登录页面,CAS Server 在浏览器中检查是否存在有效的 TGC,如果存在,则说明用户已经经过 CAS Server 的身份认证,并且已经具有了 TGT,此时 CAS Server 会向用户颁发一个 ST(Service Ticket),并将 ST 返回给用户的浏览器。
  7. 用户使用 ST 向应用程序进行身份认证。
  8. 应用程序将 ST 与 CAS Server 进行验证,从而确认用户的身份和授权信息,如果验证通过,则用户被认为已经登录了应用程序。

需要注意的是,当用户在 CAS Server 中进行身份认证成功后,CAS Server 会向用户颁发一个 TGT,同时在服务器端保存一份,用于后续的票据颁发和验证。在用户访问另一个需要进行身份认证的应用程序时,CAS Server 会向用户颁发一个 ST,用户可以使用 ST 向应用程序进行身份认证。在用户进行身份认证后,应用程序会将 ST 与 CAS Server 进行验证,从而确认用户的身份和授权信息。此外,为了提高安全性,CAS 还支持单点登出功能,即用户在一个应用程序中注销后,可以自动注销所有与 CAS Server 建立了信任关系的应用程序,避免用户在多个应用程序中反复注销。

猜你喜欢

转载自blog.csdn.net/weixin_49561506/article/details/130350349