[JAVA-27759] Fix Spring 3.1.x and Keycloak 22.x OAuth2 Tutorial (#15618)
This commit is contained in:
parent
a09f1a89f1
commit
22a1ca2978
|
@ -107,6 +107,7 @@
|
|||
<jaxb-runtime.version>4.0.0</jaxb-runtime.version>
|
||||
<wsdl4j.version>1.6.3</wsdl4j.version>
|
||||
<jaxb2-maven-plugin.version>3.1.0</jaxb2-maven-plugin.version>
|
||||
<maven.compiler.release>17</maven.compiler.release>
|
||||
</properties>
|
||||
|
||||
</project>
|
|
@ -1,15 +1,21 @@
|
|||
package com.baeldung.keycloak;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||
import org.springframework.security.core.session.SessionRegistryImpl;
|
||||
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
|
||||
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
|
||||
|
@ -19,6 +25,10 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
|||
@EnableWebSecurity
|
||||
class SecurityConfig {
|
||||
|
||||
private static final String GROUPS = "groups";
|
||||
private static final String REALM_ACCESS_CLAIM = "realm_access";
|
||||
private static final String ROLES_CLAIM = "roles";
|
||||
|
||||
private final KeycloakLogoutHandler keycloakLogoutHandler;
|
||||
|
||||
SecurityConfig(KeycloakLogoutHandler keycloakLogoutHandler) {
|
||||
|
@ -30,38 +40,63 @@ class SecurityConfig {
|
|||
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
|
||||
}
|
||||
|
||||
@Order(1)
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain clientFilterChain(HttpSecurity http) throws Exception {
|
||||
http.authorizeRequests()
|
||||
public SecurityFilterChain resourceServerFilterChain(HttpSecurity http) throws Exception {
|
||||
http.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers(new AntPathRequestMatcher("/customers*"))
|
||||
.hasRole("user")
|
||||
.requestMatchers(new AntPathRequestMatcher("/"))
|
||||
.permitAll()
|
||||
.anyRequest()
|
||||
.authenticated();
|
||||
http.oauth2Login()
|
||||
.and()
|
||||
.logout()
|
||||
.addLogoutHandler(keycloakLogoutHandler)
|
||||
.logoutSuccessUrl("/");
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Order(2)
|
||||
@Bean
|
||||
public SecurityFilterChain resourceServerFilterChain(HttpSecurity http) throws Exception {
|
||||
http.authorizeRequests()
|
||||
.requestMatchers(new AntPathRequestMatcher("/customers*"))
|
||||
.hasRole("USER")
|
||||
.anyRequest()
|
||||
.authenticated();
|
||||
.authenticated());
|
||||
http.oauth2ResourceServer((oauth2) -> oauth2
|
||||
.jwt(Customizer.withDefaults()));
|
||||
http.oauth2Login(Customizer.withDefaults())
|
||||
.logout(logout -> logout.addLogoutHandler(keycloakLogoutHandler).logoutSuccessUrl("/"));
|
||||
return http.build();
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
|
||||
return http.getSharedObject(AuthenticationManagerBuilder.class)
|
||||
.build();
|
||||
public GrantedAuthoritiesMapper userAuthoritiesMapperForKeycloak() {
|
||||
return authorities -> {
|
||||
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
||||
var authority = authorities.iterator().next();
|
||||
boolean isOidc = authority instanceof OidcUserAuthority;
|
||||
|
||||
if (isOidc) {
|
||||
var oidcUserAuthority = (OidcUserAuthority) authority;
|
||||
var userInfo = oidcUserAuthority.getUserInfo();
|
||||
|
||||
// Tokens can be configured to return roles under
|
||||
// Groups or REALM ACCESS hence have to check both
|
||||
if (userInfo.hasClaim(REALM_ACCESS_CLAIM)) {
|
||||
var realmAccess = userInfo.getClaimAsMap(REALM_ACCESS_CLAIM);
|
||||
var roles = (Collection<String>) realmAccess.get(ROLES_CLAIM);
|
||||
mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles));
|
||||
} else if (userInfo.hasClaim(GROUPS)) {
|
||||
Collection<String> roles = (Collection<String>) userInfo.getClaim(
|
||||
GROUPS);
|
||||
mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles));
|
||||
}
|
||||
} else {
|
||||
var oauth2UserAuthority = (OAuth2UserAuthority) authority;
|
||||
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
|
||||
|
||||
if (userAttributes.containsKey(REALM_ACCESS_CLAIM)) {
|
||||
Map<String, Object> realmAccess = (Map<String, Object>) userAttributes.get(
|
||||
REALM_ACCESS_CLAIM);
|
||||
Collection<String> roles = (Collection<String>) realmAccess.get(ROLES_CLAIM);
|
||||
mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles));
|
||||
}
|
||||
}
|
||||
return mappedAuthorities;
|
||||
};
|
||||
}
|
||||
|
||||
Collection<GrantedAuthority> generateAuthoritiesFromClaim(Collection<String> roles) {
|
||||
return roles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role)).collect(
|
||||
Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ class KeycloakSoapLiveTest {
|
|||
ResponseEntity<String> responseEntity = restTemplate.postForEntity("http://localhost:" + port + "/ws/api/v1/", request, String.class);
|
||||
|
||||
assertThat(responseEntity).isNotNull();
|
||||
assertThat(responseEntity.getStatusCodeValue()).isEqualTo(HttpStatus.OK.value());
|
||||
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK.value());
|
||||
assertThat(responseEntity.getBody()).isNotBlank();
|
||||
assertThat(responseEntity.getBody()).containsIgnoringCase(":id>1</");
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ class KeycloakSoapLiveTest {
|
|||
HttpEntity<String> request = new HttpEntity<>(Utility.getGetProductDetailsRequest(), headers);
|
||||
ResponseEntity<String> responseEntity = restTemplate.postForEntity("http://localhost:" + port + "/ws/api/v1/", request, String.class);
|
||||
assertThat(responseEntity).isNotNull();
|
||||
assertThat(responseEntity.getStatusCodeValue()).isEqualTo(HttpStatus.UNAUTHORIZED.value());
|
||||
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED.value());
|
||||
assertThat(responseEntity.getBody()).isBlank();
|
||||
}
|
||||
|
||||
|
@ -110,7 +110,7 @@ class KeycloakSoapLiveTest {
|
|||
ResponseEntity<String> responseEntity = restTemplate.postForEntity("http://localhost:" + port + "/ws/api/v1/", request, String.class);
|
||||
|
||||
assertThat(responseEntity).isNotNull();
|
||||
assertThat(responseEntity.getStatusCodeValue()).isEqualTo(HttpStatus.OK.value());
|
||||
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK.value());
|
||||
assertThat(responseEntity.getBody()).isNotBlank();
|
||||
assertThat(responseEntity.getBody()).containsIgnoringCase("Deleted the product with the id");
|
||||
}
|
||||
|
@ -130,7 +130,7 @@ class KeycloakSoapLiveTest {
|
|||
ResponseEntity<String> responseEntity = restTemplate.postForEntity("http://localhost:" + port + "/ws/api/v1/", request, String.class);
|
||||
|
||||
assertThat(responseEntity).isNotNull();
|
||||
assertThat(responseEntity.getStatusCodeValue()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.value());
|
||||
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.value());
|
||||
assertThat(responseEntity.getBody()).isNotBlank();
|
||||
assertThat(responseEntity.getBody()).containsIgnoringCase("Access is denied");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue