Integración con Identity Manager de Plataforma vía OAuth2/OpenID
Introducción
En esta guía se van a describir los conceptos básicos sobre la autenticación/autorización OAuth2/OpenID en Plataforma a través de su Identity Manager (IM en adelante) y cómo las aplicaciones pueden integrar con esta.
Conceptos básicos
En el IM hay un endpoint que proporciona toda la configuración que necesitan tus aplicaciones para implementar el proceso de autenticación.
Este endpoint es accesible a través de la URL:
https://{DNS}/auth/realms/{realm}/.well-known/openid-configuration
Por ejemplo:
https://development.onesaitplatform.com/auth/realms/onesaitplatform/.well-known/openid-configuration
La información se mostrará en formato de datos JSON.
Tipos de flujo de autorización
Concesión de tipo implícito (contraseña)
Este es el flujo de OAuth2 más utilizado, aunque no ofrece las capacidades de SSO, ya que no se delega la autenticación en el navegador web y las cookies.
Hay que hacer una petición POST al token_endpoint con los siguientes parámetros:
HTTP Headers (Cabeceras HTTP)
Authorization Basic (Autorización (Básica)): utilizando clientId y clientSecret. Por defecto ambos valores son "onesaitplatform", pero si estás usando Realms, pueden cambiar.
HTTP Body (Cuerpo HTTP) (x-www-form-urlencoded)
grant_type (tipo de concesión): password
scope (alcance): openid
username (nombre de usuario): {username}
password (contraseña): {password}
Si tiene éxito, se devuelve un token de acceso. Este token contiene las afirmaciones del usuario registrado. Puedes decodificarlo en base64 o hacer otra petición al userinfo_endpoint (ver más adelante).
Flujo de código de autorización
Este flujo OAuth2 se utiliza en los casos en los que se requiere de inicio de sesión único (SSO). El proceso de autenticación se delega en el navegador y las cookies.
Se utilizará el authorization_endpoint. Tras el proceso de inicio de sesión, y el intercambio de código para el token, recibirás la misma carga útil (token) que en el flujo de concesión implícita.
Información del usuario
Este punto final se utiliza para comprobar la validez del token y recuperar las afirmaciones de los usuarios.
Se debe realizar una solicitud HTTP GET con el token de portador como cabecera de la Autorización.
Integración con el Flujo de autorización del IM en aplicaciones
A continuación se muestra cómo sería la integración con el flujo de autorización (SSO) en diferentes tecnologías.
Integración en aplicaciones Spring Boot 3.X
A continuación se va a detallar como realizar la integración con el IM de Plataforma basado en Keycloak y Oauth2/OpenId para una aplicación Spring Boot con versión mínima 3.0
Beans necesarios y configuración general
Los valores de los Strings necesarios para la configuración dependerá de cada entorno, pudiendo obtener los endpoints con el método explicado anteriormente en esta guía.
Dependencias
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>9.38-rc3</version>
</dependency>
Clase de @Configuration
/** IMPORTS
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
import org.apache.hc.client5.http.ssl.TrustAllStrategy;
import org.apache.hc.core5.ssl.SSLContexts;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.web.client.RestTemplate;
**/
@Value("${oauth2.client.clientId}")
private String clientId;
@Value("${oauth2.client.clientSecret}")
private String clientSecret;
@Value("${oauth2.client.accessTokenUri}")
private String accessTokenUri;
@Value("${oauth2.client.userAuthorizationUri}")
private String userAuthorizationUri;
@Value("${oauth2.resource.userInfoUri}")
private String userInfoUri;
@Value("${oauth2.client.redirectUri}")
private String redirectUri;
@Value("${oauth2.client.processingUri:}")
private String processingUriOauth;
@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryClientRegistrationRepository(
ClientRegistration.withRegistrationId(clientId).clientId(clientId).clientSecret(clientSecret)
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE).redirectUri(redirectUri)
.authorizationUri(userAuthorizationUri).tokenUri(accessTokenUri).userInfoUri(userInfoUri)
.userNameAttributeName(KeycloakPrincipalExtractor.USERNAME).clientName(clientId).build());
}
@Bean
@Primary
public OAuth2UserService<OAuth2UserRequest, OAuth2User> userInfoTokenServices()
throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
final UserInfoServices tokenServices = new UserInfoServices((KeycloakAuthoritiesExtractor) authoritiesMapper());
return tokenServices;
}
@Bean
public GrantedAuthoritiesMapper authoritiesMapper() {
return new KeycloakAuthoritiesExtractor();
}
@Bean
public AuthenticationEntryPoint customAuthenticationEntryPoint() {
return new LoginUrlAuthenticationEntryPoint(processingUriOauth);
}
UserInfoServices
import java.util.List;
import java.util.Map;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequestEntityConverter;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.UnknownContentTypeException;
import com.minsait.onesait.platform.security.plugin.mappers.KeycloakAuthoritiesExtractor;
import com.minsait.onesait.platform.security.plugin.mappers.KeycloakPrincipalExtractor;
import com.minsait.onesait.platform.security.plugin.model.KeycloakOauth2User;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class UserInfoServices implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private static final String MISSING_USER_INFO_URI_ERROR_CODE = "missing_user_info_uri";
private static final String MISSING_USER_NAME_ATTRIBUTE_ERROR_CODE = "missing_user_name_attribute";
private static final String INVALID_USER_INFO_RESPONSE_ERROR_CODE = "invalid_user_info_response";
private static final ParameterizedTypeReference<Map<String, Object>> PARAMETERIZED_RESPONSE_TYPE = new ParameterizedTypeReference<Map<String, Object>>() {
};
private final Converter<OAuth2UserRequest, RequestEntity<?>> requestEntityConverter = new OAuth2UserRequestEntityConverter();
private final RestOperations restOperations;
private final KeycloakAuthoritiesExtractor keycloakAuthoritiesExtractor;
public UserInfoServices(KeycloakAuthoritiesExtractor keycloakAuthoritiesExtractor) {
super();
final RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
this.restOperations = restTemplate;
this.keycloakAuthoritiesExtractor = keycloakAuthoritiesExtractor;
}
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
Assert.notNull(userRequest, "userRequest cannot be null");
if (!StringUtils
.hasText(userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri())) {
final OAuth2Error oauth2Error = new OAuth2Error(MISSING_USER_INFO_URI_ERROR_CODE,
"Missing required UserInfo Uri in UserInfoEndpoint for Client Registration: "
+ userRequest.getClientRegistration().getRegistrationId(),
null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
final String userNameAttributeName = KeycloakPrincipalExtractor.USERNAME;
if (!StringUtils.hasText(userNameAttributeName)) {
final OAuth2Error oauth2Error = new OAuth2Error(MISSING_USER_NAME_ATTRIBUTE_ERROR_CODE,
"Missing required \"user name\" attribute name in UserInfoEndpoint for Client Registration: "
+ userRequest.getClientRegistration().getRegistrationId(),
null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
final RequestEntity<?> request = this.requestEntityConverter.convert(userRequest);
final ResponseEntity<Map<String, Object>> response = getResponse(userRequest, request);
final Map<String, Object> userAttributes = response.getBody();
final List<GrantedAuthority> authorities = this.keycloakAuthoritiesExtractor.extractAuthorities(userAttributes);
final OAuth2AccessToken token = userRequest.getAccessToken();
return new KeycloakOauth2User(authorities, userAttributes, userNameAttributeName, token.getTokenValue());
}
private ResponseEntity<Map<String, Object>> getResponse(OAuth2UserRequest userRequest, RequestEntity<?> request) {
try {
return this.restOperations.exchange(request, PARAMETERIZED_RESPONSE_TYPE);
} catch (final OAuth2AuthorizationException ex) {
OAuth2Error oauth2Error = ex.getError();
final StringBuilder errorDetails = new StringBuilder();
errorDetails.append("Error details: [");
errorDetails.append("UserInfo Uri: ")
.append(userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri());
errorDetails.append(", Error Code: ").append(oauth2Error.getErrorCode());
if (oauth2Error.getDescription() != null) {
errorDetails.append(", Error Description: ").append(oauth2Error.getDescription());
}
errorDetails.append("]");
oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
"An error occurred while attempting to retrieve the UserInfo Resource: " + errorDetails.toString(),
null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
} catch (final UnknownContentTypeException ex) {
final String errorMessage = "An error occurred while attempting to retrieve the UserInfo Resource from '"
+ userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri()
+ "': response contains invalid content type '" + ex.getContentType().toString() + "'. "
+ "The UserInfo Response should return a JSON object (content type 'application/json') "
+ "that contains a collection of name and value pairs of the claims about the authenticated End-User. "
+ "Please ensure the UserInfo Uri in UserInfoEndpoint for Client Registration '"
+ userRequest.getClientRegistration().getRegistrationId() + "' conforms to the UserInfo Endpoint, "
+ "as defined in OpenID Connect 1.0: 'https://openid.net/specs/openid-connect-core-1_0.html#UserInfo'";
final OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE, errorMessage, null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
} catch (final RestClientException ex) {
final OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
"An error occurred while attempting to retrieve the UserInfo Resource: " + ex.getMessage(), null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
}
}
}
KeycloakAuthoritiesExtractor
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
public class KeycloakAuthoritiesExtractor implements GrantedAuthoritiesMapper {
public static final String AUTHORITIES = "authorities";
private static final String DEFAULT_ROLE = "ROLE_USER";
public List<GrantedAuthority> extractAuthorities(Map<String, Object> map) {
return AuthorityUtils.createAuthorityList(extractRole(map));
}
@SuppressWarnings("unchecked")
public String extractRole(Map<String, Object> map) {
try {
return ((List<String>) map.get(AUTHORITIES)).iterator().next();
} catch (final Exception e) {
return DEFAULT_ROLE;
}
}
@Override
public Collection<? extends GrantedAuthority> mapAuthorities(Collection<? extends GrantedAuthority> authorities) {
return authorities;
}
}
KeycloakPrincipalExtractor
import java.util.Map;
public class KeycloakPrincipalExtractor {
public static final String USERNAME = "username";
public Object extractPrincipal(Map<String, Object> map) {
return map.get(USERNAME);
}
}
KeycloakOauth2User
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.util.Assert;
public class KeycloakOauth2User implements OAuth2User, Serializable {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final Set<GrantedAuthority> authorities;
private final Map<String, Object> attributes;
private final String nameAttributeKey;
private final String accessToken;
public KeycloakOauth2User(Collection<? extends GrantedAuthority> authorities, Map<String, Object> attributes,
String nameAttributeKey, String accessToken) {
Assert.notEmpty(attributes, "attributes cannot be empty");
Assert.hasText(nameAttributeKey, "nameAttributeKey cannot be empty");
if (!attributes.containsKey(nameAttributeKey)) {
throw new IllegalArgumentException("Missing attribute '" + nameAttributeKey + "' in attributes");
}
this.authorities = (authorities != null)
? Collections.unmodifiableSet(new LinkedHashSet<>(this.sortAuthorities(authorities)))
: Collections.unmodifiableSet(new LinkedHashSet<>(AuthorityUtils.NO_AUTHORITIES));
this.attributes = Collections.unmodifiableMap(new LinkedHashMap<>(attributes));
this.nameAttributeKey = nameAttributeKey;
this.accessToken = accessToken;
}
public String getAccessToken() {
return this.accessToken;
}
@Override
public Map<String, Object> getAttributes() {
return this.attributes;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public String getName() {
return this.getAttribute(this.nameAttributeKey).toString();
}
private Set<GrantedAuthority> sortAuthorities(Collection<? extends GrantedAuthority> authorities) {
final SortedSet<GrantedAuthority> sortedAuthorities = new TreeSet<>(
Comparator.comparing(GrantedAuthority::getAuthority));
sortedAuthorities.addAll(authorities);
return sortedAuthorities;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || this.getClass() != obj.getClass()) {
return false;
}
final KeycloakOauth2User that = (KeycloakOauth2User) obj;
if (!this.getName().equals(that.getName())) {
return false;
}
if (!this.getAuthorities().equals(that.getAuthorities())) {
return false;
}
return this.getAttributes().equals(that.getAttributes());
}
@Override
public int hashCode() {
int result = this.getName().hashCode();
result = 31 * result + this.getAuthorities().hashCode();
result = 31 * result + this.getAttributes().hashCode();
return result;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("Name: [");
sb.append(this.getName());
sb.append("], Granted Authorities: [");
sb.append(getAuthorities());
sb.append("], User Attributes: [");
sb.append(getAttributes());
sb.append("]");
return sb.toString();
}
}
Filtro de seguridad
import java.io.IOException;
import org.springframework.http.HttpHeaders;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import com.minsait.onesait.platform.security.PlugableOauthAuthenticator;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class BearerExtractorFilter implements Filter {
private final PlugableOauthAuthenticator plugableOauthAuthenticator;
public BearerExtractorFilter(PlugableOauthAuthenticator plugableOauthAuthenticator) {
this.plugableOauthAuthenticator = plugableOauthAuthenticator;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
final String header = ((HttpServletRequest) request).getHeader(HttpHeaders.AUTHORIZATION);
// continue chain
if (!StringUtils.hasText(header) || !header.toLowerCase().startsWith("bearer ")) {
chain.doFilter(request, response);
return;
}
final String token = header.split(" ")[1].trim();
final Authentication auth = loadAuthentication(token);
setContexts(auth);
chain.doFilter(request, response);
clearContexts();
}
private Authentication loadAuthentication(String token) {
return plugableOauthAuthenticator.loadFullAuthentication(token);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.debug("Initializing Bearer extractor filter");
}
@Override
public void destroy() {
log.debug("Destroying Bearer extractor filter");
}
private void setContexts(Authentication auth) {
SecurityContextHolder.getContext().setAuthentication(auth);
}
private void clearContexts() {
SecurityContextHolder.clearContext();
}
}
PlugableOauthAuthenticator
@Service
public class PlugableOauthAuthenticator {
@Value("${oauth2.client.clientId}")
private String clientId;
@Value("${oauth2.client.jwksUri}")
private String jwksUri;
private static final String REGEX_REALM = ".*\\/realms\\/([a-z-_]+).*";
private static final Pattern PATTERN_REALM = Pattern.compile(REGEX_REALM);
private final Map<String, ConfigurableJWTProcessor<SecurityContext>> jwtProcessors = new HashMap<>();
public Authentication loadFullAuthentication(String token) {
return loadOauthAuthentication(token);
}
public Authentication loadOauthAuthentication(String token) {
try {
final JWSObject jwt = JWSObject.parse(token);
final String jwksUri = computeJwksURL(jwt);
final ConfigurableJWTProcessor<SecurityContext> jwtProcessor = getJwtProcessor(jwksUri);
final JWTClaimsSet claims = jwtProcessor.process(token, null);
final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
new KeycloakOauth2User(
AuthorityUtils.createAuthorityList(
claims.getStringListClaim(KeycloakAuthoritiesExtractor.AUTHORITIES)),
claims.toJSONObject(), KeycloakPrincipalExtractor.USERNAME, token),
"", AuthorityUtils.createAuthorityList(
claims.getStringListClaim(KeycloakAuthoritiesExtractor.AUTHORITIES)));
return auth;
} catch (final Exception e) {
log.error("Error loading authentication from JWT token", e);
}
return null;
}
public ConfigurableJWTProcessor<SecurityContext> getJwtProcessor(String jwksUri) throws MalformedURLException {
if (jwtProcessors.containsKey(jwksUri)) {
return jwtProcessors.get(jwksUri);
} else {
final ConfigurableJWTProcessor<SecurityContext> jwtProcessor = doGetJwtProcessor(jwksUri);
jwtProcessors.put(jwksUri, jwtProcessor);
return jwtProcessor;
}
}
public ConfigurableJWTProcessor<SecurityContext> doGetJwtProcessor(String jwksUri) throws MalformedURLException {
final ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
jwtProcessor.setJWSTypeVerifier(new DefaultJOSEObjectTypeVerifier<>(JOSEObjectType.JWT));
final JWKSource<SecurityContext> keySource = JWKSourceBuilder.create(new URL(jwksUri)).retrying(true).build();
final JWSAlgorithm expectedJWSAlg = JWSAlgorithm.RS256;
final JWSKeySelector<SecurityContext> keySelector = new JWSVerificationKeySelector<>(expectedJWSAlg, keySource);
jwtProcessor.setJWSKeySelector(keySelector);
jwtProcessor.setJWTClaimsSetVerifier(new DefaultJWTClaimsVerifier<>(new JWTClaimsSet.Builder().build(),
new HashSet<>(Arrays.asList(KeycloakPrincipalExtractor.USERNAME, JWTClaimNames.ISSUED_AT,
JWTClaimNames.EXPIRATION_TIME, JWTClaimNames.JWT_ID))));
return jwtProcessor;
}
private String computeJwksURL(JWSObject jwt) {
final String issuer = jwt.getPayload().toJSONObject().get("iss").toString();
Matcher matcher = PATTERN_REALM.matcher(issuer);
String realm = null;
if (matcher.find()) {
realm = matcher.group(1);
} else {
log.info("No match found for keycloak realm");
}
if (realm != null) {
matcher = PATTERN_REALM.matcher(jwksUri);
if (matcher.find()) {
return new StringBuilder(jwksUri).replace(matcher.start(1), matcher.end(1), realm).toString();
}
}
return jwksUri;
}
}
Ejemplo de propiedades para una aplicación
oauth2.client.clientId = onesaitplatform
oauth2.client.clientSecret = onesaitplatform
oauth2.client.accessTokenUri = https://${SERVER_NAME}/auth/realms/onesaitplatform/protocol/openid-connect/token
oauth2.client.userAuthorizationUri = https://${SERVER_NAME}/auth/realms/onesaitplatform/protocol/openid-connect/auth?scope=openid
oauth2.client.logoutUrl=https://${SERVER_NAME}/auth/realms/onesaitplatform/protocol/openid-connect/logout?redirect_uri=https://${SERVER_NAME}/controlpanel/login
oauth2.resource.userInfoUri = https://${SERVER_NAME}/auth/realms/onesaitplatform/protocol/openid-connect/userinfo
oauth2.client.redirectUri= {baseUrl}/login/oauth2/code/{registrationId}
oauth2.client.processingUri = /oauth2/authorization/onesaitplatform
oauth2.client.jwksUri = https://${SERVER_NAME}/auth/realms/onesaitplatform/protocol/openid-connect/certs
SecurityFilterChain Bean
@Autowired
private OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService;
@Autowired
private GrantedAuthoritiesMapper grantedAuthoritiesMapper;
@Autowired
private PlugableOauthAuthenticator oauthAuthenticator;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
...
http.authorizeHttpRequests(r -> {
r.requestMatchers(new AntPathRequestMatcher("/login/**").permitAll();
});
http.oauth2Login(o -> {
o.userInfoEndpoint(ui -> {
ui.userService(oauth2UserService);
ui.userAuthoritiesMapper(grantedAuthoritiesMapper);
});
//Handlers opcionales
//o.successHandler(successHandler);
//o.failureHandler(failureHandler);
});
http.addFilterBefore(new BearerExtractorFilter(oauthAuthenticator), BasicAuthenticationFilter.class);
return http;
}
SPA con node (Vue,Angular, React)
A continuación se enlaza con una guía de la librería necesaria a integrar en las dependencias de la aplicación SPA.
Secure Vue.js app with Keycloak