Аутентификация RESTful через Spring

Проблема:
У нас есть API-интерфейс RESTful, основанный на Spring MVC, который содержит конфиденциальную информацию. API должен быть защищен, однако отправка пользовательских учетных данных (комманда user/pass) с каждым запросом нежелательна. В соответствии с рекомендациями REST (и внутренними бизнес-требованиями) сервер должен оставаться без гражданства. API будет потребляться другим сервером в стиле mashup-стиля.

Требования:

  • Клиент отправляет запрос .../authenticate (незащищенный URL) с учетными данными; сервер возвращает защищенный токен, который содержит достаточную информацию для сервера для проверки будущих запросов и остается безстоящим. Вероятно, это будет состоять из той же информации, что и Spring Безопасность Знак "Запомнить-меня" .

  • Клиент выполняет последующие запросы на различные (защищенные) URL-адреса, добавляя ранее полученный токен в качестве параметра запроса (или, желательно, HTTP-заголовка запроса).

  • Клиент не может хранить файлы cookie.

  • Поскольку мы используем Spring уже, решение должно использовать Spring Безопасность.

Мы стучали головой о стену, пытаясь сделать эту работу, поэтому, надеюсь, кто-то там уже решил эту проблему.

Учитывая описанный выше сценарий, как вы можете решить эту конкретную проблему?

Ответ 1

Нам удалось заставить это работать точно так, как описано в 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 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);
}

Это должно привести вас к хорошему началу. Счастливое кодирование.:)

Ответ 2

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

  • Запрос выполняется клиентом
  • Сервер отвечает уникальной строкой nonce
  • Клиент предоставляет имя пользователя и пароль (и некоторые другие значения), md5 хэшируется с помощью nonce; этот хэш известен как HA1
  • Затем сервер может проверять идентификацию клиента и обслуживать запрошенные материалы.
  • Связь с nonce может продолжаться до тех пор, пока сервер не поставляет новый nonce (для устранения повторных атак используется счетчик)

Все это сообщение осуществляется через заголовки, которые, как указывает jmort253, обычно более безопасны, чем передача конфиденциального материала в параметры URL.

Аудит аутентификации Digest поддерживается Spring безопасностью. Обратите внимание, что, хотя документы говорят, что у вас должен быть доступ к вашему текстовому паролю вашего клиента, вы можете успешно пройти аутентификацию, если у вас есть хеш HA1 для вашего клиента.

Ответ 3

В отношении токенов, несущих информацию, JSON Web Tokens (http://jwt.io) - блестящая технология. Основная идея заключается в том, чтобы вставлять информационные элементы (заявки) в токен и затем подписывать весь токен, чтобы проверяющий конец мог убедиться, что претензии действительно заслуживают доверия.

Я использую эту реализацию Java: https://bitbucket.org/b_c/jose4j/wiki/Home

Существует также модуль Spring (spring -security-jwt), но я не искал того, что он поддерживает.

Ответ 4

Почему бы вам не начать использовать OAuth с JSON WebTokens

http://projects.spring.io/spring-security-oauth/

OAuth2 - стандартизованный протокол/структура авторизации. Согласно официальному OAuth2 Спецификация:

Вы можете найти дополнительную информацию здесь