Проблема: У нас есть RESTful API на базе Spring MVC, который содержит конфиденциальную информацию. API должен быть защищен, однако отправка учетных данных пользователя (комбинация пользователь/пасс) при каждом запросе нежелательна. Согласно рекомендациям REST (и внутренним бизнес-требованиям), сервер должен оставаться без статических данных. API будет потребляться другим сервером в стиле mashup.
Требования:
Клиент делает запрос на .../authenticate
(незащищенный URL) с учетными данными; сервер возвращает защищенный токен, который содержит достаточно информации для того, чтобы сервер мог подтверждать будущие запросы и оставаться без статического состояния. Скорее всего, он будет состоять из той же информации, что и Spring Security Remember-Me Token.
Клиент делает последующие запросы к различным (защищенным) URL, добавляя полученный ранее токен в качестве параметра запроса (или, что менее желательно, заголовка HTTP-запроса).
Нельзя ожидать, что клиент будет хранить файлы cookie.
Поскольку мы уже используем Spring, решение должно использовать Spring Security.
Мы бились головой об стену, пытаясь заставить это работать, поэтому надеемся, что кто-то уже решил эту проблему.
Учитывая описанный выше сценарий, как вы могли бы решить эту конкретную задачу?
Нам удалось заставить его работать именно так, как описано в OP, и мы надеемся, что кто-то еще сможет воспользоваться этим решением. Вот что мы сделали:
Настройте контекст безопасности следующим образом:
<security:http realm="Protected API" use-expressions="true" auto-config="false" create-session="stateless" entry-point-ref="CustomAuthenticationEntryPoint">
<security:custom-filter ref="authenticationTokenProcessingFilter" position="FORM_LOGIN_FILTER" />
<security:intercept-url pattern="/authenticate" access="permitAll"/>
<security:intercept-url pattern="/**" access="isAuthenticated()" />
</security:http>
<bean id="CustomAuthenticationEntryPoint"
class="com.demo.api.support.spring.CustomAuthenticationEntryPoint" />
<bean id="authenticationTokenProcessingFilter"
class="com.demo.api.support.spring.AuthenticationTokenProcessingFilter" >
<constructor-arg ref="authenticationManager" />
</bean>
Как вы можете видеть, мы создали пользовательскую AuthenticationEntryPoint
, которая в основном просто возвращает 401 Unauthorized
, если запрос не был аутентифицирован в цепочке фильтров нашим AuthenticationTokenProcessingFilter
.
CustomAuthenticationEntryPoint:
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.sendError( HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized: Authentication token was either missing or invalid." );
}
}
AuthenticationTokenProcessingFilter:
public class AuthenticationTokenProcessingFilter extends GenericFilterBean {
@Autowired UserService userService;
@Autowired TokenUtils tokenUtils;
AuthenticationManager authManager;
public AuthenticationTokenProcessingFilter(AuthenticationManager authManager) {
this.authManager = authManager;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
@SuppressWarnings("unchecked")
Map<String, String[]> parms = request.getParameterMap();
if(parms.containsKey("token")) {
String token = parms.get("token")[0]; // grab the first "token" parameter
// validate the token
if (tokenUtils.validate(token)) {
// determine the user based on the (already validated) token
UserDetails userDetails = tokenUtils.getUserFromToken(token);
// build an Authentication object with the user's info
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails((HttpServletRequest) request));
// set the authentication into the SecurityContext
SecurityContextHolder.getContext().setAuthentication(authManager.authenticate(authentication));
}
}
// continue thru the filter chain
chain.doFilter(request, response);
}
}
Очевидно, что TokenUtils
содержит некоторый секретный (и очень специфичный для конкретного случая) код и не может быть легко разделен. Вот его интерфейс:
public interface TokenUtils {
String getToken(UserDetails userDetails);
String getToken(UserDetails userDetails, Long expiration);
boolean validate(String token);
UserDetails getUserFromToken(String token);
}
Это должно помочь вам начать. Счастливого кодирования. :)
Вы можете рассмотреть вариант Digest Access Authentication. По сути, протокол выглядит следующим образом:
Вся эта коммуникация осуществляется через заголовки, что, как отмечает jmort253, обычно более безопасно, чем передача конфиденциального материала в параметрах url.
Digest Access Authentication поддерживается Spring Security. Обратите внимание, что, хотя в документации говорится, что вы должны иметь доступ к паролю вашего клиента, вы можете успешно пройти аутентификацию, если у вас есть хэш HA1 для вашего клиента.
Что касается токенов, несущих информацию, то JSON Web Tokens (http://jwt.io) - это блестящая технология. Основная концепция заключается во внедрении информационных элементов (утверждений) в токен, а затем подписании всего токена, чтобы проверяющая сторона могла убедиться, что утверждения действительно заслуживают доверия.
Я использую эту реализацию на Java: https://bitbucket.org/b_c/jose4j/wiki/Home
Существует также модуль Spring (spring-security-jwt), но я не изучал, что он поддерживает.
Почему Дон'т вы начинаете с помощью OAuth с JSON WebTokens
http://projects.spring.io/spring-security-oauth/
OAuth2-это открытый стандартный протокол авторизации/рамки. Согласно официальной что OAuth2 спецификация:
Вы можете найти более подробную информацию здесь