RESTful Authentication via Spring

Problem:
Mamy RESTful API oparte na MVC Spring, które zawiera poufne informacje. API powinno być zabezpieczone, jednak wysyłanie poświadczeń użytkownika (combo user / pass) przy każdym żądaniu nie jest pożądane. Zgodnie z wytycznymi REST (i wewnętrznymi wymaganiami biznesowymi) serwer musi pozostać bezpaństwowy. API zostanie zużyte przez inny serwer w podejściu mashup.

Wymagania:

  • Klient składa wniosek do .../authenticate (niezabezpieczony Adres URL) z poświadczeniami; serwer zwraca Bezpieczny token, który zawiera wystarczająco dużo informacji, aby serwer mógł potwierdzić przyszłe żądania i pozostać bezpaństwowcem. Prawdopodobnie składałoby się to z tych samych informacji, co Token Remember-Me firmy Spring Security .

  • Klient wysyła kolejne żądania do różnych (chronionych) adresów URL, dołączając wcześniej uzyskany token jako parametr zapytania (lub, mniej pożądane, nagłówek żądania HTTP).

  • Nie można oczekiwać, że Klient przechowuj pliki cookie.

  • Ponieważ używamy już sprężyny, rozwiązanie powinno wykorzystywać zabezpieczenie sprężyny.

Walaliśmy głowami o ścianę, próbując to naprawić, więc miejmy nadzieję, że ktoś już rozwiązał ten problem.

Biorąc pod uwagę powyższy scenariusz, jak możesz rozwiązać tę szczególną potrzebę?

Author: Chris Cashwell, 2012-05-31

4 answers

Udało nam się to uruchomić dokładnie tak, jak opisano w OP, i mam nadzieję, że ktoś inny może skorzystać z rozwiązania. Oto co zrobiliśmy:

Skonfiguruj kontekst bezpieczeństwa w następujący sposób:

<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>

Jak widzisz, stworzyliśmy niestandardowe AuthenticationEntryPoint, które w zasadzie zwraca 401 Unauthorized, jeśli żądanie nie zostało uwierzytelnione w łańcuchu filtrów przez naszego 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);
    }
}

Oczywiście, TokenUtils zawiera pewien tajny (i bardzo specyficzny dla przypadku) kod i nie może być łatwo udostępniony. Oto jego interfejs:

public interface TokenUtils {
    String getToken(UserDetails userDetails);
    String getToken(UserDetails userDetails, Long expiration);
    boolean validate(String token);
    UserDetails getUserFromToken(String token);
}
To powinno dać ci dobry początek. Szczęśliwego kodowania. :)
 174
Author: Chris Cashwell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-08 17:25:14

Możesz rozważyć Digest Access Authentication . Zasadniczo protokół jest następujący:

  1. Prośba jest składana od klienta
  2. serwer odpowiada unikalnym ciągiem nonce
  3. Klient dostarcza nazwę użytkownika i hasło (i kilka innych wartości) md5 zaszyfrowane nonce; ten hash jest znany jako HA1
  4. serwer jest w stanie zweryfikować tożsamość klienta i dostarczyć żądane materiały
  5. Komunikacja z nonce może trwać aż do serwera dostarcza nową nonce (licznik jest używany do eliminowania ataków replay)

Cała ta komunikacja odbywa się za pomocą nagłówków, które, jak wskazuje jmort253, są na ogół bezpieczniejsze niż przekazywanie poufnych materiałów w parametrach adresu url.

Uwierzytelnianie Digest Access jest obsługiwane przez Spring Security . Zauważ, że chociaż dokumenty mówią, że musisz mieć dostęp do zwykłego hasła klienta, możesz pomyślnie uwierzytelnić, jeśli masz HA1 hash dla Twojego klienta.

 19
Author: Tim Pote,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-31 02:57:43

Jeśli chodzi o tokeny przenoszące informacje, JSON Web Tokens (http://jwt.io ) jest genialną technologią. Główną koncepcją jest osadzenie elementów informacyjnych (twierdzeń) w tokenie, a następnie podpisanie całego tokenu, aby koniec walidacji mógł sprawdzić, czy twierdzenia są rzeczywiście wiarygodne.

Używam tej implementacji Javy: https://bitbucket.org/b_c/jose4j/wiki/Home

Istnieje również moduł Spring (spring-security-jwt) , ale nie zajrzałem do tego, co to podpory.

 4
Author: Leif John,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-28 10:01:28

Dlaczego nie zaczniesz używać OAuth z JSON WebTokens

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

OAuth2 jest standardowym protokołem/frameworkiem autoryzacji. Według oficjalnej specyfikacji OAuth2 :

Więcej informacji znajdziesz tutaj

 1
Author: vaquar khan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-24 04:43:04