Русский
Русский
English
Статистика
Реклама

Spring Security пример REST-сервиса с авторизацией по протоколу OAuth2 через BitBucket и JWT

В предыдущей статье мы разработали простое защищенное веб приложение, в котором для аутентификации пользователей использовался протокол OAuth2 с Bitbucket в качестве сервера авторизации. Кому-то такая связка может показаться странной, но представьте, что мы разрабатываем CI (Continuous Integration) сервер и хотели бы иметь доступ к ресурсам пользователя в системе контроля версий. Например, по такому же принципу работает довольно известная CI платформа drone.io.

В предыдущем примере для авторизации запросов к серверу использовалась HTTP-сессия (и куки). Однако для реализации REST-сервиса данный способ авторизации не подходит, поскольку одним из требований REST архитектуры является отсутсвие состояния. В данной статье мы реализуем REST-сервис, авторизация запросов к которому будет осуществляться с помощью токена доступа (access token).

Немного теории


Аутентификация это процесс проверки учётных данных пользователя (логин/пароль). Проверка подлинности пользователя осуществляется путём сравнения введённого им логина/пароля с сохраненными данными.
Авторизация это проверка прав пользователя на доступ к определенным ресурсам. Авторизация выполняется непосредственно при обращении пользователя к ресурсу.

Рассмотрим порядок работы двух вышеупомянутых способов авторизации запросов.
Авторизация запросов с помощью HTTP-сессии:
  • Пользователь проходит аутентификацию любым из способов.
  • На сервере создается HTTP-сессия и куки JSESSIONID, хранящий идентификатор сессии.
  • Куки JSESSIONID передается на клиент и сохраняется в браузере.
  • С каждым последующим запросом на сервер отправляется куки JSESSIONID.
  • Сервер находит соответствующую HTTP-сессию с информацией о текущем пользователе и определяет имеет ли пользователь права на выполнение данного вызова.
  • Для выполнения выхода из приложения необходимо удалить с сервера HTTP-сессию.


Авторизация запросов с помощью токена доступа:
  • Пользователь проходит аутентификацию любым из способов.
  • Сервер создает токен доступа, подписанный секретным ключом, а затем отправляет его клиенту. Токен содержит идентификатор пользователя и его роли.
  • Токен сохраняется на клиенте и передается на сервер с каждым последующим запросом. Как правило для передачи токена используетя HTTP заголовок Authorization.
  • Сервер сверяет подпись токена, извлекает из него идентификатор пользователя, его роли и определяет имеет ли пользователь права на выполнение данного вызова.
  • Для выполнения выхода из приложения достаточно просто удалить токен на клиенте без необходимости взаимодействия с сервером.


Распространенным форматом токена доступа в настоящее время является JSON Web Token (JWT). Токен в формате JWT содержит три блока, разделенных точками: заголовок (header), набор полей (payload) и сигнатуру (подпись). Первые два блока представлены в JSON-формате и закодированы в формат base64. Набор полей может состоять как из зарезервированных имен (iss, iat, exp), так и произвольных пар имя/значение. Подпись может генерироваться как при помощи симметричных, так и асимметричных алгоритмов шифрования.

Реализация


Мы реализуем REST-сервис, предоставляющий следующее API:
  • GET /auth/login запустить процесс аутентификации пользователя.
  • POST /auth/token запросить новую пару access/refresh токенов.
  • GET /api/repositories получить список Bitbucket репозиториев текущего пользователя.


Высокоуровневая архитектура приложения.

Заметим, что поскольку приложение состоит из трех взаимодействующих компонентов, помимо того, что мы выполняем авторизацию запросов клиента к серверу, Bitbucket авторизует запросы сервера к нему. Мы не будем настраивать авторизацию методов по ролям, чтобы не делать пример сложнее. У нас есть только один API метод GET /api/repositories, вызывать который могут только аутентифицированные пользователи. Сервер может выполнять на Bitbucket любые операции, разрешенные при регистрации OAuth клиента.

Процесс регистрации OAuth клиента описан в предыдущей статье.

Для реализации мы будем использовать Spring Boot версии 2.2.2.RELEASE и Spring Security версии 5.2.1.RELEASE.

Переопределим AuthenticationEntryPoint.


В стандартном веб-приложении, когда осуществляется обращение к защищенному ресурсу и в секьюрити контексте отсутствует объект Authentication, Spring Security перенаправляет пользователя на страницу аутентификации. Однако для REST-сервиса более подходящим поведением в этом случае было бы возвращать HTTP статус 401 (UNAUTHORIZED).

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


Создадим login endpoint.


Для аутентификации пользователя мы по-прежнему используем OAuth2 с типом авторизации Authorization Code. Однако на предыдущем шаге мы заменили стандартный AuthenticationEntryPoint своей реализацией, поэтому нам нужен явный способ запустить процесс аутентификации. При отправке GET запроса по адресу /auth/login мы перенаправим пользователя на страницу аутентификации Bitbucket. Параметром этого метода будет URL обратного вызова, по которому мы возвратим токен доступа после успешной аутентификации.

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


Переопределим AuthenticationSuccessHandler.


AuthenticationSuccessHandler вызывается после успешной аутентификации. Сгенерируем тут токен доступа, refresh токен и выполним редирект по адресу обратного вызова, который был передан в начале процесса аутентификации. Токен доступа вернем параметром GET запроса, а refresh токен в httpOnly куке. Что такое refresh токен разберем позже.

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


Переопределим AuthenticationFailureHandler.


В случае, если пользователь не прошел аутентификацию, мы перенаправим его по адресу обратного вызова, который был передан в начале процесса аутентификации с параметром error, содержащим текст ошибки.

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


Создадим TokenAuthenticationFilter.


Задача этого фильтра извлечь токен доступа из заголовка Authorization в случае его наличия, провалидировать его и инициализировать секьюрити контекст.

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


Создадим refresh token endpoint.


В целях безопасности время жизни токена доступа обычно делают небольшим. Тогда в случае его кражи злоумышленник не сможет пользоваться им бесконечно долго. Чтобы не заставлять пользователя выполнять вход в приложение снова и снова используется refresh токен. Он выдается сервером после успешной аутентификации вместе с токеном доступа и имеет большее время жизни. Используя его можно запросить новую пару токенов. Refresh токен рекомендуют хранить в httpOnly куке.

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


Переопределим AuthorizationRequestRepository.


Spring Security использует объект AuthorizationRequestRepository для хранения объектов OAuth2AuthorizationRequest на время процесса аутентификации. Реализацией по умолчанию является класс HttpSessionOAuth2AuthorizationRequestRepository, который использует HTTP-сессию в качестве хранилища. Т.к. наш сервис не должен хранить состояние, эта реализация нам не подходит. Реализуем свой класс, который будет использовать HTTP cookies.

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


Настроим Spring Security.


Соберем все проделанное выше вместе и настроим Spring Security.

WebSecurityConfig
@Configuration@EnableWebSecuritypublic 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);    }}


Создадим repositories endpoint.


То ради чего и нужна была аутентификация через OAuth2 и Bitbucket возможность использовать Bitbucket API для доступа к своим ресурсам. Используем Bitbucket repositories API для получения списка репозиториев текущего пользователя.

Repositories endpoint
@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;    }}


Тестирование


Для тестирования нам понадобится небольшой HTTP-сервер, на который будет отправлен токен доступа. Сначала попробуем вызвать repositories endpoint без токена доступа и убедимся, что в этом случае получим ошибку 401. Затем пройдем аутентификацию. Для этого запустим сервер и перейдем в браузере по адресу http://localhost:8080/auth/login. После того как мы введем логин/пароль, клиент получит токен и вызовет repositories endpoint еще раз. Затем будет запрошен новый токен и снова вызван repositories endpoint с новым токеном.

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


Консольный вывод клиента.
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)

Исходный код


Полный исходный код рассмотренного приложения находится на Github.

Ссылки



P.S.
Созданный нами REST-сервис работает по протоколу HTTP, чтобы не усложнять пример. Но поскольку токены у нас никак не шифруются, рекомендуется перейти на защищенный канал (HTTPS).
Источник: habr.com
К списку статей
Опубликовано: 17.11.2020 00:09:32
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Java

Spring security

Oauth 2.0

Аутентификация

Авторизация

Jwt

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru