background
In the previous micro-services field: In this article, we used Spring Security and AWS Cognito to configure OAuth2 login for the Spring Cloud Gateway application. This article focuses on the logout (logout) function.
This article is based on Thymeleaf OAuth2 Login with Spring Security and AWS Cognito. Be sure to read through the articles to get a basic understanding of the sample code and Spring Security and Cognito.
How do I manage sessions?
In Gateway, we use Redis to manage sessions and cannot use local memory because in a cloud environment, Gateway may have multiple instances and session data needs to be kept in external storage. Build. Gradle dependency:
// for session
implementation "org.springframework.session:spring-session-data-redis"
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
Copy the code
Then add the following configuration to application.yaml:
spring:
session:
store-type: redis # Defines where the session is stored JVM or redis
redis:
flush-mode: immediate #Tells spring to flush the session data immediately into redis
redis:
host: localhost
port: 6379
Copy the code
You can view the saved Session information in Redis after logging in:
> redis-cli -h localhost -p 6379
localhost:6379> keys spring*
1) "spring:session:sessions:078cfd0a-805c-4608-ad50-27101747fe45"
localhost:6379> HGETALL spring:session:sessions:078cfd0a-805c-4608-ad50-27101747fe45
// ...
Copy the code
With OpenID Connect and Spring Security, our users will have two sessions: one application-specific (Gateway) and one for the identity provider (Cognito). Whenever a user logs out, Spring Security (by default) invalidates the first session, but our user will still be logged in at the identity provider.
This way, any subsequent Cognito login attempts will be automatically logged into our application without requiring the user to provide any credentials. This is not what we want, we want users to be completely logged out so they can easily quit the app or switch accounts.
Does Spring Security not support logout?
Yes, of course.
The OIDC specification defines how a client can perform Logout at an identity provider: OpenID Connect RP-Initiated Logout 1.0 (currently in draft status).
Spring Security can only Logout identity providers that support OpenID Connect rp-initiated Logout 1.0, such as Keycloak. For this type of Id Provider, only need to use OidcClientInitiatedServerLogoutSuccessHandler can realize the cancellation.
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, ReactiveClientRegistrationRepository clientRegistrationRepository, MyAuthorizationManager authorizationManager, MyOAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver) {
// ...
// Also logout at the OpenID Connect provider
http.logout(logout -> logout.logoutSuccessHandler(new OidcClientInitiatedServerLogoutSuccessHandler(
clientRegistrationRepository)));
// ...
return http.build();
}
Copy the code
Unfortunately, AWS Cognito does not currently support OpenID Connect RP-initiated Logout 1.0, but it does provide a Logout mechanism.
How do I cancel Cognito?
AWS Cognito defines a LOGOUT endpoint with the following parameters. For details, see AWS Cognito LOGOUT Endpoint.
GET https://<DOMAIN_PREFIX>.auth.<AWS_REGION>.amazoncognito.com/logout?
response_type=code&
client_id=<YOUR_CLIENT_ID>&
redirect_uri=https://YOUR_APP/redirect_uri&
state=<STATE>&
scope=openid
Copy the code
Let Spring Security support Cognnito
With the powerful extensibility of Spring Security, we can define our own LogoutHandler. Spring Security invokes this custom handler as part of its own logout process (invalidating the Gateway HTTP session and clearing the SecurityContextHolder).
By default, the logout endpoint is /logout, and we don’t need to develop a Controller for this endpoint (Spring Security already implements this). Due to the AWS Cognito need HTTPS cancellation request, we can imitate the OidcClientInitiatedServerLogoutSuccessHandler implementation, write a own LogoutHandler.
public class CognitoOidcLogoutSuccessHandler implements ServerLogoutSuccessHandler {
private final String logoutUrl;
private final ReactiveClientRegistrationRepository clientRegistrationRepository;
private final RedirectServerLogoutSuccessHandler serverLogoutSuccessHandler =
new RedirectServerLogoutSuccessHandler();
private final ServerRedirectStrategy redirectStrategy = new DefaultServerRedirectStrategy();
public CognitoOidcLogoutSuccessHandler(String logoutUrl, ReactiveClientRegistrationRepository clientRegistrationRepository) {
Assert.notNull(logoutUrl, "logoutUrl cannot be null");
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
this.logoutUrl = logoutUrl;
this.clientRegistrationRepository = clientRegistrationRepository;
}
@Override
public Mono<Void> onLogoutSuccess(WebFilterExchange exchange, Authentication authentication) {
return Mono.just(authentication)
.filter(OAuth2AuthenticationToken.class::isInstance)
.filter((token) -> authentication.getPrincipal() instanceofOidcUser) .map(OAuth2AuthenticationToken.class::cast) .map(OAuth2AuthenticationToken::getAuthorizedClientRegistrationId) .flatMap(this.clientRegistrationRepository::findByRegistrationId)
.flatMap((clientRegistration) -> {
String clientId = clientRegistration.getClientId();
Set<String> scopes = clientRegistration.getScopes();
URI postLogoutRedirectUri = postLogoutRedirectUri(exchange.getExchange().getRequest(), clientId, scopes);
return Mono.just(postLogoutRedirectUri);
})
.switchIfEmpty(
this.serverLogoutSuccessHandler.onLogoutSuccess(exchange, authentication).then(Mono.empty())
)
.flatMap((endpointUri) -> this.redirectStrategy.sendRedirect(exchange.getExchange(), endpointUri));
}
private URI postLogoutRedirectUri(ServerHttpRequest request, String clientId, Set<String> scopes) {
if (this.logoutUrl == null) {
return null;
}
UriComponents baseUrl = UriComponentsBuilder
.fromUri(request.getURI())
.replacePath(request.getPath().contextPath().value())
.replaceQuery(null)
.fragment(null)
.build();
return UriComponentsBuilder
.fromUri(URI.create(logoutUrl))
.queryParam("client_id", clientId)
.queryParam("logout_uri", baseUrl)
.queryParam("redirect_uri", baseUrl)
.queryParam("response_type"."code")
.queryParam("scope", String.join("+", scopes)) .encode(StandardCharsets.UTF_8) .build() .toUri(); }}Copy the code
This implementation requires logoutUrl as a parameter. Logout redirects the user to our application’s baseURL. The easiest way to do this is to configure them to application.yaml and inject logoutUrl with @Value.
cognito:
logoutUrl: https://sc-gateway-sample.auth.ap-northeast-1.amazoncognito.com/logout
Copy the code
The rest of the work is in SecurityConfig we custom CognitoOidcLogoutSuccessHandler configuration.
@Value("${cognito.logoutUrl}")
String logoutUrl;
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, ReactiveClientRegistrationRepository clientRegistrationRepository, MyAuthorizationManager authorizationManager, MyOAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver) {
// ...
// Also logout at the OpenID Connect provider
http.logout(l```
logout -> logout.logoutSuccessHandler(new CognitoOidcLogoutSuccessHandler(
logoutUrl,
clientRegistrationRepository)));
// ...
return http.build();
}
Copy the code
In this way, you can unregister both sessions with one click.
Do I need to log in again after the access token expires?
It’s not necessary.
Spring Security automatically updates the Access Token if the Refresh Token does not expire. TokenRelayFilter passes the Access Token to the back-end API. Note, however, that ID tokens stored in the Gateway’s session SPRING_SECURITY_CONTEXT are not automatically refreshed.
conclusion
This article describes how to log out of AWS Cognito using Spring Security and provides the implementation code.
If it helps you, please click “like” and subscribe to share. Thanks!
Related articles
- Microservices: BFF case based on Spring Cloud Gateway + AWS Cognito
- Microservices: How to test oAUth-certified microservices
- Is your micro server using OAuth2 and not aware of CSRF and PKCE?
Refer to the link
- OpenID Connect RP-Initiated Logout 1.0
- OIDC Logout With AWS Cognito and Spring Security
- AWS Cognito LOGOUT endpoint