/
Integración con Identity Manager de Plataforma vía OAuth2/OpenID

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

 

Related content