270 votes

Authentification RESTful via Spring

Problème:
Nous avons un Spring MVC RESTful API qui contient des informations sensibles. L'API doit être garanti, cependant envoyer les identifiants de l'utilisateur (user/pass combo) avec chaque demande n'est pas souhaitable. Par REPOS lignes directrices (internes et les exigences d'affaires), le serveur doit rester apatrides. L'API sera consommée par un autre serveur dans un mashup approche de style.

Exigences:

  • Le Client effectue une demande d' .../authenticate (non protégé URL) avec les informations d'identification; serveur renvoie un jeton sécurisé qui contient suffisamment d'informations pour le serveur pour valider les demandes futures et apatride. Ce serait probablement composé de la même information que le Ressort de Sécurité du Souvenir de Moi Jeton.

  • Le Client effectue des demandes ultérieures divers (protégés), les Url, en y ajoutant le obtenu précédemment jeton comme un paramètre de requête (ou, du moins, de préférence, une tête de requête HTTP).

  • Le Client ne peut pas être prévu pour stocker des cookies.

  • Depuis que nous utilisons le Printemps déjà, la solution doit faire l'utilisation de Ressort de Sécurité.

Nous avons été taper la tête contre le mur en essayant de faire ce travail, alors j'espère 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?

193voto

Chris Cashwell Points 9264

Nous avons réussi à le faire fonctionner exactement comme décrit dans l'OP, et j'espère que quelqu'un d'autre peut faire usage de la solution. Voici ce que nous avons fait:

Définir le contexte de sécurité de la sorte:

<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 class="com.demo.api.support.spring.AuthenticationTokenProcessingFilter"
    id="authenticationTokenProcessingFilter">
    <constructor-arg ref="authenticationManager" />
</bean>

Comme vous pouvez le voir, nous avons créé une coutume AuthenticationEntryPoint, ce qui, fondamentalement, tout simplement renvoie une 401 Unauthorized si la demande n'est pas authentifiée dans la chaîne de filtrage par nos 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);
    }
}

De toute évidence, TokenUtils contient certaines privé (et très spécifiques) et le code ne peut pas être facilement partagés. Voici son interface:

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

Qui devrait vous descendez à un bon début. Heureux de codage. :)

28voto

Tim Pote Points 8905

Vous pourriez envisager de Digest Authentification d'Accès. Essentiellement, le protocole est comme suit:

  1. Demande est faite à partir d'un client
  2. Le serveur répond avec une unique chaîne de nonce
  3. Client fournit un nom d'utilisateur et le mot de passe (et quelques autres valeurs) hashé en md5 avec le nonce; ce hash est connu comme HA1
  4. Le serveur est alors en mesure de vérifier l'identité du client et de servir le matériel demandé
  5. La Communication avec le nonce peut continuer jusqu'à ce que le serveur fournit un nouveau nonce (un compteur est utilisé pour éliminer les attaques de relecture)

L'ensemble de cette communication est faite par des en-têtes, qui, comme jmort253 points, est généralement plus sûre que la communication des matériaux dans les paramètres de l'url.

Digest Authentification d'Accès est pris en charge par la Sécurité Printemps. Notez que, bien que les docs disent que vous devez avoir accès à votre client de la plaine de texte mot de passe, vous pouvez vous authentifier avec succès si vous avez la HA1 de hachage pour votre client.

0voto

Roy Truelove Points 6532

Je recommande fortement que vous considériez également cette implémentation:

http://stackoverflow.com/a/14735345/295797

Semblable à la réponse acceptée ci-dessus, mais OMI beaucoup plus propre

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X