Keamanan Musim Semi - Contoh Layanan REST dengan Autentikasi OAuth2 melalui BitBucket dan JWT

Di artikel sebelumnya , kami mengembangkan aplikasi web aman sederhana yang menggunakan OAuth2 untuk mengautentikasi pengguna dengan Bitbucket sebagai server otorisasi. Bagi sebagian orang, bundel semacam itu mungkin tampak aneh, tetapi bayangkan bahwa kami sedang mengembangkan server CI (Continuous Integration) dan ingin memiliki akses ke sumber daya pengguna di sistem kontrol versi. Misalnya, platform CI yang terkenal drone.io bekerja dengan prinsip yang sama .



Dalam contoh sebelumnya, sesi HTTP (dan cookie) digunakan untuk mengotorisasi permintaan ke server. Namun, untuk implementasi layanan REST, metode otorisasi ini tidak cocok, karena salah satu persyaratan arsitektur REST adalah tidak adanya status. Pada artikel ini, kami akan menerapkan layanan REST, otorisasi permintaan yang akan dilakukan menggunakan token akses.



Sedikit teori



Otentikasi adalah proses memverifikasi kredensial pengguna (login / kata sandi). Otentikasi pengguna dilakukan dengan membandingkan login / kata sandi yang dimasukkan olehnya dengan data yang disimpan.



Otorisasi adalah verifikasi hak pengguna untuk mengakses sumber daya tertentu. Otorisasi dilakukan secara langsung saat pengguna mengakses sumber daya.



Mari pertimbangkan urutan kerja dari dua metode otorisasi permintaan yang disebutkan di atas.

Memberi otorisasi permintaan menggunakan sesi HTTP:



  • Pengguna diautentikasi dengan salah satu cara.
  • Sesi HTTP dibuat di server dan cookie JSESSIONID menyimpan pengenal sesi.
  • Cookie JSESSIONID dikirim ke klien dan disimpan di browser.
  • Dengan setiap permintaan berikutnya, cookie JSESSIONID dikirim ke server.
  • Server menemukan sesi HTTP yang sesuai dengan informasi tentang pengguna saat ini dan menentukan apakah pengguna memiliki hak untuk melakukan panggilan ini.
  • Untuk keluar dari aplikasi, Anda harus menghapus sesi HTTP dari server.


Memberi otorisasi permintaan menggunakan token akses:



  • Pengguna diautentikasi dengan salah satu cara.
  • Server menghasilkan token akses yang ditandatangani dengan kunci pribadi dan kemudian mengirimkannya ke klien. Token berisi pengenal pengguna dan perannya.
  • Token disimpan di klien dan dikirim ke server dengan setiap permintaan berikutnya. Biasanya, header HTTP Otorisasi digunakan untuk mentransfer token.
  • Server memverifikasi tanda tangan token, mengekstrak darinya ID pengguna, perannya, dan menentukan apakah pengguna memiliki hak untuk melakukan panggilan ini.
  • Untuk keluar dari aplikasi, cukup hapus token pada klien tanpa harus berinteraksi dengan server.


JSON Web Token (JWT) saat ini adalah format token akses yang umum. Token JWT berisi tiga blok, dipisahkan oleh titik-titik: header, payload, dan tanda tangan. Dua blok pertama dalam format JSON dan dienkode dalam format base64. Kumpulan bidang dapat terdiri dari nama yang dicadangkan (iss, iat, exp) atau sembarang pasangan nama / nilai. Tanda tangan dapat dibuat menggunakan algoritma enkripsi simetris dan asimetris.



Penerapan



Kami akan menerapkan layanan REST yang menyediakan API berikut:



  • GET / auth / login - memulai proses otentikasi pengguna.
  • POST / auth / token - minta sepasang token akses / refresh baru.
  • GET / api / repositories - Dapatkan daftar repositori Bitbucket dari pengguna saat ini.




Arsitektur Aplikasi Tingkat Tinggi



Perhatikan bahwa karena aplikasi terdiri dari tiga komponen yang saling berinteraksi, selain mengotorisasi permintaan klien ke server, Bitbucket memberi otorisasi permintaan server ke sana. Kami tidak akan mengonfigurasi otorisasi metode berdasarkan peran, agar tidak membuat contoh menjadi lebih rumit. Kami hanya memiliki satu metode API GET / api / repositories yang hanya dapat dipanggil oleh pengguna yang diautentikasi. Server dapat melakukan operasi apa pun di Bitbucket yang diizinkan oleh pendaftaran OAuth klien.





Proses registrasi klien OAuth dijelaskan di artikel sebelumnya .



Untuk implementasi, kita akan menggunakan Spring Boot versi 2.2.2.RELEASE dan Spring Security versi 5.2.1.RELEASE.



Ganti AuthenticationEntryPoint



Dalam aplikasi web standar, saat sumber daya aman diakses dan tidak ada objek Otentikasi dalam konteks keamanan, Keamanan Musim Semi akan mengarahkan pengguna ke halaman otentikasi. Namun, untuk layanan REST, perilaku yang lebih sesuai dalam kasus ini adalah mengembalikan status HTTP 401 (TIDAK SAH).



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




Buat titik akhir login



Kami masih menggunakan OAuth2 dengan jenis otorisasi Kode Otorisasi untuk otentikasi pengguna. Namun, pada langkah sebelumnya, kami mengganti AuthenticationEntryPoint standar dengan implementasi kami sendiri, jadi kami memerlukan cara eksplisit untuk memulai proses autentikasi. Saat kami mengirim permintaan GET ke / auth / login, kami akan mengarahkan pengguna ke halaman otentikasi Bitbucket. Parameter dari metode ini adalah URL panggilan balik, di mana kita akan mengembalikan token akses setelah otentikasi berhasil.



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




Ganti AuthenticationSuccessHandler



AuthenticationSuccessHandler dipanggil setelah otentikasi berhasil. Mari buat token akses di sini, token penyegaran, dan alihkan ke alamat panggilan balik yang dikirim pada awal proses otentikasi. Kami mengembalikan token akses dengan parameter permintaan GET, dan token penyegaran di cookie httpOnly. Kami akan menganalisis apa itu token penyegaran nanti.



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




Ganti AuthenticationFailureHandler



Jika pengguna tidak diautentikasi, kami akan mengarahkannya ke alamat panggilan balik yang diberikan di awal proses otentikasi dengan parameter kesalahan yang berisi teks kesalahan.



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




Buat TokenAuthenticationFilter



Tugas filter ini adalah mengekstrak token akses dari header Otorisasi, jika ada, memvalidasinya, dan menginisialisasi konteks keamanan.



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




Buat titik akhir token penyegaran



Untuk alasan keamanan, masa pakai token akses biasanya dibuat kecil. Kemudian, jika dicuri, penyerang tidak akan dapat menggunakannya tanpa batas waktu. Agar tidak memaksa pengguna untuk masuk ke aplikasi lagi dan lagi, token penyegaran digunakan. Itu dikeluarkan oleh server setelah otentikasi berhasil bersama dengan token akses dan memiliki masa pakai yang lebih lama. Dengan menggunakannya, Anda dapat meminta sepasang token baru. Direkomendasikan untuk menyimpan token Refresh di cookie httpOnly.



Segarkan titik akhir token
@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();
        });
    }
}




Timpa AuthorizationRequestRepository



Spring Security menggunakan objek AuthorizationRequestRepository untuk menyimpan objek OAuth2AuthorizationRequest selama proses autentikasi. Implementasi defaultnya adalah kelas HttpSessionOAuth2AuthorizationRequestRepository, yang menggunakan sesi HTTP sebagai repositori. Karena layanan kami tidak boleh menyimpan negara, implementasi ini tidak cocok untuk kami. Mari menerapkan kelas kita sendiri yang akan menggunakan cookie 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())));
    }
}




Mengonfigurasi Keamanan Musim Semi



Mari gabungkan semua hal di atas dan konfigurasikan Keamanan Musim Semi.



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




Buat titik akhir repositori



Untuk apa otentikasi melalui OAuth2 dan Bitbucket diperlukan - kemampuan untuk menggunakan Bitbucket API untuk mengakses sumber daya Anda. Kami menggunakan API repositori Bitbucket untuk mendapatkan daftar repositori pengguna saat ini.



Titik akhir repositori
@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;
    }
}




Menguji



Untuk pengujian, kita membutuhkan server HTTP kecil untuk mengirim token akses. Pertama, coba panggil titik akhir repositori tanpa token akses dan pastikan kita mendapatkan kesalahan 401 dalam kasus ini. Kemudian kita akan mengautentikasi. Untuk melakukan ini, mulai server dan buka browser di http: // localhost: 8080 / auth / login . Setelah kami memasukkan nama pengguna / kata sandi, klien akan menerima token dan memanggil titik akhir repositori lagi. Kemudian token baru akan diminta dan titik akhir repositori akan dipanggil lagi dengan token baru.



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




Keluaran konsol klien.



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)


Sumber



Kode sumber lengkap untuk aplikasi yang diulas ada di Github .



Tautan





PS

Layanan REST yang kami buat dijalankan melalui protokol HTTP agar tidak mempersulit contoh. Tetapi karena token kami tidak dienkripsi dengan cara apa pun, disarankan untuk beralih ke saluran aman (HTTPS).



All Articles