Hello Habr!
As you know, spring OAuth2.0.x went into support mode almost 2 years ago , and most of its functionality is now available in spring-security ( mapping matrix ). Spring-security refused to port the Authorization service ( roadmap ) and propose to use free or paid analogs instead, in particular keycloak . In this post, we would like to share different options for connecting keycloak to spring-boot applications.
Content
-
-
We connect Keycloak using an adapter
Using OAuth2 Client from spring-security
We connect the application as a ResourceService
Authorizing service calls using keycloak
A little about 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 . , , , . , , , , .
!