Токен XSRF Springboot OAuth2 CSRF не соответствует

У меня есть это демонстрационное приложение Springboot, и моя цель — узнать больше об oauth2. Мне удалось заставить его работать, используя аутентификацию github, которую я настроил следующим образом.

      spring:
  security:
    oauth2:
      client:
        registration:
          github:
            clientId: ***
            clientSecret: ***

Это мои зависимости Gradle

      implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-web'

это моя цепочка фильтров безопасности

      @Slf4j
@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {

    @Autowired
    private CustomOAuth2AuthenticationFailureHandler failureHandler;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        CookieCsrfTokenRepository cRepo = CookieCsrfTokenRepository.withHttpOnlyFalse();
        http
            .addFilterBefore(new PrintCsrfTokenFilter(cRepo), CsrfFilter.class)
            .csrf(csrf ->  csrf.csrfTokenRepository(cRepo))
            .authorizeHttpRequests(authorize -> authorize
                .requestMatchers("/", "/error", "/webjars/**","/index.html").permitAll()
                .anyRequest().authenticated()
            )
            .logout((logout) -> logout.logoutSuccessUrl("/").permitAll())
            .oauth2Login(t -> t.failureHandler((request, response, exception) -> {
                log.error(exception.getMessage());
                request.getSession().setAttribute("error.message", exception.getMessage());
                failureHandler.onAuthenticationFailure(request, response, exception);
            }));
        return http.build();
    }
}

Это мой класс фильтра отладки

      @Slf4j
public final class PrintCsrfTokenFilter extends OncePerRequestFilter {

    private CsrfTokenRepository tokenRepository;

    public PrintCsrfTokenFilter(CsrfTokenRepository csrfTokenRepository) {
        Assert.notNull(csrfTokenRepository, "csrfTokenRepository cannot be null");
        this.tokenRepository = csrfTokenRepository;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        DeferredCsrfToken deferredCsrfToken = this.tokenRepository.loadDeferredToken(request, response);
        CsrfToken csrfToken = deferredCsrfToken.get();
        String actualToken = this.resolveCsrfTokenValue(request, csrfToken);
        log.info("csrfToken: {} , actualToken: {}", csrfToken.getToken(), actualToken);

        String xcsrfToken = request.getHeader("X-XSRF-TOKEN");
        log.info("xcsrfToken Token: " + xcsrfToken);

        filterChain.doFilter(request, response);
    }

    private String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
        Assert.notNull(request, "request cannot be null");
        Assert.notNull(csrfToken, "csrfToken cannot be null");
        String actualToken = request.getHeader(csrfToken.getHeaderName());
        log.info("header {}: {} ", csrfToken.getHeaderName(), actualToken);
        if (actualToken == null) {
            actualToken = request.getParameter(csrfToken.getParameterName());
            log.info("param {}: {} ", csrfToken.getParameterName(), actualToken);
        }
        return actualToken;
    }
}

вот моя передняя часть

      <body>
<script type="text/javascript">
// Declare logout function in the global scope
window.logout = function() {
    $.post("/logout", function() {
        $("#user").html('');
        $(".unauthenticated").show();
        $(".authenticated").hide();
    })
    return true;
}

$.ajaxSetup({
  beforeSend : function(xhr, settings) {
    if (settings.type == 'POST' || settings.type == 'PUT'
        || settings.type == 'DELETE') {
      if (!(/^http:.*/.test(settings.url) || /^https:.*/
        .test(settings.url))) {
        // Only send the token to relative URLs i.e. locally.
        xhr.setRequestHeader("X-XSRF-TOKEN",
          Cookies.get('XSRF-TOKEN'));
      }
    }
  }
});

$(document).ready(function() {
    $.get("/user", function(data, status, xhr) {
        if (xhr.status == 200 && data.name != null && data.name != "") {
            $("#user").html(data.name);
            $(".unauthenticated").hide();
            $(".authenticated").show();
        } else {
            $(".authenticated").hide();
            $(".unauthenticated").show();
        }
    }).fail(function() {
        $(".authenticated").hide();
        $(".unauthenticated").show();
    });
});

</script>



<div class="container">
    <h1>Demo</h1>
    <div class="container unauthenticated">
        With GitHub: <a href="/oauth2/authorization/github">click here</a>
    </div>
    <div class="container authenticated">
        Logged in as: <span id="user"></span>
        <div>
            <button onClick="logout()" class="btn btn-primary">Logout</button>
        </div>
    </div>
</div>
</body>

Я вижу, чтоactualTokenиcsrfToken.getToken()то же самое, когда я пытаюсь отладить с помощью точки останова, но я все равно получаю эту ошибку при попытке выйти из системы

      2023-07-31T18:17:31.904+08:00  INFO 10277 --- [nio-8080-exec-9] c.mark.oauth2.Demo.PrintCsrfTokenFilter  : csrfToken: afc6a080-363c-4d9f-87e2-187314baf11a , actualToken: afc6a080-363c-4d9f-87e2-187314baf11a
2023-07-31T18:17:31.905+08:00 DEBUG 10277 --- [nio-8080-exec-9] o.s.security.web.csrf.CsrfFilter         : Invalid CSRF token found for http://localhost:8080/logout

Вот как это выглядит в браузере [ (https://stackru.com/images/ed6b46b2528a6d19339aa1e4c9d7b7be08bffc5a.png)

Я хотел знать, не упускаю ли я, как мне реализовать OAuth2. Любые ответы будут оценены по достоинству, я буду обновлять этот пост для всеобщего обучения и справки.

Я попытался выйти из браузера, но он не работает, потому что, хотя токен csrf тот же, он продолжает говоритьInvalid CSRF token foundЯ ожидаю, что это будет работать так, как я это понял, но явно чего-то не хватает.

1 ответ

Документация по безопасности CSRF в Spring теперь немного разрослась и найти нужный раздел для SPA не так уж и тривиально . Просто примените то, что там описано, и все будет хорошо.

И, кстати, если вы воспользуетесь моим стартером , активируя CSRF-защиту с помощьюhttp-only=falseэто просто вопрос установки свойства:

      <dependency>
    <groupId>org.springframework.boot</groupId>
    <!-- For a reactive application, use spring-boot-starter-webflux instead -->
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
    <groupId>com.c4-soft.springaddons</groupId>
    <artifactId>spring-addons-starter-oidc</artifactId>
    <version>7.0.7</version>
</dependency>
      cognito-issuer: https://cognito-idp.us-west-2.amazonaws.com/us-west-2_RzhmgLwjl
cognito-client-id: change-me
cognito-secret: change-me

spring:
  security:
    oauth2:
      client:
        provider:
          cognito:
            issuer-uri: ${cognito-issuer}
        registration:
          cognito-authorization-code:
            authorization-grant-type: authorization_code
            client-id: ${cognito-client-id}
            client-secret: ${cognito-secret}
            provider: cognito
            scope: openid,profile,email,offline_access
com:
  c4-soft:
    springaddons:
      oidc:
        ops:
        - iss: ${cognito-issuer}
          username-claim: username
          authorities:
          - path: cognito:groups
        client:
          csrf: cookie-accessible-from-js
          security-matchers:
          - /**
          permit-all:
          - /login/**
          - /oauth2/**
          - /
          # Auth0 and Cognito do not follow strictly the OpenID RP-Initiated Logout spec and need specific configuration
          oauth2-logout:
            cognito-authorization-code:
              uri: https://spring-addons.auth.us-west-2.amazoncognito.com/logout
              client-id-request-param: client_id
              post-logout-uri-request-param: logout_uri
Другие вопросы по тегам