RESTful Authentication via Spring

前端 未结 4 814
醉话见心
醉话见心 2020-11-27 23:58

Problem:
We have a Spring MVC-based RESTful API which contains sensitive information. The API should be secured, however sending the user\'s credentials

相关标签:
4条回答
  • 2020-11-28 00:32

    You might consider Digest Access Authentication. Essentially the protocol is as follows:

    1. Request is made from client
    2. Server responds with a unique nonce string
    3. Client supplies a username and password (and some other values) md5 hashed with the nonce; this hash is known as HA1
    4. Server is then able to verify client's identity and serve up the requested materials
    5. Communication with the nonce can continue until the server supplies a new nonce (a counter is used to eliminate replay attacks)

    All of this communication is made through headers, which, as jmort253 points out, is generally more secure than communicating sensitive material in the url parameters.

    Digest Access Authentication is supported by Spring Security. Notice that, although the docs say that you must have access to your client's plain-text password, you can successfully authenticate if you have the HA1 hash for your client.

    0 讨论(0)
  • 2020-11-28 00:33

    Regarding tokens carrying information, JSON Web Tokens (http://jwt.io) is a brilliant technology. The main concept is to embed information elements (claims) into the token, and then signing the whole token so that the validating end can verify that the claims are indeed trustworthy.

    I use this Java implementation: https://bitbucket.org/b_c/jose4j/wiki/Home

    There is also a Spring module (spring-security-jwt), but I haven't looked into what it supports.

    0 讨论(0)
  • 2020-11-28 00:43

    Why don't you start using OAuth with JSON WebTokens

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

    OAuth2 is an standardized authorization protocol/framework. As per Official OAuth2 Specification:

    You can find more info here

    0 讨论(0)
  • 2020-11-28 00:49

    We managed to get this working exactly as described in the OP, and hopefully someone else can make use of the solution. Here's what we did:

    Set up the security context like so:

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

    As you can see, we've created a custom AuthenticationEntryPoint, which basically just returns a 401 Unauthorized if the request wasn't authenticated in the filter chain by our 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);
        }
    }
    

    Obviously, TokenUtils contains some privy (and very case-specific) code and can't be readily shared. Here's its interface:

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

    That ought to get you off to a good start. Happy coding. :)

    0 讨论(0)
提交回复
热议问题