Токен 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