I'm writing a spring boot shell application which should access a rest API which is protected by keycloak. In a web application is everything fine. The browser redirects me to keycloak and keycloak back to my application and voilà I'm authenticated.
But in Cli/Shell application without browser it seems to be impossible.
All I want is to get an access token to attach it to each rest request. Like this:
POST {{realmUri}}/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded
Accept: application/json
grant_type=password
&username={{username}}
&password={{userPassword}}
&client_id={{clientId}}
&client_secret={{clientSecret}}
&scope=openid+phone
That returns the access token. How can I achieve this via spring security oauth. The "Resource Owner Password Flow" should be the right thing but it is not inplemented.
But I think it must be possible.
Maybe OAuth2LoginAuthenticationProvider could do the job.
That's my final solution:
@Service
@Slf4j
@RequiredArgsConstructor
public class TokenService {
private OAuth2User currentUser;
@Getter
@Setter
@ToString
public static class OAuth2AccessTokenResponse {
@JsonProperty(OAuth2ParameterNames.ACCESS_TOKEN)
private String accessToken;
@JsonProperty(OAuth2ParameterNames.EXPIRES_IN)
private String expiresIn;
@JsonProperty("refresh_expires_in")
private String refreshExpiresIn;
@JsonProperty(OAuth2ParameterNames.REFRESH_TOKEN)
private String refreshToken;
@JsonProperty(OAuth2ParameterNames.TOKEN_TYPE)
private String tokenType;
@JsonProperty("not-before-policy")
private String notBeforePolicy;
@JsonProperty("session_state")
private String sessionState;
@JsonProperty(OAuth2ParameterNames.SCOPE)
private String scope;
}
@Data
@Builder
@RequiredArgsConstructor
public static class OAuth2User {
private final Jwt accessToken;
private final OAuth2AccessTokenResponse response;
public String getRefreshToken() {
return response.refreshToken;
}
}
private final OAuth2ClientProperties tokenServiceProperties;
private final RestTemplate simpleRestTemplate = new RestTemplateBuilder().build();;
protected OAuth2User resposeToUser(OAuth2AccessTokenResponse response) {
var jwtDecoder = JwtDecoders
.fromIssuerLocation(tokenServiceProperties.getProvider().get("keycloak").getIssuerUri());
Jwt accessToken = jwtDecoder.decode(response.getAccessToken());
return OAuth2User.builder().accessToken(accessToken).response(response).build();
}
public OAuth2User authenticateUser(String username, String password) {
log.info("Try to login user {}", username);
var token = sendUserAuthenticationRequest(username, password);
log.debug("User {} successfully log in {}", username, token);
currentUser = token;
return currentUser;
}
OAuth2User sendUserAuthenticationRequest(String username, String password) {
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("grant_type", "password");
map.add("client_id", tokenServiceProperties.getRegistration().get("keycloak").getClientId());
map.add("username", username);
map.add("password", password);
var token = sendKeycloakRequest(map);
return resposeToUser(token);
}
OAuth2User sendTokenRefreshRequest(String refreshToken) {
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("grant_type", "refresh_token");
map.add("client_id", tokenServiceProperties.getRegistration().get("keycloak").getClientId());
map.add("refresh_token", refreshToken);
var token = sendKeycloakRequest(map);
return resposeToUser(token);
}
OAuth2AccessTokenResponse sendKeycloakRequest(MultiValueMap<String, String> map) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
ResponseEntity<OAuth2AccessTokenResponse> exchange = simpleRestTemplate.exchange(
tokenServiceProperties.getProvider().get("keycloak").getTokenUri(), HttpMethod.POST, request,
OAuth2AccessTokenResponse.class);
return exchange.getBody();
}
public String getAccessToken() {
if (currentUser == null) {
throw new CliException(ErrorCode.user_not_logged_in);
}
if (currentUser.accessToken.getExpiresAt().isBefore(Instant.now())) {
log.info("Refresh token {}", currentUser.accessToken);
currentUser = sendTokenRefreshRequest(currentUser.getRefreshToken());
}
return currentUser.accessToken.getTokenValue();
}
}