web-dev-qa-db-fra.com

Authentification RESTful via Spring

Problème:
Nous avons une API RESTful basée sur Spring MVC qui contient des informations sensibles. L'API doit être sécurisée. Toutefois, l'envoi des informations d'identification de l'utilisateur (combo utilisateur/mot de passe) avec chaque demande n'est pas souhaitable. Conformément aux consignes REST (et aux exigences commerciales internes), le serveur doit rester sans état. L'API sera utilisée par un autre serveur dans une approche de type mashup.

Conditions requises:

  • Le client envoie une demande à .../authenticate (URL non protégée) avec les informations d'identification. Le serveur renvoie un jeton sécurisé contenant suffisamment d'informations pour que le serveur puisse valider les demandes futures et rester sans état. Cela consisterait probablement en la même information que celle de Spring Security: Remember-Me Token .

  • Le client envoie des requêtes ultérieures à diverses URL (protégées), en ajoutant le jeton obtenu précédemment comme paramètre de requête (ou, de façon moins souhaitable, en-tête de requête HTTP).

  • Le client ne peut pas s'attendre à stocker des cookies.

  • Comme nous utilisons déjà Spring, la solution devrait utiliser Spring Security.

Nous nous sommes cogné la tête contre le mur pour essayer de faire fonctionner cela, alors espérons que quelqu'un a déjà résolu ce problème.

Étant donné le scénario ci-dessus, comment pourriez-vous résoudre ce besoin particulier?

246
Chris Cashwell

Nous avons réussi à faire en sorte que cela fonctionne exactement comme décrit dans le PO, et espérons que quelqu'un d'autre puisse utiliser la solution. Voici ce que nous avons fait:

Configurez le contexte de sécurité comme suit:

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

Comme vous pouvez le constater, nous avons créé une variable personnalisée AuthenticationEntryPoint, qui renvoie simplement un 401 Unauthorized si la demande n'a pas été authentifiée dans la chaîne de filtres par notre 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);
    }
}

Évidemment, TokenUtils contient du code privé (et très spécifique à la casse) et ne peut pas être facilement partagé. Voici son interface:

public interface TokenUtils {
    String getToken(UserDetails userDetails);
    String getToken(UserDetails userDetails, Long expiration);
    boolean validate(String token);
    UserDetails getUserFromToken(String token);
}

Cela devrait vous permettre de bien démarrer. Bonne codage. :)

179
Chris Cashwell

Vous pourriez envisager Authentification d'accès Digest . Le protocole est essentiellement le suivant:

  1. La demande est faite par le client
  2. Le serveur répond avec une chaîne de nonce unique
  3. Le client fournit un nom d'utilisateur et un mot de passe (et quelques autres valeurs) md5 haché avec le nonce; ce hachage est appelé HA1
  4. Le serveur est alors capable de vérifier l'identité du client et de servir les matériaux demandés
  5. La communication avec le nonce peut continuer jusqu'à ce que le serveur fournisse un nouveau nonce (un compteur est utilisé pour éliminer les attaques par rejeu)

Toute cette communication s'effectue par le biais d'en-têtes, ce qui, comme le souligne jmort253, est généralement plus sécurisé que la communication de matériel sensible dans les paramètres d'URL.

L'authentification Digest Access est prise en charge par Spring Security . Notez que, bien que les documents indiquent que vous devez avoir accès au mot de passe en texte brut de votre client, vous pouvez vous authentifier avec succès si vous avez le hachage HA1 pour votre client.

22
Tim Pote

En ce qui concerne les jetons contenant des informations, JSON Web Tokens ( http://jwt.io ) est une technologie brillante. Le concept principal consiste à incorporer des éléments d'information (revendications) dans le jeton, puis à signer l'intégralité du jeton afin que l'extrémité en cours de validation puisse vérifier que les revendications sont réellement dignes de confiance.

J'utilise cette implémentation Java: https://bitbucket.org/b_c/jose4j/wiki/Home

Il existe également un module Spring (spring-security-jwt), mais je n’ai pas cherché à savoir ce qu’il supportait.

4
Leif John

Pourquoi ne commencez-vous pas à utiliser OAuth avec JSON WebTokens

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

OAuth2 est un protocole/cadre d'autorisation normalisé. Selon Official OAuth2 Specification :

Vous pouvez trouver plus d'informations ici

1
vaquar khan