Spring Security - Exemple de service REST avec authentification OAuth2 via BitBucket et JWT

Dans l' article précédent , nous avons développé une application Web sécurisée simple qui utilisait OAuth2 pour authentifier les utilisateurs avec Bitbucket comme serveur d'autorisation. Pour certains, un tel bundle peut sembler étrange, mais imaginez que nous développons un serveur CI (Continuous Integration) et que nous aimerions avoir accès aux ressources utilisateur dans le système de contrôle de version. Par exemple, la célèbre plateforme de CI drone.io fonctionne sur le même principe .



Dans l'exemple précédent, une session HTTP (et des cookies) ont été utilisés pour autoriser les requêtes au serveur. Cependant, pour la mise en œuvre d'un service REST, cette méthode d'autorisation n'est pas adaptée, car l'une des exigences de l'architecture REST est l'absence d'état. Dans cet article, nous allons implémenter un service REST, l'autorisation des requêtes à laquelle sera effectuée à l'aide d'un jeton d'accès.



Un peu de théorie



L'authentification est le processus de vérification des informations d'identification de l'utilisateur (login / mot de passe). L'authentification de l'utilisateur est effectuée en comparant le login / mot de passe saisi par lui avec les données enregistrées.



L'autorisation est la vérification des droits d'un utilisateur à accéder à certaines ressources. L'autorisation est effectuée directement lorsque l'utilisateur accède à la ressource.



Considérons l'ordre de travail des deux méthodes susmentionnées d'autorisation des requêtes.

Autoriser les requêtes à l'aide d'une session HTTP:



  • L'utilisateur est authentifié de l'une des manières.
  • Une session HTTP est créée sur le serveur et un cookie JSESSIONID stockant l'identifiant de session.
  • Le cookie JSESSIONID est transmis au client et stocké dans le navigateur.
  • À chaque demande suivante, un cookie JSESSIONID est envoyé au serveur.
  • Le serveur trouve la session HTTP correspondante avec des informations sur l'utilisateur actuel et détermine si l'utilisateur est autorisé à effectuer cet appel.
  • Pour quitter l'application, vous devez supprimer la session HTTP du serveur.


Autoriser les demandes à l'aide d'un jeton d'accès:



  • L'utilisateur est authentifié de l'une des manières.
  • Le serveur génère un jeton d'accès signé avec une clé privée, puis l'envoie au client. Le token contient l'identifiant de l'utilisateur et son rôle.
  • Le jeton est stocké sur le client et transmis au serveur à chaque requête ultérieure. En règle générale, l'en-tête HTTP d'autorisation est utilisé pour transférer le jeton.
  • Le serveur vérifie la signature du jeton, en extrait l'identifiant de l'utilisateur, son rôle et détermine si l'utilisateur a le droit d'effectuer cet appel.
  • Pour quitter l'application, supprimez simplement le token sur le client sans avoir à interagir avec le serveur.


Le jeton Web JSON (JWT) est actuellement un format de jeton d'accès courant. Le jeton JWT contient trois blocs, séparés par des points: en-tête, charge utile et signature. Les deux premiers blocs sont au format JSON et encodés au format base64. L'ensemble de champs peut être constitué de noms réservés (iss, iat, exp) ou de paires nom / valeur arbitraires. La signature peut être générée à l'aide d'algorithmes de chiffrement symétriques et asymétriques.



la mise en oeuvre



Nous allons implémenter un service REST qui fournit l'API suivante:



  • GET / auth / login - lance le processus d'authentification de l'utilisateur.
  • POST / auth / token - demande une nouvelle paire de jetons d'accès / d'actualisation.
  • GET / api / repositories - Obtenez la liste des référentiels Bitbucket de l'utilisateur actuel.




Architecture d'application de haut niveau



Notez que puisque l'application se compose de trois composants en interaction, en plus d'autoriser les demandes des clients au serveur, Bitbucket autorise les demandes du serveur sur celui-ci. Nous ne configurerons pas l'autorisation de méthode par rôle, afin de ne pas compliquer l'exemple. Nous n'avons qu'une seule méthode API GET / api / repositories qui ne peut être appelée que par des utilisateurs authentifiés. Le serveur peut effectuer toutes les opérations sur Bitbucket qui sont autorisées par l'enregistrement OAuth du client.





Le processus d'enregistrement d'un client OAuth est décrit dans l' article précédent .



Pour l'implémentation, nous utiliserons Spring Boot version 2.2.2.RELEASE et Spring Security version 5.2.1.RELEASE.



Remplacer AuthenticationEntryPoint



Dans une application Web standard, lorsqu'une ressource sécurisée est accédée et qu'il n'y a pas d'objet d'authentification dans le contexte de sécurité, Spring Security redirigera l'utilisateur vers la page d'authentification. Cependant, pour un service REST, le comportement le plus approprié dans ce cas serait de renvoyer l'état HTTP 401 (NON AUTORISÉ).



RestAuthenticationEntryPoint
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(
            HttpServletRequest request,
            HttpServletResponse response,
            AuthenticationException authException) throws IOException {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
    }
}




Créer un point de terminaison de connexion



Nous utilisons toujours OAuth2 avec le type d'autorisation Code d'autorisation pour l'authentification des utilisateurs. Cependant, à l'étape précédente, nous avons remplacé le standard AuthenticationEntryPoint par notre propre implémentation, nous avons donc besoin d'un moyen explicite pour démarrer le processus d'authentification. Lorsque nous envoyons une requête GET à / auth / login, nous redirigeons l'utilisateur vers la page d'authentification Bitbucket. Le paramètre de cette méthode sera l'URL de rappel, où nous retournerons le jeton d'accès après une authentification réussie.



Point de terminaison de connexion
@Path("/auth")
public class AuthEndpoint extends EndpointBase {

...

    @GET
    @Path("/login")
    public Response authorize(@QueryParam(REDIRECT_URI) String redirectUri) {
        String authUri = "/oauth2/authorization/bitbucket";
        UriComponentsBuilder builder = fromPath(authUri).queryParam(REDIRECT_URI, redirectUri);
        return handle(() -> temporaryRedirect(builder.build().toUri()).build());
    }
}




Remplacer AuthenticationSuccessHandler



AuthenticationSuccessHandler est appelé après une authentification réussie. Générons ici un jeton d'accès, un jeton d'actualisation et redirigeons vers l'adresse de rappel qui a été envoyée au début du processus d'authentification. Nous renvoyons le jeton d'accès avec le paramètre de requête GET et le jeton d'actualisation dans le cookie httpOnly. Nous analyserons ce qu'est un jeton d'actualisation plus tard.



ExampleAuthenticationSuccessHandler
public class ExampleAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    private final TokenService tokenService;

    private final AuthProperties authProperties;

    private final HttpCookieOAuth2AuthorizationRequestRepository authorizationRequestRepository;

    public ExampleAuthenticationSuccessHandler(
            TokenService tokenService,
            AuthProperties authProperties,
            HttpCookieOAuth2AuthorizationRequestRepository authorizationRequestRepository) {
        this.tokenService = requireNonNull(tokenService);
        this.authProperties = requireNonNull(authProperties);
        this.authorizationRequestRepository = requireNonNull(authorizationRequestRepository);
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        log.info("Logged in user {}", authentication.getPrincipal());
        super.onAuthenticationSuccess(request, response, authentication);
    }

    @Override
    protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        Optional<String> redirectUri = getCookie(request, REDIRECT_URI).map(Cookie::getValue);

        if (redirectUri.isPresent() && !isAuthorizedRedirectUri(redirectUri.get())) {
            throw new BadRequestException("Received unauthorized redirect URI.");
        }

        return UriComponentsBuilder.fromUriString(redirectUri.orElse(getDefaultTargetUrl()))
                .queryParam("token", tokenService.newAccessToken(toUserContext(authentication)))
                .build().toUriString();
    }

    @Override
    protected void handle(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        redirectToTargetUrl(request, response, authentication);
    }

    private boolean isAuthorizedRedirectUri(String uri) {
        URI clientRedirectUri = URI.create(uri);
        return authProperties.getAuthorizedRedirectUris()
                .stream()
                .anyMatch(authorizedRedirectUri -> {
                    // Only validate host and port. Let the clients use different paths if they want to.
                    URI authorizedURI = URI.create(authorizedRedirectUri);
                    return authorizedURI.getHost().equalsIgnoreCase(clientRedirectUri.getHost())
                            && authorizedURI.getPort() == clientRedirectUri.getPort();
                });
    }

    private TokenService.UserContext toUserContext(Authentication authentication) {
        ExampleOAuth2User principal = (ExampleOAuth2User) authentication.getPrincipal();
        return TokenService.UserContext.builder()
                .login(principal.getName())
                .name(principal.getFullName())
                .build();
    }

    private void addRefreshTokenCookie(HttpServletResponse response, Authentication authentication) {
        RefreshToken token = tokenService.newRefreshToken(toUserContext(authentication));
        addCookie(response, REFRESH_TOKEN, token.getId(), (int) token.getValiditySeconds());
    }

    private void redirectToTargetUrl(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        String targetUrl = determineTargetUrl(request, response, authentication);

        if (response.isCommitted()) {
            logger.debug("Response has already been committed. Unable to redirect to " + targetUrl);
            return;
        }

        addRefreshTokenCookie(response, authentication);
        authorizationRequestRepository.removeAuthorizationRequestCookies(request, response);
        getRedirectStrategy().sendRedirect(request, response, targetUrl);
    }
}




Remplacer AuthenticationFailureHandler



Dans le cas où l'utilisateur n'est pas authentifié, nous le redirigerons vers l'adresse de rappel qui a été transmise au début du processus d'authentification avec le paramètre d'erreur contenant le texte d'erreur.



ExampleAuthenticationFailureHandler
public class ExampleAuthenticationFailureHandler implements AuthenticationFailureHandler {

    private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    private final HttpCookieOAuth2AuthorizationRequestRepository authorizationRequestRepository;

    public ExampleAuthenticationFailureHandler(
            HttpCookieOAuth2AuthorizationRequestRepository authorizationRequestRepository) {
        this.authorizationRequestRepository = requireNonNull(authorizationRequestRepository);
    }

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
        String targetUrl = getFailureUrl(request, exception);
        authorizationRequestRepository.removeAuthorizationRequestCookies(request, response);
        redirectStrategy.sendRedirect(request, response, targetUrl);
    }

    private String getFailureUrl(HttpServletRequest request, AuthenticationException exception) {
        String targetUrl = getCookie(request, Cookies.REDIRECT_URI)
                .map(Cookie::getValue)
                .orElse(("/"));

        return UriComponentsBuilder.fromUriString(targetUrl)
                .queryParam("error", exception.getLocalizedMessage())
                .build().toUriString();
    }
}




Créer TokenAuthenticationFilter



La tâche de ce filtre est d'extraire le jeton d'accès de l'en-tête Authorization, s'il est présent, pour le valider et initialiser le contexte de sécurité.



TokenAuthenticationFilter
public class TokenAuthenticationFilter extends OncePerRequestFilter {

    private final UserService userService;

    private final TokenService tokenService;

    public TokenAuthenticationFilter(
            UserService userService, TokenService tokenService) {
        this.userService = requireNonNull(userService);
        this.tokenService = requireNonNull(tokenService);
    }

    @Override
    protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain chain) throws ServletException, IOException {
        try {
            Optional<String> jwtOpt = getJwtFromRequest(request);
            if (jwtOpt.isPresent()) {
                String jwt = jwtOpt.get();
                if (isNotEmpty(jwt) && tokenService.isValidAccessToken(jwt)) {
                    String login = tokenService.getUsername(jwt);
                    Optional<User> userOpt = userService.findByLogin(login);
                    if (userOpt.isPresent()) {
                        User user = userOpt.get();
                        ExampleOAuth2User oAuth2User = new ExampleOAuth2User(user);
                        OAuth2AuthenticationToken authentication = new OAuth2AuthenticationToken(oAuth2User, oAuth2User.getAuthorities(), oAuth2User.getProvider());
                        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                        SecurityContextHolder.getContext().setAuthentication(authentication);
                    }
                }
            }
        } catch (Exception e) {
            logger.error("Could not set user authentication in security context", e);
        }

        chain.doFilter(request, response);
    }

    private Optional<String> getJwtFromRequest(HttpServletRequest request) {
        String token = request.getHeader(AUTHORIZATION);
        if (isNotEmpty(token) && token.startsWith("Bearer ")) {
            token = token.substring(7);
        }
        return Optional.ofNullable(token);
    }
}




Créer un point de terminaison de jeton d'actualisation



Pour des raisons de sécurité, la durée de vie du jeton d'accès est généralement limitée. Ensuite, s'il est volé, un attaquant ne pourra pas l'utiliser indéfiniment. Afin de ne pas forcer l'utilisateur à se connecter encore et encore à l'application, le jeton d'actualisation est utilisé. Il est émis par le serveur après une authentification réussie avec le jeton d'accès et a une durée de vie plus longue. En l'utilisant, vous pouvez demander une nouvelle paire de jetons. Il est recommandé de stocker le jeton Refresh dans le cookie httpOnly.



Actualiser le point de terminaison du jeton
@Path("/auth")
public class AuthEndpoint extends EndpointBase {

...

    @POST
    @Path("/token")
    @Produces(APPLICATION_JSON)
    public Response refreshToken(@CookieParam(REFRESH_TOKEN) String refreshToken) {
        return handle(() -> {
            if (refreshToken == null) {
                throw new InvalidTokenException("Refresh token was not provided.");
            }
            RefreshToken oldRefreshToken = tokenService.findRefreshToken(refreshToken);
            if (oldRefreshToken == null || !tokenService.isValidRefreshToken(oldRefreshToken)) {
                throw new InvalidTokenException("Refresh token is not valid or expired.");
            }

            Map<String, String> result = new HashMap<>();
            result.put("token", tokenService.newAccessToken(of(oldRefreshToken.getUser())));

            RefreshToken newRefreshToken = newRefreshTokenFor(oldRefreshToken.getUser());
            return Response.ok(result).cookie(createRefreshTokenCookie(newRefreshToken)).build();
        });
    }
}




Remplacer AuthorizationRequestRepository



Spring Security utilise l'objet AuthorizationRequestRepository pour stocker les objets OAuth2AuthorizationRequest pendant la durée du processus d'authentification. L'implémentation par défaut est la classe HttpSessionOAuth2AuthorizationRequestRepository, qui utilise la session HTTP comme référentiel. Car notre service ne doit pas stocker d'état, cette implémentation ne nous convient pas. Implémentons notre propre classe qui utilisera des cookies HTTP.



HttpCookieOAuth2AuthorizationRequestRepository
public class HttpCookieOAuth2AuthorizationRequestRepository implements AuthorizationRequestRepository<OAuth2AuthorizationRequest> {

    private static final int COOKIE_EXPIRE_SECONDS = 180;

    private static final String OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME = "OAUTH2-AUTH-REQUEST";

    @Override
    public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) {
        return getCookie(request, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME)
                .map(cookie -> deserialize(cookie, OAuth2AuthorizationRequest.class))
                .orElse(null);
    }

    @Override
    public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, HttpServletRequest request, HttpServletResponse response) {
        if (authorizationRequest == null) {
            removeAuthorizationRequestCookies(request, response);
            return;
        }

        addCookie(response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME, serialize(authorizationRequest), COOKIE_EXPIRE_SECONDS);
        String redirectUriAfterLogin = request.getParameter(QueryParams.REDIRECT_URI);
        if (isNotBlank(redirectUriAfterLogin)) {
            addCookie(response, REDIRECT_URI, redirectUriAfterLogin, COOKIE_EXPIRE_SECONDS);
        }
    }

    @Override
    public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request) {
        return loadAuthorizationRequest(request);
    }

    public void removeAuthorizationRequestCookies(HttpServletRequest request, HttpServletResponse response) {
        deleteCookie(request, response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME);
        deleteCookie(request, response, REDIRECT_URI);
    }

    private static String serialize(Object object) {
        return Base64.getUrlEncoder().encodeToString(SerializationUtils.serialize(object));
    }

    @SuppressWarnings("SameParameterValue")
    private static <T> T deserialize(Cookie cookie, Class<T> clazz) {
        return clazz.cast(SerializationUtils.deserialize(Base64.getUrlDecoder().decode(cookie.getValue())));
    }
}




Configurer Spring Security



Mettons tout ce qui précède ensemble et configurons Spring Security.



WebSecurityConfig
@Configuration
@EnableWebSecurity
public static class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final ExampleOAuth2UserService userService;

    private final TokenAuthenticationFilter tokenAuthenticationFilter;

    private final AuthenticationFailureHandler authenticationFailureHandler;

    private final AuthenticationSuccessHandler authenticationSuccessHandler;

    private final HttpCookieOAuth2AuthorizationRequestRepository authorizationRequestRepository;

    @Autowired
    public WebSecurityConfig(
            ExampleOAuth2UserService userService,
            TokenAuthenticationFilter tokenAuthenticationFilter,
            AuthenticationFailureHandler authenticationFailureHandler,
            AuthenticationSuccessHandler authenticationSuccessHandler,
            HttpCookieOAuth2AuthorizationRequestRepository authorizationRequestRepository) {
        this.userService = userService;
        this.tokenAuthenticationFilter = tokenAuthenticationFilter;
        this.authenticationFailureHandler = authenticationFailureHandler;
        this.authenticationSuccessHandler = authenticationSuccessHandler;
        this.authorizationRequestRepository = authorizationRequestRepository;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .cors().and()
                .csrf().disable()
                .formLogin().disable()
                .httpBasic().disable()
                .sessionManagement(sm -> sm.sessionCreationPolicy(STATELESS))
                .exceptionHandling(eh -> eh
                        .authenticationEntryPoint(new RestAuthenticationEntryPoint())
                )
                .authorizeRequests(authorizeRequests -> authorizeRequests
                        .antMatchers("/auth/**").permitAll()
                        .anyRequest().authenticated()
                )
                .oauth2Login(oauth2Login -> oauth2Login
                        .failureHandler(authenticationFailureHandler)
                        .successHandler(authenticationSuccessHandler)
                        .userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint.userService(userService))
                        .authorizationEndpoint(authEndpoint -> authEndpoint.authorizationRequestRepository(authorizationRequestRepository))
                );

        http.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
    }
}




Créer un point de terminaison de référentiels



Pour quelle authentification via OAuth2 et Bitbucket était nécessaire - la possibilité d'utiliser l'API Bitbucket pour accéder à vos ressources. Nous utilisons l'API des référentiels Bitbucket pour obtenir une liste des référentiels de l'utilisateur actuel.



Point de terminaison des référentiels
@Path("/api")
public class ApiEndpoint extends EndpointBase {

    @Autowired
    private BitbucketService bitbucketService;

    @GET
    @Path("/repositories")
    @Produces(APPLICATION_JSON)
    public List<Repository> getRepositories() {
        return handle(bitbucketService::getRepositories);
    }
}

public class BitbucketServiceImpl implements BitbucketService {

    private static final String BASE_URL = "https://api.bitbucket.org";

    private final Supplier<RestTemplate> restTemplate;

    public BitbucketServiceImpl(Supplier<RestTemplate> restTemplate) {
        this.restTemplate = restTemplate;
    }

    @Override
    public List<Repository> getRepositories() {
        UriComponentsBuilder uriBuilder = fromHttpUrl(format("%s/2.0/repositories", BASE_URL));
        uriBuilder.queryParam("role", "member");

        ResponseEntity<BitbucketRepositoriesResponse> response = restTemplate.get().exchange(
                uriBuilder.toUriString(),
                HttpMethod.GET,
                new HttpEntity<>(new HttpHeadersBuilder()
                        .acceptJson()
                        .build()),
                BitbucketRepositoriesResponse.class);

        BitbucketRepositoriesResponse body = response.getBody();
        return body == null ? emptyList() : extractRepositories(body);
    }

    private List<Repository> extractRepositories(BitbucketRepositoriesResponse response) {
        return response.getValues() == null
                ? emptyList()
                : response.getValues().stream().map(BitbucketServiceImpl.this::convertRepository).collect(toList());
    }

    private Repository convertRepository(BitbucketRepository bbRepo) {
        Repository repo = new Repository();
        repo.setId(bbRepo.getUuid());
        repo.setFullName(bbRepo.getFullName());
        return repo;
    }
}




Essai



Pour les tests, nous avons besoin d'un petit serveur HTTP auquel envoyer un jeton d'accès. Tout d'abord, essayons d'appeler le point de terminaison des référentiels sans jeton d'accès et assurez-vous que nous obtenons une erreur 401. Dans ce cas, nous nous authentifierons. Pour ce faire, démarrez le serveur et accédez au navigateur à l' adresse http: // localhost: 8080 / auth / login . Après avoir entré le nom d'utilisateur / mot de passe, le client recevra un jeton et appellera à nouveau le point de terminaison des référentiels. Ensuite, un nouveau jeton sera demandé et le point de terminaison des référentiels sera à nouveau appelé avec le nouveau jeton.



OAuth2JwtExampleClient
public class OAuth2JwtExampleClient {

    /**
     * Start client, then navigate to http://localhost:8080/auth/login.
     */
    public static void main(String[] args) throws Exception {
        AuthCallbackHandler authEndpoint = new AuthCallbackHandler(8081);
        authEndpoint.start(SOCKET_READ_TIMEOUT, true);

        HttpResponse response = getRepositories(null);
        assert (response.getStatusLine().getStatusCode() == SC_UNAUTHORIZED);

        Tokens tokens = authEndpoint.getTokens();
        System.out.println("Received tokens: " + tokens);
        response = getRepositories(tokens.getAccessToken());
        assert (response.getStatusLine().getStatusCode() == SC_OK);
        System.out.println("Repositories: " + IOUtils.toString(response.getEntity().getContent(), UTF_8));

        // emulate token usage - wait for some time until iat and exp attributes get updated
        // otherwise we will receive the same token
        Thread.sleep(5000);

        tokens = refreshToken(tokens.getRefreshToken());
        System.out.println("Refreshed tokens: " + tokens);

        // use refreshed token
        response = getRepositories(tokens.getAccessToken());
        assert (response.getStatusLine().getStatusCode() == SC_OK);
    }

    private static Tokens refreshToken(String refreshToken) throws IOException {
        BasicClientCookie cookie = new BasicClientCookie(REFRESH_TOKEN, refreshToken);
        cookie.setPath("/");
        cookie.setDomain("localhost");
        BasicCookieStore cookieStore = new BasicCookieStore();
        cookieStore.addCookie(cookie);

        HttpPost request = new HttpPost("http://localhost:8080/auth/token");
        request.setHeader(ACCEPT, APPLICATION_JSON.getMimeType());

        HttpClient httpClient = HttpClientBuilder.create().setDefaultCookieStore(cookieStore).build();
        HttpResponse execute = httpClient.execute(request);

        Gson gson = new Gson();
        Type type = new TypeToken<Map<String, String>>() {
        }.getType();
        Map<String, String> response = gson.fromJson(IOUtils.toString(execute.getEntity().getContent(), UTF_8), type);

        Cookie refreshTokenCookie = cookieStore.getCookies().stream()
                .filter(c -> REFRESH_TOKEN.equals(c.getName()))
                .findAny()
                .orElseThrow(() -> new IOException("Refresh token cookie not found."));
        return Tokens.of(response.get("token"), refreshTokenCookie.getValue());
    }

    private static HttpResponse getRepositories(String accessToken) throws IOException {
        HttpClient httpClient = HttpClientBuilder.create().build();
        HttpGet request = new HttpGet("http://localhost:8080/api/repositories");
        request.setHeader(ACCEPT, APPLICATION_JSON.getMimeType());
        if (accessToken != null) {
            request.setHeader(AUTHORIZATION, "Bearer " + accessToken);
        }
        return httpClient.execute(request);
    }
}




Sortie de la console client.



Received tokens: Tokens(accessToken=eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJldm9sdmVjaS10ZXN0a2l0IiwidXNlcm5hbWUiOiJFdm9sdmVDSSBUZXN0a2l0IiwiaWF0IjoxNjA1NDY2MDMxLCJleHAiOjE2MDU0NjY2MzF9.UuRYMdIxzc8ZFEI2z8fAgLz-LG_gDxaim25pMh9jNrDFK6YkEaDqDO8Huoav5JUB0bJyf1lTB0nNPaLLpOj4hw, refreshToken=BBF6dboG8tB4XozHqmZE5anXMHeNUncTVD8CLv2hkaU2KsfyqitlJpgkV4HrQqPk)

Repositories: [{"id":"{c7bb4165-92f1-4621-9039-bb1b6a74488e}","fullName":"test-namespace/test-repository1"},{"id":"{aa149604-c136-41e1-b7bd-3088fb73f1b2}","fullName":"test-namespace/test-repository2"}]

Refreshed tokens: Tokens(accessToken=eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJldm9sdmVjaS10ZXN0a2l0IiwidXNlcm5hbWUiOiJFdm9sdmVDSSBUZXN0a2l0IiwiaWF0IjoxNjA1NDY2MDM2LCJleHAiOjE2MDU0NjY2MzZ9.oR2A_9k4fB7qpzxvV5QKY1eU_8aZMYEom-ngc4Kuc5omeGPWyclfqmiyQTpJW_cHOcXbY9S065AE_GKXFMbh_Q, refreshToken=mdc5sgmtiwLD1uryubd2WZNjNzSmc5UGo6JyyzsiYsBgOpeaY3yw3T3l8IKauKYQ)


La source



Le code source complet de l'application examinée se trouve sur Github .



Liens





PS

Le service REST que nous avons créé fonctionne sur le protocole HTTP pour ne pas compliquer l'exemple. Mais comme nos jetons ne sont en aucun cas chiffrés, il est recommandé de passer à un canal sécurisé (HTTPS).



All Articles