Search code examples
springspring-bootshelloauthkeycloak

How to make a oauth2 authentication within a spring boot shell application


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.


Solution

  • 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();
        }
    
    }