From a378efdb0b99bebf427508cb7978a30c82302d72 Mon Sep 17 00:00:00 2001 From: ialbert Date: Mon, 29 Aug 2022 19:59:38 +0300 Subject: [PATCH] example authenticating to keycloak though login endpoint and session --- ...oakRestTemplateAuthenticationProvider.java | 82 +++++++++++++++++++ .../security/WebSecurityConfig.java | 21 ++++- .../src/main/resources/keycloak.json | 4 +- 3 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/security/KeycloakRestTemplateAuthenticationProvider.java diff --git a/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/security/KeycloakRestTemplateAuthenticationProvider.java b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/security/KeycloakRestTemplateAuthenticationProvider.java new file mode 100644 index 000000000..585362485 --- /dev/null +++ b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/security/KeycloakRestTemplateAuthenticationProvider.java @@ -0,0 +1,82 @@ +package ru.spcex.clearing.backendapi.security; + +import org.keycloak.adapters.KeycloakConfigResolver; +import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.adapters.rotation.AdapterTokenVerifier; +import org.keycloak.common.VerificationException; +import org.keycloak.representations.AccessToken; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.*; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Component("keycloakRestAuthenticationProvider") +public class KeycloakRestTemplateAuthenticationProvider implements AuthenticationProvider { + private final KeycloakConfigResolver resolver; + private final HttpServletRequest currentHttpRequest; + private final HttpServletResponse currentHttpResponse; + private final RestTemplate restTemplate; + + public KeycloakRestTemplateAuthenticationProvider(KeycloakConfigResolver resolver, HttpServletRequest currentHttpRequest, HttpServletResponse currentHttpResponse, + @Qualifier("clearing-rest") RestTemplate restTemplate) { + this.resolver = resolver; + this.currentHttpRequest = currentHttpRequest; + this.currentHttpResponse = currentHttpResponse; + this.restTemplate = restTemplate; + } + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + + try { + KeycloakDeployment deployment = resolver.resolve(null); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + MultiValueMap map = new LinkedMultiValueMap<>(); + map.add("username", authentication.getName()); + map.add("password", (String) authentication.getCredentials()); + map.add("grant_type", "password"); + map.add("client_id", deployment.getResourceName()); + map.add("client_secret", (String) deployment.getResourceCredentials().get("secret")); + + ResponseEntity response = restTemplate.exchange(deployment.getTokenUrl(), HttpMethod.POST, new HttpEntity<>(map, headers), Map.class); + + String accessTokenString = (String) response.getBody().get("access_token"); + + AccessToken accessToken = AdapterTokenVerifier.verifyToken(accessTokenString, deployment); + + System.out.println("login successful"); + List realmRoles = accessToken.getRealmAccess().getRoles().stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()); + + return new UsernamePasswordAuthenticationToken(authentication.getName(), authentication.getCredentials(), realmRoles); + + } catch (VerificationException vex) { + throw new AuthenticationServiceException("keycloak direct access grant auth failed: bad token", vex); + } catch (HttpClientErrorException.Unauthorized hceex) { + throw new AuthenticationServiceException("keycloak direct access grant auth failed: bad credentials", hceex); + } + + } + + @Override + public boolean supports(Class type) { + return UsernamePasswordAuthenticationToken.class.equals(type); + } +} diff --git a/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/security/WebSecurityConfig.java b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/security/WebSecurityConfig.java index da522ad13..5be99a5c0 100644 --- a/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/security/WebSecurityConfig.java +++ b/clearing-parent/backend-api/src/main/java/ru/spcex/clearing/backendapi/security/WebSecurityConfig.java @@ -4,10 +4,13 @@ import org.keycloak.adapters.springsecurity.KeycloakConfiguration; import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider; import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; +import org.springframework.security.config.core.GrantedAuthorityDefaults; import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper; import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; @@ -46,13 +49,29 @@ public class WebSecurityConfig extends KeycloakWebSecurityConfigurerAdapter { .authorizeRequests() .antMatchers("/anonymous/**").permitAll() .antMatchers("/sso/login").permitAll() + .antMatchers("/login").permitAll() .antMatchers("/error").permitAll() .antMatchers("/backend-api-login/**").permitAll() .anyRequest(); if (securityDisabled) { anyReq.permitAll(); } else { - anyReq.hasAnyRole("admin", "default-roles-master"); + anyReq.hasAnyRole("admin", "default-roles-master").and().formLogin(); } } + + + @Bean + public GrantedAuthorityDefaults grantedAuthorityDefaults() { + // Remove the ROLE_ prefix + return new GrantedAuthorityDefaults(""); + } + + @Autowired + private AuthenticationProvider authenticationProvider; + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.authenticationProvider(authenticationProvider); + } } diff --git a/clearing-parent/backend-api/src/main/resources/keycloak.json b/clearing-parent/backend-api/src/main/resources/keycloak.json index 4c07b528d..2db328740 100644 --- a/clearing-parent/backend-api/src/main/resources/keycloak.json +++ b/clearing-parent/backend-api/src/main/resources/keycloak.json @@ -8,10 +8,10 @@ "cors-max-age" : 1000, "cors-allowed-methods" : "POST, PUT, DELETE, GET", "cors-exposed-headers" : "WWW-Authenticate", - "bearer-only" : false, + "bearer-only" : true, "enable-basic-auth" : false, "expose-token" : false, - "verify-token-audience" : true, + "verify-token-audience" : false, "credentials" : { "secret" : "WnOVCxcAmrUYc8IjiFHOuRif5Oesoyfr" },