1. Обзор
Безопасность является первоклассным гражданином в экосистеме Spring. Поэтому неудивительно, что OAuth2 может работать с Spring Web MVC практически без настройки.
Однако собственное решение Spring — не единственный способ реализовать уровень представления. Jersey , реализация, совместимая с JAX-RS, также может работать в тандеме со Spring OAuth2.
В этом руководстве мы узнаем, как защитить приложение на Джерси с помощью Spring Social Login, реализованного с использованием стандарта OAuth2.
2. Зависимости Maven
Давайте добавим артефакт spring-boot-starter-jersey , чтобы интегрировать Jersey в приложение Spring Boot:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
Для настройки безопасности OAuth2 нам понадобятся spring-boot-starter-security и spring-security-oauth2-client :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
</dependency>
Мы будем управлять всеми этими зависимостями, используя Spring Boot Starter Parent версии 2 .
3. Уровень представления Джерси
Нам понадобится класс ресурсов с парой конечных точек, чтобы использовать Джерси в качестве уровня представления.
3.1. Класс ресурсов
Вот класс, который содержит определения конечных точек:
@Path("/")
public class JerseyResource {
// endpoint definitions
}
Сам класс очень простой — в нем есть только аннотация @Path
. Значение этой аннотации определяет базовый путь для всех конечных точек в теле класса.
Возможно, стоит упомянуть, что этот класс ресурсов не содержит стереотипной аннотации для сканирования компонентов. На самом деле, это даже не обязательно должен быть bean-компонент Spring. Причина в том, что мы не полагаемся на Spring для обработки сопоставления запросов.
3.2. Страница авторизации
Вот метод, который обрабатывает запросы на вход:
@GET
@Path("login")
@Produces(MediaType.TEXT_HTML)
public String login() {
return "Log in with <a href=\"/oauth2/authorization/github\">GitHub</a>";
}
Этот метод возвращает строку для запросов GET, нацеленных на конечную точку /login
. Тип содержимого text/html
указывает браузеру пользователя отображать ответ со ссылкой, по которой можно щелкнуть.
Мы будем использовать GitHub в качестве провайдера OAuth2, отсюда и ссылка /oauth2/authorization/github
. Эта ссылка вызовет перенаправление на страницу авторизации GitHub.
3.3. Домашняя страница
Давайте определим другой метод для обработки запросов к корневому пути:
@GET
@Produces(MediaType.TEXT_PLAIN)
public String home(@Context SecurityContext securityContext) {
OAuth2AuthenticationToken authenticationToken = (OAuth2AuthenticationToken) securityContext.getUserPrincipal();
OAuth2AuthenticatedPrincipal authenticatedPrincipal = authenticationToken.getPrincipal();
String userName = authenticatedPrincipal.getAttribute("login");
return "Hello " + userName;
}
Этот метод возвращает домашнюю страницу, которая представляет собой строку, содержащую имя пользователя, вошедшего в систему. Обратите внимание, в данном случае мы извлекли имя пользователя из атрибута входа .
Однако другой поставщик OAuth2 может использовать другой атрибут для имени пользователя.
Очевидно, что описанный выше метод работает только для аутентифицированных запросов. Если запрос не прошел проверку подлинности, он будет перенаправлен на конечную точку входа
. Мы увидим, как настроить это перенаправление в разделе 4.
3.4. Регистрация Джерси в контейнере Spring
Давайте зарегистрируем класс ресурсов в контейнере сервлетов, чтобы включить службы Джерси. К счастью, это довольно просто:
@Component
public class RestConfig extends ResourceConfig {
public RestConfig() {
register(JerseyResource.class);
}
}
Зарегистрировав JerseyResource
в подклассе ResourceConfig
, мы сообщили контейнеру сервлетов обо всех конечных точках в этом классе ресурсов.
Последний шаг — зарегистрировать подкласс ResourceConfig , в данном случае
RestConfig
, в контейнере Spring. Мы реализовали эту регистрацию с помощью аннотации @Component
.
4. Настройка безопасности Spring
Мы можем настроить безопасность для Джерси так же, как для обычного приложения Spring:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login")
.permitAll()
.anyRequest()
.authenticated()
.and()
.oauth2Login()
.loginPage("/login");
}
}
Самый важный метод в данной цепочке — oauth2Login
. Этот метод настраивает поддержку проверки подлинности с помощью поставщика OAuth 2.0. В этом руководстве поставщиком является GitHub.
Еще одна заметная конфигурация — страница входа. Предоставляя строку «/login»
методу loginPage
, мы указываем Spring перенаправлять неаутентифицированные запросы на конечную точку /login
.
Обратите внимание, что конфигурация безопасности по умолчанию также предоставляет автоматически сгенерированную страницу в /login
. Следовательно, даже если бы мы не настроили страницу входа, неаутентифицированный запрос все равно был бы перенаправлен на эту конечную точку.
Разница между конфигурацией по умолчанию и явной настройкой заключается в том, что в случае по умолчанию приложение возвращает сгенерированную страницу, а не нашу пользовательскую строку.
5. Конфигурация приложения
Чтобы иметь приложение, защищенное OAuth2, нам нужно зарегистрировать клиент у поставщика OAuth2. После этого добавьте учетные данные клиента в приложение.
5.1. Регистрация клиента OAuth2
Давайте начнем процесс регистрации, зарегистрировав приложение GitHub . После перехода на страницу разработчика GitHub нажмите кнопку « Новое приложение OAuth
» , чтобы открыть форму « Зарегистрировать новое приложение OAuth»
.
Затем заполните отображаемую форму соответствующими значениями. В качестве имени приложения введите любую строку, которая сделает приложение узнаваемым. URL-адрес домашней страницы может быть http://localhost:8083,
а URL-адрес обратного вызова авторизации — http://localhost:8083/login/oauth2/code/github
.
URL-адрес обратного вызова — это путь, на который перенаправляется браузер после аутентификации пользователя в GitHub и предоставления доступа к приложению.
Вот как может выглядеть регистрационная форма:
Теперь нажмите кнопку Зарегистрировать приложение
. Затем браузер должен перенаправить на домашнюю страницу приложения GitHub, которая показывает идентификатор клиента и секрет клиента.
5.2. Настройка приложения Spring Boot
Давайте добавим файл свойств с именем jersey-application.properties
в путь к классам:
server.port=8083
spring.security.oauth2.client.registration.github.client-id=<your-client-id>
spring.security.oauth2.client.registration.github.client-secret=<your-client-secret>
Не забудьте заменить заполнители <your-client-id>
и <your-client-secret>
значениями из нашего собственного приложения GitHub.
Наконец, добавьте этот файл в качестве источника свойств в приложение Spring Boot:
@SpringBootApplication
@PropertySource("classpath:jersey-application.properties")
public class JerseyApplication {
public static void main(String[] args) {
SpringApplication.run(JerseyApplication.class, args);
}
}
6. Аутентификация в действии
Давайте посмотрим, как мы можем войти в наше приложение после регистрации на GitHub.
6.1. Доступ к приложению
Давайте запустим приложение, затем зайдем на домашнюю страницу по адресу localhost:8083
. Поскольку запрос не аутентифицирован, мы будем перенаправлены на страницу входа :
Теперь, когда мы нажмем на ссылку GitHub, браузер перенаправит на страницу авторизации GitHub:
Глядя на URL-адрес, мы видим, что перенаправленный запрос содержал множество параметров запроса, таких как response_type
, client_id
и scope
:
https://github.com/login/oauth/authorize?response_type=code&client_id=c30a16c45a9640771af5&scope=read:user&state=dpTme3pB87wA7AZ--XfVRWSkuHD3WIc9Pvn17yeqw38%3D&redirect_uri=http://localhost:8083/login/oauth2/code/github
Значением response_type
является code
, что означает, что тип гранта OAuth2 — это код авторизации. Между тем, параметр client_id
помогает идентифицировать наше приложение. Чтобы узнать о значениях всех параметров, перейдите на страницу разработчика GitHub .
Когда появится страница авторизации, нам нужно авторизовать приложение для продолжения. После успешной авторизации браузер перенаправит на предопределенную конечную точку в нашем приложении вместе с несколькими параметрами запроса:
http://localhost:8083/login/oauth2/code/github?code=561d99681feeb5d2edd7&state=dpTme3pB87wA7AZ--XfVRWSkuHD3WIc9Pvn17yeqw38%3D
Затем приложение за кулисами обменяет код авторизации на токен доступа. После этого он использует этот токен для получения информации о вошедшем в систему пользователе.
После возврата запроса к localhost:8083/login/oauth2/code/github
браузер возвращается на домашнюю страницу. На этот раз мы должны увидеть приветственное сообщение с нашим собственным именем пользователя :
6.2. Как получить имя пользователя?
Понятно, что имя пользователя в приветственном сообщении — это наше имя пользователя GitHub. В этот момент может возникнуть вопрос: как мы можем получить имя пользователя и другую информацию от аутентифицированного пользователя?
В нашем примере мы извлекли имя пользователя из атрибута входа .
Однако это не одинаково для всех поставщиков OAuth2. Другими словами, провайдер может предоставлять данные в определенных атрибутах по своему усмотрению. Поэтому можно сказать, что стандартов в этом плане просто нет.
В случае с GitHub мы можем найти, какие атрибуты нам нужны, в справочной документации . Точно так же другие поставщики OAuth2 предоставляют свои собственные ссылки.
Другое решение состоит в том, что мы можем запустить приложение в режиме отладки и установить точку останова после создания объекта OAuth2AuthenticatedPrincipal .
При просмотре всех атрибутов этого объекта мы получим информацию о пользователе.
7. Тестирование
Давайте напишем несколько тестов, чтобы проверить поведение приложения.
7.1. Настройка среды
Вот класс, который будет содержать наши методы тестирования:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
@TestPropertySource(properties = "spring.security.oauth2.client.registration.github.client-id:test-id")
public class JerseyResourceUnitTest {
@Autowired
private TestRestTemplate restTemplate;
@LocalServerPort
private int port;
private String basePath;
@Before
public void setup() {
basePath = "http://localhost:" + port + "/";
}
// test methods
}
Вместо использования реального идентификатора клиента GitHub мы определили тестовый идентификатор для клиента OAuth2. Затем этот идентификатор устанавливается в свойство spring.security.oauth2.client.registration.github.client-id
.
Все аннотации в этом тестовом классе являются общими для тестирования Spring Boot, поэтому мы не будем рассматривать их в этом руководстве. Если какая-либо из этих аннотаций неясна, перейдите к разделу Тестирование в Spring Boot , Интеграционное тестирование в Spring или Изучение Spring Boot TestRestTemplate .
7.2. Домашняя страница
Мы докажем, что когда неаутентифицированный пользователь пытается получить доступ к домашней странице, он будет перенаправлен на страницу входа для аутентификации:
@Test
public void whenUserIsUnauthenticated_thenTheyAreRedirectedToLoginPage() {
ResponseEntity<Object> response = restTemplate.getForEntity(basePath, Object.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FOUND);
assertThat(response.getBody()).isNull();
URI redirectLocation = response.getHeaders().getLocation();
assertThat(redirectLocation).isNotNull();
assertThat(redirectLocation.toString()).isEqualTo(basePath + "login");
}
7.3. Страница авторизации
Давайте проверим, что доступ к странице входа приведет к возврату пути авторизации:
@Test
public void whenUserAttemptsToLogin_thenAuthorizationPathIsReturned() {
ResponseEntity response = restTemplate.getForEntity(basePath + "login", String.class);
assertThat(response.getHeaders().getContentType()).isEqualTo(TEXT_HTML);
assertThat(response.getBody()).isEqualTo("Log in with <a href="\"/oauth2/authorization/github\"">GitHub</a>");
}
7.4. Конечная точка авторизации
Наконец, при отправке запроса на конечную точку авторизации браузер перенаправит на страницу авторизации провайдера OAuth2 с соответствующими параметрами:
@Test
public void whenUserAccessesAuthorizationEndpoint_thenTheyAresRedirectedToProvider() {
ResponseEntity response = restTemplate.getForEntity(basePath + "oauth2/authorization/github", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FOUND);
assertThat(response.getBody()).isNull();
URI redirectLocation = response.getHeaders().getLocation();
assertThat(redirectLocation).isNotNull();
assertThat(redirectLocation.getHost()).isEqualTo("github.com");
assertThat(redirectLocation.getPath()).isEqualTo("/login/oauth/authorize");
String redirectionQuery = redirectLocation.getQuery();
assertThat(redirectionQuery.contains("response_type=code"));
assertThat(redirectionQuery.contains("client_id=test-id"));
assertThat(redirectionQuery.contains("scope=read:user"));
}
8. Заключение
В этом руководстве мы настроили Spring Social Login с помощью приложения Jersey. В учебник также включены шаги по регистрации приложения у поставщика GitHub OAuth2.
Полный исходный код можно найти на GitHub .