Содержание
-
ссылка: #overview[ 1. Обзор]
-
ссылка: # цель[ 2. Цель]
-
ссылка: #client[ 3. Клиент]
-
ссылка: #testing[ 4. Тестирование службы аутентификации]
-
ссылка: # заключение[ 5. заключение]
1. Обзор
Эта статья посвящена тому, как проходить аутентификацию на основе безопасного REST API , который предоставляет RESTful User Account и службу аутентификации.
2. Цель
Во-первых, давайте рассмотрим участников - типичное приложение с поддержкой Spring Security должно проходить проверку подлинности на основе чего-либо - что-то может быть:
-
база данных
-
LDAP
-
ОТДЫХ
База данных является наиболее распространенным сценарием; однако служба RESTful UAA (учетная запись пользователя и аутентификация) может работать так же хорошо
Для целей данной статьи служба REST UAA предоставит одну операцию GET для /authentication , которая вернет основную информацию , необходимую Spring Security для выполнения процесса полной аутентификации.
3. Клиент
Как правило, простое приложение с поддержкой Spring Security будет использовать простой пользовательский сервис в качестве источника аутентификации:
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="customUserDetailsService"/>
</authentication-manager>
Это позволит реализовать org.springframework.security.core.userdetails.UserDetailsService и будет возвращать принципала на основе предоставленного username :
@Component
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) {
...
}
}
Когда клиент проходит проверку подлинности с помощью службы RESTful UAA, работы только с username больше не будет достаточно - клиенту теперь нужны полные учетные данные - и username , и password - при отправке запроса на проверку подлинности службе. Это имеет смысл, так как сама служба защищена, поэтому для правильной обработки запрос должен содержать учетные данные аутентификации.
С точки зрения Spring Security это не может быть сделано изнутри loadUserByUsername , поскольку password больше не доступен на этом этапе - нам нужно быстрее взять под контроль процесс аутентификации .
Мы можем сделать это, предоставив провайдера полной аутентификации Spring Security:
<authentication-manager alias="authenticationManager">
<authentication-provider ref="restAuthenticationProvider"/>
</authentication-manager>
Переопределение всего провайдера аутентификации дает нам гораздо больше свободы для выполнения пользовательского извлечения Принципала из Сервиса, но это действительно сопряжено со значительной сложностью. Стандартный поставщик аутентификации - DaoAuthenticationProvider - имеет большую часть того, что нам нужно, поэтому хорошим подходом было бы просто расширить его и изменить только то, что необходимо.
К сожалению, это невозможно, так как retrieveUser - метод, который мы хотели бы расширить - это final . Это несколько не интуитивно понятно (здесь есть a JIRA, где обсуждается проблема ) - похоже, что задуманное здесь - просто предоставить альтернативную реализацию, которая не идеальна, но не является серьезной проблемой - наш RestAuthenticationProvider копирует и вставляет большую часть реализации DaoAuthenticationProvider и переписывает то, что ему нужно - получение принципала из службы:
@Override
protected UserDetails retrieveUser(String name, UsernamePasswordAuthenticationToken auth){
String password = auth.getCredentials().toString();
UserDetails loadedUser = null;
try {
ResponseEntity<Principal> authenticationResponse =
authenticationApi.authenticate(name, password);
if (authenticationResponse.getStatusCode().value() == 401) {
return new User("wrongUsername", "wrongPass",
Lists.<GrantedAuthority> newArrayList());
}
Principal principalFromRest = authenticationResponse.getBody();
Set<String> privilegesFromRest = Sets.newHashSet();
//fill in the privilegesFromRest from the Principal
String[]authoritiesAsArray =
privilegesFromRest.toArray(new String[privilegesFromRest.size()]);
List<GrantedAuthority> authorities =
AuthorityUtils.createAuthorityList(authoritiesAsArray);
loadedUser = new User(name, password, true, authorities);
} catch (Exception ex) {
throw new AuthenticationServiceException(repositoryProblem.getMessage(), ex);
}
return loadedUser;
}
Давайте начнем с самого начала - HTTP-связь со службой REST - это обрабатывается authenticationApi - простым API, обеспечивающим операцию authenticate для фактической службы. Сама операция может быть реализована с помощью любой библиотеки, поддерживающей HTTP - в этом случае реализация использует RestTemplate__:
public ResponseEntity<Principal> authenticate(String username, String pass) {
HttpEntity<Principal> entity = new HttpEntity<Principal>(createHeaders(username, pass))
return restTemplate.exchange(authenticationUri, HttpMethod.GET, entity, Principal.class);
}
HttpHeaders createHeaders(String email, String password) {
HttpHeaders acceptHeaders = new HttpHeaders() {
{
set(com.google.common.net.HttpHeaders.ACCEPT,
MediaType.APPLICATION__JSON.toString());
}
};
String authorization = username + ":" + password;
String basic = new String(Base64.encodeBase64
(authorization.getBytes(Charset.forName("US-ASCII"))));
acceptHeaders.set("Authorization", "Basic " + basic);
return acceptHeaders;
}
FactoryBean может использоваться для связи:/how-to-use-resttemplate-with-basic-authentication-in-spring # resttemplate[установить шаблон RestTemplate в контексте].
-
Далее ** , если запрос аутентификации привел к HTTP 401 Unauthorized , скорее всего, из-за неправильных учетных данных от клиента, участник с неправильными учетными данными возвращается, чтобы процесс аутентификации Spring Security мог отклонить их:
return new User("wrongUsername", "wrongPass", Lists.<GrantedAuthority> newArrayList());
Наконец, Spring Security Principal нуждается в некоторых полномочиях - привилегиях, которые этот конкретный участник будет иметь и использовать локально после процесса аутентификации. Операция/ authenticate получила полный принципал, включая привилегии, поэтому их необходимо извлечь из результата запроса и преобразовать в объекты GrantedAuthority , как того требует Spring Security.
Детали того, как хранятся эти привилегии, здесь не имеют значения - они могут храниться как простые строки или как сложная структура Role-Privilege - но независимо от деталей нам нужно использовать только их имена для создания объектов GrantedAuthoritiy . После создания окончательного участника Spring Security он возвращается к стандартному процессу аутентификации:
List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(authoritiesAsArray);
loadedUser = new User(name, password, true, authorities);
4. Тестирование службы аутентификации
Написание интеграционного теста, который использует службу REST аутентификации на happy-path, достаточно просто:
@Test
public void whenAuthenticating__then200IsReceived() {
//When
ResponseEntity<Principal> response
= authenticationRestTemplate.authenticate("admin", "adminPass");
//Then
assertThat(response.getStatusCode().value(), is(200));
}
После этого простого теста могут быть также реализованы более сложные интеграционные тесты - однако это выходит за рамки этого поста.
5. Заключение
В этой статье объясняется, как выполнять аутентификацию с помощью REST API, а не на локальной системе, такой как база данных.
Для полной реализации защищенного сервиса RESTful, который можно использовать в качестве поставщика аутентификации, обратитесь к проекту GitHub .