如何在spring Boot shell应用程序中进行oauth2身份验证

goqiplq2  于 2023-10-23  发布在  Shell
关注(0)|答案(3)|浏览(154)

我正在编写一个Sping Boot Shell应用程序,它应该访问一个受keycloak保护的REST API。在Web应用程序中,一切都很好。浏览器将我重定向到keycloak,然后keycloak返回到我的应用程序,瞧,我通过了身份验证。
但在没有浏览器的Cli/Shell应用程序中,这似乎是不可能的。
我只想获得一个访问令牌,将其附加到每个REST请求。就像这样:

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

返回访问令牌的。如何通过Spring Security OAuth实现这一点。“资源所有者密码流”应该是正确的事情,但它没有实现。
但我认为这一定是可能的。
也许OAuth2 LoginAuthenticationProvider可以完成这项工作。

ulydmbyx

ulydmbyx1#

您可以使用Sping Boot 中的RestTemplate向Keycloak令牌端点发送POST请求,并提供必要的参数(grant_type、username、password等),然后从响应中提取访问令牌,并将其作为身份验证的承载令牌附加到REST API请求。

pftdvrlh

pftdvrlh2#

我找到了一个解决方案,当然这种方式是弃用。有更好的主意吗?我怎样才能用不被弃用的类实现同样的目标呢?

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
import org.springframework.security.oauth2.client.PasswordOAuth2AuthorizedClientProvider;

import lombok.RequiredArgsConstructor;

@SpringBootApplication
@Configuration
@RequiredArgsConstructor
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    private final OAuth2ClientProperties tokenServiceProperties;

    public String login(String username, String password) {
        var clientConfig = new OAuth2ClientPropertiesMapper(tokenServiceProperties).asClientRegistrations();
        OAuth2AuthorizationContext oAuth2AuthorizeContext = OAuth2AuthorizationContext
                .withClientRegistration(clientConfig.get("keycloak")).attributes(attrs -> {
                    attrs.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
                    attrs.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
                    attrs.put(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
                }).principal(new UsernamePasswordAuthenticationToken("no", "matter")).build();

        var user = new PasswordOAuth2AuthorizedClientProvider().authorize(oAuth2AuthorizeContext);
        return user.getAccessToken().getTokenValue();
    }

    @Bean
    CommandLineRunner run() {
        System.out.println(login("myapp_user_user", "password"));
        return aa -> {
        };
    }

}

以及application.properties:

spring.security.oauth2.client.registration.keycloak.client-id=my-app
spring.security.oauth2.client.registration.keycloak.authorization-grant-type=password
spring.security.oauth2.client.provider.keycloak.issuer-uri=http://localhost:5050/realms/MyRealm
azpvetkf

azpvetkf3#

这是我的最终解决方案:

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

}

相关问题