Halo Habr!
Seperti yang Anda ketahui, pegas OAuth2.0.x masuk ke mode dukungan hampir 2 tahun yang lalu , dan sebagian besar fungsinya sekarang tersedia dalam keamanan pegas ( matriks pemetaan ). Spring-security menolak untuk memasukkan layanan Otorisasi ( peta jalan ) dan mengusulkan untuk menggunakan analog gratis atau berbayar sebagai gantinya, dalam keycloak tertentu . Dalam posting ini, kami ingin membagikan opsi berbeda untuk menghubungkan keycloak ke aplikasi spring-boot.
Kandungan
-
Luncurkan dan konfigurasikan keycloak
Kami menghubungkan Keycloak menggunakan adaptor
Menggunakan Klien OAuth2 dari keamanan pegas
Kami menghubungkan aplikasi sebagai ResourceService
Mengotorisasi panggilan layanan menggunakan keycloak
Sedikit tentang Keycloak
SSO (Single sign on) .
, Keycloak:
Single-Sign On and Single-Sign Out.
OpenID/OAuth 2.0/SAML.
Identity Brokering β OpenID Connect SAML.
Social Login β Google, GitHub, Facebook, Twitter.
User Federation β LDAP Active Directory .
Kerberos bridge β Kerberos .
realm.
JavaScript, WildFly, JBoss EAP, Fuse, Tomcat, Jetty, Spring.
.
- ...
keycloak
keycloak docker-compose. , , . docker-compose standalone postgres:
docker-compose.yml
version: "3.8"
services:
postgres:
container_name: postgres
image: library/postgres
environment:
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
POSTGRES_DB: keycloak_db
ports:
- "5432:5432"
restart: unless-stopped
keycloak:
image: jboss/keycloak
container_name: keycloak
environment:
DB_VENDOR: POSTGRES
DB_ADDR: postgres
DB_DATABASE: keycloak_db
DB_USER: ${POSTGRES_USER:-postgres}
DB_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
KEYCLOAK_USER: admin
KEYCLOAK_PASSWORD: admin_password
ports:
- "8484:8080"
depends_on:
- postgres
links:
- "postgres:postgres"
realm, , .
. realm "my_realm":
"my_client"
, ( -):
redirect_url
. : http://localhost:8080/*
- "ADMIN", "USER"
:
"admin"
"ADMIN"
:
"user"
"USER"
. "Credentials"
:
, spring boot .
Keycloak
keycloak - , boilerplate . (supported-platforms). Spring Boot Adapter.
, spring-boot ( ) Keycloak Spring Boot . maven :
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="<http://maven.apache.org/POM/4.0.0>" xmlns:xsi="<http://www.w3.org/2001/XMLSchema-instance>" xsi:schemaLocation="<http://maven.apache.org/POM/4.0.0> <https://maven.apache.org/xsd/maven-4.0.0.xsd>">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.9.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>org.akazakov.keycloak</groupId>
<artifactId>demo-keycloak-adapter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Demo Keycloak Adapter</name>
<description>Demo project for Spring Boot and Keycloak</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.keycloak.bom</groupId>
<artifactId>keycloak-adapter-bom</artifactId>
<version>12.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
, ( ):
@RestController
@RequestMapping("/api")
public class SampleController {
@GetMapping("/anonymous")
public String getAnonymousInfo() {
return "Anonymous";
}
@GetMapping("/user")
@PreAuthorize("hasRole('USER')")
public String getUserInfo() {
return "user info";
}
@GetMapping("/admin")
@PreAuthorize("hasRole('ADMIN')")
public String getAdminInfo() {
return "admin info";
}
@GetMapping("/service")
@PreAuthorize("hasRole('SERVICE')")
public String getServiceInfo() {
return "service info";
}
@GetMapping("/me")
public Object getMe() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication.getName();
}
}
keycloak, . , , application.yml :
server:
port: ${SERVER_PORT:8080}
spring:
application.name: ${APPLICATION_NAME:spring-security-keycloak}
keycloak:
auth-server-url: http://localhost:8484/auth
realm: my_realm
resource: my_client
public-client: true
spring-security, KeycloakWebSecurityConfigurerAdapter
, :
@KeycloakConfiguration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder authManagerBuilder) {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
authManagerBuilder.authenticationProvider(keycloakAuthenticationProvider);
}
@Bean
public KeycloakConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.authorizeRequests()
.antMatchers("/api/anonymous/**").permitAll()
.anyRequest().fullyAuthenticated();
}
}
. url. : http://localhost:8080/api/admin
. , :
, . , :
(http://localhost:8080/api/me
), uuid keycloak:
, , bearer-only: true
:
keycloak:
auth-server-url: http://localhost:8484/auth
realm: my_realm
resource: my_client
public-client: true
bearer-only: true
OAuth2 Client spring-security
keycloak boilerplate . . - , .
spring security 5 OAuth2 OIDC. OAuth2 spring-security keycloak.
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="<http://maven.apache.org/POM/4.0.0>" xmlns:xsi="<http://www.w3.org/2001/XMLSchema-instance>"
xsi:schemaLocation="<http://maven.apache.org/POM/4.0.0> <https://maven.apache.org/xsd/maven-4.0.0.xsd>">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.akazakov.keycloak</groupId>
<artifactId>demo-keycloak-oauth</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo-keycloak-oauth</name>
<description>Demo project for Spring Boot OAuth and Keycloak</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</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-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yaml
:
server:
port: ${SERVER_PORT:8080}
spring:
application.name: ${APPLICATION_NAME:spring-security-keycloak-oauth}
security:
oauth2:
client:
provider:
keycloak:
issuer-uri: http://localhost:8484/auth/realms/my_realm
registration:
keycloak:
client-id: my_client
"scope"
access token, "ROLE_USER"
. scope. realm'. , , oidcUserService
. "groups"
, . , spring security oidcUserService
:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests -> authorizeRequests
.antMatchers("/api/anonymous/**").permitAll()
.anyRequest().authenticated())
.oauth2Login(oauth2Login -> oauth2Login
.userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint
.oidcUserService(this.oidcUserService())
)
);
}
@Bean
public OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
final OidcUserService delegate = new OidcUserService();
return (userRequest) -> {
OidcUser oidcUser = delegate.loadUser(userRequest);
final Map<String, Object> claims = oidcUser.getClaims();
final JSONArray groups = (JSONArray) claims.get("groups");
final Set<GrantedAuthority> mappedAuthorities = groups.stream()
.map(role -> new SimpleGrantedAuthority(("ROLE_" + role)))
.collect(Collectors.toSet());
return new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
};
}
}
keycloak .
ResourceService
, . . keycloak resource server. , . : spring-security-oauth2-resource-server spring-security-oauth2-jose ( ). pom.xml
:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="<http://maven.apache.org/POM/4.0.0>" xmlns:xsi="<http://www.w3.org/2001/XMLSchema-instance>"
xsi:schemaLocation="<http://maven.apache.org/POM/4.0.0> <https://maven.apache.org/xsd/maven-4.0.0.xsd>">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.akazakov.keycloak</groupId>
<artifactId>demo-keycloak-resource</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo-keycloak-resource</name>
<description>Demo project for Spring Boot and Spring security and Keycloak</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
JWK (JSON Web Key) , . keycloak : http://${host}/auth/realms/${realm)/protocol/openid-connect/certs
. application.yml
:
server:
port: ${SERVER_PORT:8080}
spring:
application.name: ${APPLICATION_NAME:spring-security-keycloak-resource}
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: ${KEYCLOAK_REALM_CERT_URL:http://localhost:8484/auth/realms/my_realm/protocol/openid-connect/certs}
OAuth2 Client . jwtAuthenticationConverter
.
WebSecurityConfiguration
:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests -> authorizeRequests
.antMatchers("/api/anonymous/**").permitAll()
.anyRequest().authenticated())
.oauth2ResourceServer(resourceServerConfigurer -> resourceServerConfigurer
.jwt(jwtConfigurer -> jwtConfigurer
.jwtAuthenticationConverter(jwtAuthenticationConverter()))
);
}
@Bean
public Converter<Jwt, AbstractAuthenticationToken> jwtAuthenticationConverter() {
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter());
return jwtAuthenticationConverter;
}
@Bean
public Converter<Jwt, Collection<GrantedAuthority>> jwtGrantedAuthoritiesConverter() {
JwtGrantedAuthoritiesConverter delegate = new JwtGrantedAuthoritiesConverter();
return new Converter<>() {
@Override
public Collection<GrantedAuthority> convert(Jwt jwt) {
Collection<GrantedAuthority> grantedAuthorities = delegate.convert(jwt);
if (jwt.getClaim("realm_access") == null) {
return grantedAuthorities;
}
JSONObject realmAccess = jwt.getClaim("realm_access");
if (realmAccess.get("roles") == null) {
return grantedAuthorities;
}
JSONArray roles = (JSONArray) realmAccess.get("roles");
final List<SimpleGrantedAuthority> keycloakAuthorities = roles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role)).collect(Collectors.toList());
grantedAuthorities.addAll(keycloakAuthorities);
return grantedAuthorities;
}
};
}
}
(jwtGrantedAuthoritiesConverter
), "realm_access"
. , , , , .
. Intellij idea http , VSCode - Rest Client. , keycloak, :
###
POST <http://localhost:8484/auth/realms/my_realm/protocol/openid-connect/token>
Content-Type: application/x-www-form-urlencoded
client_id=my_client&grant_type=password&scope=openid&username=admin&password=admin
> {% client.global.set("auth_token", response.body.access_token); %}
:
POST <http://localhost:8484/auth/realms/my_realm/protocol/openid-connect/token>
HTTP/1.1 200 OK
...
Content-Type: application/json
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJlb21qWFY2d3dNek8xVS0tYUdhVllpSHM3eURaZVM1aU96bl9JR3RlS1ZzIn0.eyJleHAiOjE2MTY2NTQzNjEsImlhdCI6MTYxNjY1NDA2MSwianRpIjoiMGQwMjg2YWUtYTlmYy00MzcxLWFmM2ItZjJlNTM5N2I4NzViIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4NDg0L2F1dGgvcmVhbG1zL215X3JlYWxtIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjkzMGIxMTNmLWI0NzUtNDhkMC05NTQxLWMyYzI2MWZlYmRmZCIsInR5cCI6IkJlYXJlciIsImF6cCI6Im15X2NsaWVudCIsInNlc3Npb25fc3RhdGUiOiI1ZDI5ZDQ2ZS1iOTI2LTRkNTktODlmOC0yNDM2ZWRjYWU0ZjAiLCJhY3IiOiIxIiwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwiQURNSU4iLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6ImFkbWluIn0.dvGvYhhhfH8r6EP8k_spFwBS35ulYMTWNL4lcz9PR2e-p4FU-ehre1EQA8xpbkYzYEWRB_elzTya5IhbYR8KArrujplIDNAOlqJ9W6a4Tx-r44QCteM0DW4BNzbZAH2L0Bg7aSstRKUuULceRNYQcdCvSFjEU5DsHk26a6TM5KCrkv0ryGo11pam-pnbs2Z2jOSfSHvOAfMNL9OVJYRBjlTmsEzzgH9dHSa_pT2Q-SvgvfCcwfY0XkgUZkMPUtz85-lqchROb4XpHOiy3Cfn8MgrGNwhf-MsmN5wiAGe0DI_LW2Jxr3boZMLS4AuuNQ7agr65g-JuO9-LhlgndxN8g",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJmNGEwNWQxNy0yNWU4LTRjMjEtOTMyMC0zMzcwODlhNTg5MjQifQ.eyJleHAiOjE2MTY2NTU4NjEsImlhdCI6MTYxNjY1NDA2MSwianRpIjoiMjNmNDBiZWUtNmQ3Ny00ZTIxLTg0NTItNDg1NDc2OTk1ZDUyIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4NDg0L2F1dGgvcmVhbG1zL215X3JlYWxtIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo4NDg0L2F1dGgvcmVhbG1zL215X3JlYWxtIiwic3ViIjoiOTMwYjExM2YtYjQ3NS00OGQwLTk1NDEtYzJjMjYxZmViZGZkIiwidHlwIjoiUmVmcmVzaCIsImF6cCI6Im15X2NsaWVudCIsInNlc3Npb25fc3RhdGUiOiI1ZDI5ZDQ2ZS1iOTI2LTRkNTktODlmOC0yNDM2ZWRjYWU0ZjAiLCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGVtYWlsIn0.r4BrjwfavKFF8dst3AyRi0LTfymbSVfDKDT9KyMpmzk",
"token_type": "bearer",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJlb21qWFY2d3dNek8xVS0tYUdhVllpSHM3eURaZVM1aU96bl9JR3RlS1ZzIn0.eyJleHAiOjE2MTY2NTQzNjEsImlhdCI6MTYxNjY1NDA2MSwiYXV0aF90aW1lIjowLCJqdGkiOiJiN2UwNDhmZS01ZTRjLTQxMWYtYTBjMC0xNGExYzhlOGJhYWEiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0Ojg0ODQvYXV0aC9yZWFsbXMvbXlfcmVhbG0iLCJhdWQiOiJteV9jbGllbnQiLCJzdWIiOiI5MzBiMTEzZi1iNDc1LTQ4ZDAtOTU0MS1jMmMyNjFmZWJkZmQiLCJ0eXAiOiJJRCIsImF6cCI6Im15X2NsaWVudCIsInNlc3Npb25fc3RhdGUiOiI1ZDI5ZDQ2ZS1iOTI2LTRkNTktODlmOC0yNDM2ZWRjYWU0ZjAiLCJhdF9oYXNoIjoiRlh2VzB2Z3pwd3R6N1FabEZtTFhJdyIsImFjciI6IjEiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6ImFkbWluIn0.ZDeZg4Z-PPmn2fVm7opGLRutzDh6l8uRYqZzbqIX7wk0GhgtMHV1CW8RvDd51AuYw81WyoMyRAD_-T6ne58Rt9f5XNZZfS8xoXzTFV1xH6XigOVQH2jIHN-2VIM1IgJnteo7nuTz9zo4OXIFvEjaFHq4AXDkiq6jhThv0qPS3WrAA-MutyW8G37GM0fsCgANvlGKoWm1_1wKyeTZ0Gfug32Vf6gUikfxA9bmaS4oGYGc6lqFE6EHgtjIn0q9gNUfpEXaqpiL3mCBu9V6sJG5Rp_MOqp-aXrM9NbLTz2JTXevtClHI6qVUIoh8OXXXT98QmKrVr9Cyr9BRUrQyt0Zzg",
"not-before-policy": 0,
"session_state": "5d29d46e-b926-4d59-89f8-2436edcae4f0",
"scope": "openid profile email"
}
Response code: 200 (OK); Time: 114ms; Content length: 2987 bytes
, :
GET <http://localhost:8080/api/admin>
Authorization: Bearer {{auth_token}}
Content-Type: application/json
:
GET <http://localhost:8080/api/admin>
HTTP/1.1 200
...
admin info
Response code: 200; Time: 34ms; Content length: 10 bytes
keycloak
. , - , - . Client Credentials Flow, keycloak ( ).
, :
("Access Type"
) "confidential"
"Service accounts Enabled"
. :
, , , , :
. "Service Account Roles"
- "SERVICE"
:
client_id client_secret :
, http://localhost:8080/api/service
.
, keycloak:
@Component
public class KeycloakAuthClient {
private static final Logger log = LoggerFactory
.getLogger(KeycloakAuthClient.class);
private static final String TOKEN_PATH = "/token";
private static final String GRANT_TYPE = "grant_type";
private static final String CLIENT_ID = "client_id";
private static final String CLIENT_SECRET = "client_secret";
public static final String CLIENT_CREDENTIALS = "client_credentials";
@Value("${app.keycloak.auth-url:http://localhost:8484/auth/realms/my_realm/protocol/openid-connect}")
private String authUrl;
@Value("${app.keycloak.client-id:service_client}")
private String clientId;
@Value("${app.keycloak.client-secret:acb719cf-4afd-42d3-91f2-93a60b3f2023}")
private String clientSecret;
private final RestTemplate restTemplate;
public KeycloakAuthClient(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public KeycloakAuthResponse authenticate() {
MultiValueMap<String, String> paramMap = new LinkedMultiValueMap<>();
paramMap.add(CLIENT_ID, clientId);
paramMap.add(CLIENT_SECRET, clientSecret);
paramMap.add(GRANT_TYPE, CLIENT_CREDENTIALS);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
String url = authUrl + TOKEN_PATH;
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(paramMap, headers);
log.info("Try to authenticate");
ResponseEntity<KeycloakAuthResponse> response =
restTemplate.exchange(url,
HttpMethod.POST,
entity,
KeycloakAuthResponse.class);
if (!response.getStatusCode().is2xxSuccessful()) {
log.error("Failed to authenticate");
throw new RuntimeException("Failed to authenticate");
}
log.info("Authentication success");
return response.getBody();
}
}
authenticate
keycloak KeycloakAuthResponse
:
public class KeycloakAuthResponse {
@JsonProperty("access_token")
private String accessToken;
@JsonProperty("expires_in")
private Integer expiresIn;
@JsonProperty("refresh_expires_in")
private Integer refreshExpiresIn;
@JsonProperty("refresh_token")
private String refreshToken;
@JsonProperty("token_type")
private String tokenType;
@JsonProperty("id_token")
private String idToken;
@JsonProperty("session_state")
private String sessionState;
@JsonProperty("scope")
private String scope;
// Getters and setters or lombok ...
}
access_token
, . :
@SpringBootApplication
public class DemoServiceAuthApplication implements CommandLineRunner {
private static final String BEARER = "Bearer ";
private static final String SERVICE_INFO_URL = "http://localhost:8080/api/service";
private final KeycloakAuthClient keycloakAuthClient;
private final RestTemplate restTemplate;
private static final Logger log = LoggerFactory
.getLogger(DemoServiceAuthApplication.class);
public DemoServiceAuthApplication(KeycloakAuthClient keycloakAuthClient, RestTemplate restTemplate) {
this.keycloakAuthClient = keycloakAuthClient;
this.restTemplate = restTemplate;
}
public static void main(String[] args) {
SpringApplication.run(DemoServiceAuthApplication.class, args);
}
@Override
public void run(String... args) {
final KeycloakAuthResponse authenticate = keycloakAuthClient.authenticate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(authenticate.getAccessToken());
log.info("Make request to resource server");
final ResponseEntity<String> responseEntity = restTemplate.exchange(SERVICE_INFO_URL, HttpMethod.GET, new HttpEntity(headers), String.class);
if (!responseEntity.getStatusCode().is2xxSuccessful()) {
log.error("Failed to request");
throw new RuntimeException("Failed to request");
}
log.info("Response data: {}", responseEntity.getBody());
}
}
keycloak, , HTTP Headers Authorization: Bearer ...
:
. ____ _ __ _ _
/\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\
( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\
\\\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.4.4)
2021-04-13 16:04:36.672 INFO 19240 --- [ main] o.a.keycloak.DemoServiceAuthApplication : Starting DemoServiceAuthApplication using Java 14.0.1 on MacBook-Pro.local with PID 19240 (/Users/akazakov/Projects/spring-boot-keycloak/demo-service-auth/target/classes started by akazakov in /Users/akazakov/Projects/spring-boot-keycloak)
2021-04-13 16:04:36.674 INFO 19240 --- [ main] o.a.keycloak.DemoServiceAuthApplication : No active profile set, falling back to default profiles: default
2021-04-13 16:04:37.199 INFO 19240 --- [ main] o.a.keycloak.DemoServiceAuthApplication : Started DemoServiceAuthApplication in 0.814 seconds (JVM running for 6.425)
2021-04-13 16:04:37.203 INFO 19240 --- [ main] o.akazakov.keycloak.KeycloakAuthClient : Try to authenticate
2021-04-13 16:04:53.697 INFO 19240 --- [ main] o.akazakov.keycloak.KeycloakAuthClient : Authentication success
2021-04-13 16:04:53.697 INFO 19240 --- [ main] o.a.keycloak.DemoServiceAuthApplication : Make request to resource server
2021-04-13 16:04:54.088 INFO 19240 --- [ main] o.a.keycloak.DemoServiceAuthApplication : Response data: service info
Disconnected from the target VM, address: '127.0.0.1:57479', transport: 'socket'
Process finished with exit code 0
, KeycloakAuthClient
, , .
keycloak , , . . spring . , , , . , , , , .
!