Package org.springframework.security.test.web.servlet.request

Source Code of org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors$DigestRequestPostProcessor

/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.test.web.servlet.request;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.security.test.context.TestSecurityContextHolder;
import org.springframework.security.test.web.support.WebTestUtils;
import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.util.Assert;
import org.springframework.util.DigestUtils;

/**
* Contains {@link MockMvc} {@link RequestPostProcessor} implementations for
* Spring Security.
*
* @author Rob Winch
* @since 4.0
*/
public final class SecurityMockMvcRequestPostProcessors {

    /**
     * Creates a DigestRequestPostProcessor that enables easily adding digest based authentication to a request.
     *
     * @return the DigestRequestPostProcessor to use
     */
    public static DigestRequestPostProcessor digest() {
        return new DigestRequestPostProcessor();
    }

    /**
     * Creates a DigestRequestPostProcessor that enables easily adding digest based authentication to a request.
     *
     * @param username the username to use
     * @return the DigestRequestPostProcessor to use
     */
    public static DigestRequestPostProcessor digest(String username) {
        return digest().username(username);
    }

    /**
     * Populates the provided X509Certificate instances on the request.
     * @param certificates the X509Certificate instances to pouplate
     * @return the {@link org.springframework.test.web.servlet.request.RequestPostProcessor} to use.
     */
    public static RequestPostProcessor x509(X509Certificate... certificates) {
        return new X509RequestPostProcessor(certificates);
    }

    /**
     * Finds an X509Cetificate using a resoureName and populates it on the request.
     *
     * @param resourceName the name of the X509Certificate resource
     * @return the {@link org.springframework.test.web.servlet.request.RequestPostProcessor} to use.
     * @throws IOException
     * @throws CertificateException
     */
    public static RequestPostProcessor x509(String resourceName) throws IOException, CertificateException {
        ResourceLoader loader = new DefaultResourceLoader();
        Resource resource = loader.getResource(resourceName);
        InputStream inputStream = resource.getInputStream();
        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
        X509Certificate certificate = (X509Certificate) certFactory.generateCertificate(inputStream);
        return x509(certificate);
    }

    /**
     * Creates a {@link RequestPostProcessor} that will automatically populate a
     * valid {@link CsrfToken} in the request.
     *
     * @return the {@link CsrfRequestPostProcessor} for further customizations.
     */
    public static CsrfRequestPostProcessor csrf() {
        return new CsrfRequestPostProcessor();
    }

    /**
     * Creates a {@link RequestPostProcessor} that can be used to ensure that
     * the resulting request is ran with the user in the
     * {@link TestSecurityContextHolder}.
     *
     * @return the {@link RequestPostProcessor} to sue
     */
    public static RequestPostProcessor testSecurityContext() {
        return new TestSecurityContextHolderPostProcessor();
    }

    /**
     * Establish a {@link SecurityContext} that has a
     * {@link UsernamePasswordAuthenticationToken} for the
     * {@link Authentication#getPrincipal()} and a {@link User} for the
     * {@link UsernamePasswordAuthenticationToken#getPrincipal()}. All details
     * are declarative and do not require that the user actually exists.
     *
     * @param username
     *            the username to populate
     * @return the {@link UserRequestPostProcessor} for additional customization
     */
    public static UserRequestPostProcessor user(String username) {
        return new UserRequestPostProcessor(username);
    }

    /**
     * Establish a {@link SecurityContext} that has a
     * {@link UsernamePasswordAuthenticationToken} for the
     * {@link Authentication#getPrincipal()} and a custom {@link UserDetails}
     * for the {@link UsernamePasswordAuthenticationToken#getPrincipal()}. All
     * details are declarative and do not require that the user actually exists.
     *
     * @param user
     *            the UserDetails to populate
     * @return the {@link RequestPostProcessor} to use
     */
    public static RequestPostProcessor user(UserDetails user) {
        return new UserDetailsRequestPostProcessor(user);
    }

    /**
     * Establish a {@link SecurityContext} that uses the specified {@link Authentication} for the
     * {@link Authentication#getPrincipal()} and a custom {@link UserDetails}. All
     * details are declarative and do not require that the user actually exists.
     *
     * @param user
     *            the UserDetails to populate
     * @return the {@link RequestPostProcessor} to use
     */
    public static RequestPostProcessor authentication(
            Authentication authentication) {
        return new AuthenticationRequestPostProcessor(authentication);
    }

    /**
     * Establish the specified {@link SecurityContext} to be used.
     */
    public static RequestPostProcessor securityContext(
            SecurityContext securityContext) {
        return new SecurityContextRequestPostProcessor(securityContext);
    }

    /**
     * Convenience mechanism for setting the Authorization header to use HTTP
     * Basic with the given username and password. This method will
     * automatically perform the necessary Base64 encoding.
     *
     * @param username
     *            the username to include in the Authorization header.
     * @param password the password to include in the Authorization header.
     * @return the {@link RequestPostProcessor} to use
     */
    public static RequestPostProcessor httpBasic(String username, String password) {
        return new HttpBasicRequestPostProcessor(username, password);
    }

    /**
     * Populates the X509Certificate instances onto the request
     */
    private static class X509RequestPostProcessor implements RequestPostProcessor {
        private final X509Certificate[] certificates;

        private X509RequestPostProcessor(X509Certificate... certificates) {
            Assert.notNull("X509Certificate cannot be null");
            this.certificates = certificates;
        }

        public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
            request.setAttribute("javax.servlet.request.X509Certificate", certificates);
            return request;
        }
    }

    /**
     * Populates a valid {@link CsrfToken} into the request.
     *
     * @author Rob Winch
     * @since 4.0
     */
    public static class CsrfRequestPostProcessor implements
            RequestPostProcessor {

        private boolean asHeader;

        private boolean useInvalidToken;

        /*
         * (non-Javadoc)
         *
         * @see
         * org.springframework.test.web.servlet.request.RequestPostProcessor
         * #postProcessRequest
         * (org.springframework.mock.web.MockHttpServletRequest)
         */
        public MockHttpServletRequest postProcessRequest(
                MockHttpServletRequest request) {

            CsrfTokenRepository repository = WebTestUtils
                    .getCsrfTokenRepository(request);
            CsrfToken token = repository.generateToken(request);
            repository.saveToken(token, request, new MockHttpServletResponse());
            String tokenValue = useInvalidToken ? "invalid" + token.getToken() : token.getToken();
            if(asHeader) {
                request.addHeader(token.getHeaderName(), tokenValue);
            } else {
                request.setParameter(token.getParameterName(), tokenValue);
            }
            return request;
        }

        /**
         * Instead of using the {@link CsrfToken} as a request parameter
         * (default) will populate the {@link CsrfToken} as a header.
         *
         * @return the {@link CsrfRequestPostProcessor} for additional customizations
         */
        public CsrfRequestPostProcessor asHeader() {
            this.asHeader = true;
            return this;
        }

        /**
         * Populates an invalid token value on the request.
         *
         * @return the {@link CsrfRequestPostProcessor} for additional customizations
         */
        public CsrfRequestPostProcessor useInvalidToken() {
            this.useInvalidToken = true;
            return this;
        }

        private CsrfRequestPostProcessor() {}
    }

    public static class DigestRequestPostProcessor implements RequestPostProcessor {
        private String username = "user";

        private String password = "password";

        private String realm = "Spring Security";

        private String nonce = generateNonce(60);

        private String qop = "auth";

        private String nc = "00000001";

        private String cnonce = "c822c727a648aba7";

        /**
         * Configures the username to use
         * @param username the username to use
         * @return the DigestRequestPostProcessor for further customization
         */
        private DigestRequestPostProcessor username(String username) {
            Assert.notNull(username, "username cannot be null");
            this.username = username;
            return this;
        }

        /**
         * Configures the password to use
         * @param password the password to use
         * @return the DigestRequestPostProcessor for further customization
         */
        public DigestRequestPostProcessor password(String password) {
            Assert.notNull(password, "password cannot be null");
            this.password = password;
            return this;
        }

        /**
         * Configures the realm to use
         * @param realm the realm to use
         * @return the DigestRequestPostProcessor for further customization
         */
        public DigestRequestPostProcessor realm(String realm) {
            Assert.notNull(realm, "realm cannot be null");
            this.realm = realm;
            return this;
        }

        private static String generateNonce(int validitySeconds) {
            long expiryTime = System.currentTimeMillis() + (validitySeconds * 1000);
            String toDigest = expiryTime + ":" + "key";
            String signatureValue = md5Hex(toDigest);
            String nonceValue = expiryTime + ":" + signatureValue;

            return new String(Base64.encode(nonceValue.getBytes()));
        }

        private String createAuthorizationHeader(MockHttpServletRequest request) {
            String uri = request.getRequestURI();
            String responseDigest = generateDigest(username, realm, password, request.getMethod(),
                    uri, qop, nonce, nc, cnonce);
            return "Digest username=\"" + username + "\", realm=\"" + realm + "\", nonce=\"" + nonce + "\", uri=\"" + uri
                    + "\", response=\"" + responseDigest + "\", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + cnonce + "\"";
        }

        public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {

            request.addHeader("Authorization",
                    createAuthorizationHeader(request));
            return request;
        }


        /**
         * Computes the <code>response</code> portion of a Digest authentication header. Both the server and user
         * agent should compute the <code>response</code> independently. Provided as a static method to simplify the
         * coding of user agents.
         *
         * @param username               the user's login name.
         * @param realm                  the name of the realm.
         * @param password               the user's password in plaintext or ready-encoded.
         * @param httpMethod             the HTTP request method (GET, POST etc.)
         * @param uri                    the request URI.
         * @param qop                    the qop directive, or null if not set.
         * @param nonce                  the nonce supplied by the server
         * @param nc                     the "nonce-count" as defined in RFC 2617.
         * @param cnonce                 opaque string supplied by the client when qop is set.
         * @return the MD5 of the digest authentication response, encoded in hex
         * @throws IllegalArgumentException if the supplied qop value is unsupported.
         */
        private static String generateDigest(String username, String realm, String password,
                                     String httpMethod, String uri, String qop, String nonce, String nc, String cnonce)
                throws IllegalArgumentException {
            String a1Md5 = encodePasswordInA1Format(username, realm, password);
            String a2 = httpMethod + ":" + uri;
            String a2Md5 = md5Hex(a2);

            String digest;

            if (qop == null) {
                // as per RFC 2069 compliant clients (also reaffirmed by RFC 2617)
                digest = a1Md5 + ":" + nonce + ":" + a2Md5;
            } else if ("auth".equals(qop)) {
                // As per RFC 2617 compliant clients
                digest = a1Md5 + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + a2Md5;
            } else {
                throw new IllegalArgumentException("This method does not support a qop: '" + qop + "'");
            }

            return md5Hex(digest);
        }

        static String encodePasswordInA1Format(String username, String realm, String password) {
            String a1 = username + ":" + realm + ":" + password;

            return md5Hex(a1);
        }

        private static String md5Hex(String a2) {
            try {
                return DigestUtils.md5DigestAsHex(a2.getBytes("UTF-8"));
            } catch(UnsupportedEncodingException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * Support class for {@link RequestPostProcessor}'s that establish a Spring
     * Security context
     */
    private static abstract class SecurityContextRequestPostProcessorSupport {

        /**
         * Saves the specified {@link Authentication} into an empty
         * {@link SecurityContext} using the {@link SecurityContextRepository}.
         *
         * @param authentication the {@link Authentication} to save
         * @param request the {@link HttpServletRequest} to use
         */
        final void save(Authentication authentication,
                HttpServletRequest request) {
            SecurityContext securityContext = SecurityContextHolder
                    .createEmptyContext();
            securityContext.setAuthentication(authentication);
            save(securityContext, request);
        }

        /**
         * Saves the {@link SecurityContext} using the
         * {@link SecurityContextRepository}
         *
         * @param securityContext the {@link SecurityContext} to save
         * @param request the {@link HttpServletRequest} to use
         */
        final void save(SecurityContext securityContext,
                HttpServletRequest request) {
            SecurityContextRepository securityContextRepository = WebTestUtils.getSecurityContextRepository(request);
            boolean isTestRepository = securityContextRepository instanceof TestSecurityContextRepository;
            if(!isTestRepository) {
                securityContextRepository = new TestSecurityContextRepository(securityContextRepository);
                WebTestUtils.setSecurityContextRepository(request, securityContextRepository);
            }

            HttpServletResponse response = new MockHttpServletResponse();

            HttpRequestResponseHolder requestResponseHolder = new HttpRequestResponseHolder(
                    request, response);
            securityContextRepository.loadContext(requestResponseHolder);

            request = requestResponseHolder.getRequest();
            response = requestResponseHolder.getResponse();

            securityContextRepository.saveContext(securityContext, request,
                    response);
        }

        /**
         * Used to wrap the SecurityContextRepository to provide support for testing in stateless mode
         */
        private static class TestSecurityContextRepository implements SecurityContextRepository {
            private final String ATTR_NAME = TestSecurityContextRepository.class.getName().concat(".REPO");

            private final SecurityContextRepository delegate;

            private TestSecurityContextRepository(SecurityContextRepository delegate) {
                this.delegate = delegate;
            }

            public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
                SecurityContext result = getContext(requestResponseHolder.getRequest());
                // always load from the delegate to ensure the request/response in the holder are updated
                // remember the SecurityContextRepository is used in many different locations
                SecurityContext delegateResult = delegate.loadContext(requestResponseHolder);
                return result == null ? delegateResult : result;
            }

            public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
                request.setAttribute(ATTR_NAME, context);
                delegate.saveContext(context, request, response);
            }

            public boolean containsContext(HttpServletRequest request) {
                return getContext(request) != null || delegate.containsContext(request);
            }

            private SecurityContext getContext(HttpServletRequest request) {
                return (SecurityContext) request.getAttribute(ATTR_NAME);
            }
        }
    }

    /**
     * Associates the {@link SecurityContext} found in
     * {@link TestSecurityContextHolder#getContext()} with the
     * {@link MockHttpServletRequest}.
     *
     * @author Rob Winch
     * @since 4.0
     */
    private final static class TestSecurityContextHolderPostProcessor extends
            SecurityContextRequestPostProcessorSupport implements
            RequestPostProcessor {

        public MockHttpServletRequest postProcessRequest(
                MockHttpServletRequest request) {
            save(TestSecurityContextHolder.getContext(), request);
            return request;
        }
    }

    /**
     * Associates the specified {@link SecurityContext} with the
     * {@link MockHttpServletRequest}.
     *
     * @author Rob Winch
     * @since 4.0
     */
    private final static class SecurityContextRequestPostProcessor extends
            SecurityContextRequestPostProcessorSupport implements
            RequestPostProcessor {

        private final SecurityContext securityContext;

        private SecurityContextRequestPostProcessor(
                SecurityContext securityContext) {
            this.securityContext = securityContext;
        }

        public MockHttpServletRequest postProcessRequest(
                MockHttpServletRequest request) {
            save(this.securityContext, request);
            return request;
        }
    }

    /**
     * Sets the specified {@link Authentication} on an empty
     * {@link SecurityContext} and associates it to the
     * {@link MockHttpServletRequest}
     *
     * @author Rob Winch
     * @since 4.0
     *
     */
    private final static class AuthenticationRequestPostProcessor extends
            SecurityContextRequestPostProcessorSupport implements
            RequestPostProcessor {
        private final Authentication authentication;

        private AuthenticationRequestPostProcessor(Authentication authentication) {
            this.authentication = authentication;
        }

        public MockHttpServletRequest postProcessRequest(
                MockHttpServletRequest request) {
            SecurityContext context = SecurityContextHolder.getContext();
            context.setAuthentication(authentication);
            save(authentication, request);
            return request;
        }
    }

    /**
     * Creates a {@link UsernamePasswordAuthenticationToken} and sets the
     * {@link UserDetails} as the principal and associates it to the
     * {@link MockHttpServletRequest}.
     *
     * @author Rob Winch
     * @since 4.0
     */
    private final static class UserDetailsRequestPostProcessor implements
            RequestPostProcessor {
        private final RequestPostProcessor delegate;

        public UserDetailsRequestPostProcessor(UserDetails user) {
            Authentication token = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());

            delegate = new AuthenticationRequestPostProcessor(token);
        }

        public MockHttpServletRequest postProcessRequest(
                MockHttpServletRequest request) {
            return delegate.postProcessRequest(request);
        }
    }

    /**
     * Creates a {@link UsernamePasswordAuthenticationToken} and sets the
     * principal to be a {@link User} and associates it to the
     * {@link MockHttpServletRequest}.
     *
     * @author Rob Winch
     * @since 4.0
     */
    public final static class UserRequestPostProcessor extends
            SecurityContextRequestPostProcessorSupport implements
            RequestPostProcessor {

        private String username;

        private String password = "password";

        private static final String ROLE_PREFIX = "ROLE_";

        private Collection<? extends GrantedAuthority> authorities = AuthorityUtils
                .createAuthorityList("ROLE_USER");

        private boolean enabled = true;

        private boolean accountNonExpired = true;

        private boolean credentialsNonExpired = true;

        private boolean accountNonLocked = true;

        /**
         * Creates a new instance with the given username
         * @param username the username to use
         */
        private UserRequestPostProcessor(String username) {
            Assert.notNull(username, "username cannot be null");
            this.username = username;
        }

        /**
         * Specify the roles of the user to authenticate as. This method is
         * similar to {@link #authorities(GrantedAuthority...)}, but just not as
         * flexible.
         *
         * @param roles
         *            The roles to populate. Note that if the role does not
         *            start with {@link #rolePrefix(String)} it will
         *            automatically be prepended. This means by default
         *            {@code roles("ROLE_USER")} and {@code roles("USER")} are
         *            equivalent.
         * @see #authorities(GrantedAuthority...)
         * @see #rolePrefix(String)
         * @return the UserRequestPostProcessor for further customizations
         */
        public UserRequestPostProcessor roles(String... roles) {
            List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(
                    roles.length);
            for (String role : roles) {
                if (role.startsWith(ROLE_PREFIX)) {
                    throw new IllegalArgumentException("Role should not start with "+ROLE_PREFIX + " since this method automatically prefixes with this value. Got "+ role);
                } else {
                    authorities.add(new SimpleGrantedAuthority(ROLE_PREFIX
                            + role));
                }
            }
            this.authorities = authorities;
            return this;
        }

        /**
         * Populates the user's {@link GrantedAuthority}'s. The default is
         * ROLE_USER.
         *
         * @param authorities
         * @see #roles(String...)
         * @return the UserRequestPostProcessor for further customizations
         */
        public UserRequestPostProcessor authorities(
                GrantedAuthority... authorities) {
            return authorities(Arrays.asList(authorities));
        }

        /**
         * Populates the user's {@link GrantedAuthority}'s. The default is
         * ROLE_USER.
         *
         * @param authorities
         * @see #roles(String...)
         * @return the UserRequestPostProcessor for further customizations
         */
        public UserRequestPostProcessor authorities(
                Collection<? extends GrantedAuthority> authorities) {
            this.authorities = authorities;
            return this;
        }

        /**
         * Populates the user's password. The default is "password"
         *
         * @param password
         *            the user's password
         * @return the UserRequestPostProcessor for further customizations
         */
        public UserRequestPostProcessor password(String password) {
            this.password = password;
            return this;
        }

        public MockHttpServletRequest postProcessRequest(
                MockHttpServletRequest request) {
            UserDetailsRequestPostProcessor delegate = new UserDetailsRequestPostProcessor(createUser());
            return delegate.postProcessRequest(request);
        }

        /**
         * Creates a new {@link User}
         * @return the {@link User} for the principal
         */
        private User createUser() {
            return new User(username, password, enabled, accountNonExpired,
                    credentialsNonExpired, accountNonLocked, authorities);
        }
    }

    private static class HttpBasicRequestPostProcessor implements RequestPostProcessor {
        private String headerValue;

        private HttpBasicRequestPostProcessor(String username, String password) {
            byte[] toEncode;
            try {
                toEncode = (username + ":" + password).getBytes("UTF-8");
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException(e);
            }
            this.headerValue = "Basic " + new String(Base64.encode(toEncode));
        }

        public MockHttpServletRequest postProcessRequest(
                MockHttpServletRequest request) {
            request.addHeader("Authorization", headerValue);
            return request;
        }
    }

    private SecurityMockMvcRequestPostProcessors() { }
}
TOP

Related Classes of org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors$DigestRequestPostProcessor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.