• security with restful


    国内私募机构九鼎控股打造APP,来就送 20元现金领取地址:http://jdb.jiudingcapital.com/phone.html
    内部邀请码:C8E245J (不写邀请码,没有现金送)
    国内私募机构九鼎控股打造,九鼎投资是在全国股份转让系统挂牌的公众公司,股票代码为430719,为中国PE第一股,市值超1000亿元。 

    ------------------------------------------------------------------------------------------------------------------------------------------------------------------

     

    http://marcin-michalski.pl/

    Webservice (Spring Security + JavaScript + AJAX)

    Recently I was playing a little bit with different types of authentications. As you know we distinguish Basic, Digest and Form-Based authentications (more about that here). When we are working with web application it is usually sufficient to use either of them but the problem arises when we need to work with RESTfull system where there is no login page or any browser pop-up window to enter the credentials.

    One of the solutions would be to store credentials as a plain text somewhere in the request, however it is not very secure way and I would not recommend this solution. It is better to use Digest Authentication (RFC 2617). With this approach we always send data in encrypted format. Spring Security gives us most of that functionality out of the box however it does not support pure JavaScript clients. That is why I had to do some refinements in order to make it work.

    Below I’m presenting step by step what needs to be done in order to configure Spring Security and jQuery/Ajax client that calls secured resource.

    You may also download whole code from our github repository.

    Setting Spring Contexts

    Main Application context file – web-application-context.xml:

     XML |     copy code | ?  
    01
    <?xml version="1.0" encoding="UTF-8"?>
    02
    <beans xmlns="http://www.springframework.org/schema/beans"
    03
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    04
           xmlns:context="http://www.springframework.org/schema/context"
    05
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
    06
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
    07
     
    08
       <import resource="security.xml"/>
    09
       <import resource="security-inmemory-auth-provider.xml"/>
    10
     
    11
    </beans>

    Security configuration context – security.xml

     XML |     copy code | ?  
    01
    <?xml version="1.0" encoding="UTF-8"?>
    02
    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    03
           xmlns:security="http://www.springframework.org/schema/security"
    04
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
    05
     http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">
    06
     
    07
       <security:global-method-security secured-annotations="enabled" pre-post-annotations="enabled"/>
    08
     
    09
       <security:http pattern="/resources/**" security="none"/>
    10
       <security:http pattern="/guest/**" security="none"/>
    11
     
    12
       <security:http 
    13
                     entry-point-ref="digestEntryPoint" >
    14
       <security:custom-filter ref="digestFilter" position="BASIC_AUTH_FILTER"/>
    15
          <security:intercept-url pattern="/user/**" access="ROLE_USER"/>
    16
       </security:http>
    17
     
    18
     
    19
     <bean id="digestFilter" class="org.springframework.security.web.authentication.www.DigestAuthenticationFilter">
    20
       <property name="userDetailsService" ref="inMemoryUserService"/>
    21
       <property name="authenticationEntryPoint" ref="digestEntryPoint"/>
    22
     </bean>
    23
     <bean id="digestEntryPoint" class="pl.arrowgroup.restauth.security.AjaxDigestAuthenticationEntryPoint">
    24
       <property name="realmName" value="REST-Realm"/>
    25
       <property name="key" value="testNonce"/>
    26
       <property name="nonceValiditySeconds" value="10000"/>
    27
     </bean>
    28
     
    29
     
    30
       <security:authentication-manager alias="authenticationManager">
    31
          <security:authentication-provider ref="inMemoryAuthenticationProvider"/>
    32
       </security:authentication-manager>
    33
    </beans>

    Authentication provider settings (users/passwords/roles) – security-inmemory-auth-provider.xml

     XML |     copy code | ?  
    01
    <?xml version="1.0" encoding="UTF-8"?>
    02
    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    03
           xmlns:security="http://www.springframework.org/schema/security"
    04
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
    05
     http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">
    06
     
    07
     <bean id="inMemoryAuthenticationProvider"
    08
             class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
    09
          <property name="hideUserNotFoundExceptions" value="false"/>
    10
          <property name="userDetailsService" ref="inMemoryUserService"/>
    11
          <property name="messageSource" ref="messageSource"/>
    12
       </bean>
    13
     
    14
     <security:user-service id="inMemoryUserService">
    15
     <security:user name="marcin" password="michalski" authorities="ROLE_USER"/> 
    16
     </security:user-service>
    17
    </beans> 

    Servlet front controller configuration (SpringMVC) – servlet-context.xml

     XML |     copy code | ?  
    01
    <?xml version="1.0" encoding="UTF-8"?>
    02
    <beans xmlns="http://www.springframework.org/schema/beans"
    03
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    04
           xmlns:context="http://www.springframework.org/schema/context"
    05
           xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:p="http://www.springframework.org/schema/p"
    06
           xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
    07
     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
    08
     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
    09
     
    10
       <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    11
          <property name="defaultStatusCode" value="200"/>
    12
          <property name="defaultErrorView" value="/error"/>
    13
          <property name="exceptionMappings">
    14
           <props>
    15
           <prop key="org.springframework.security.access.AccessDeniedException">/denied</prop>
    16
           </props>
    17
          </property>
    18
       </bean>
    19
     
    20
      <context:component-scan base-package="pl.arrowgroup.restauth.controllers"/>
    21
     
    22
      <bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
    23
          <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    24
          <property name="prefix" value="/WEB-INF/jsp/"/>
    25
          <property name="suffix" value=".jsp"/>
    26
       </bean>
    27
     
    28
      <mvc:annotation-driven>
    29
          <mvc:message-converters>
    30
             <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
    31
             <bean class="org.springframework.http.converter.ResourceHttpMessageConverter"/>
    32
             <bean class="org.springframework.http.converter.BufferedImageHttpMessageConverter"/>
    33
             <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
    34
          </mvc:message-converters>
    35
       </mvc:annotation-driven>
    36
     
    37
     
    38
      <mvc:resources mapping="/resources/**" location="/resources/"/>
    39
      <bean id="jsonMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
    40
       <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    41
          <property name="supportedMethods" value="GET,POST,PUT,HEAD,DELETE"/>
    42
          <property name="messageConverters">
    43
             <list>
    44
                <ref bean="jsonMessageConverter"/>
    45
             </list>
    46
          </property>
    47
       </bean>
    48
    </beans>

    For simplicity I have created security layer that uses inmemory authentication provider probably in production environment you would like to have user/password information stored in database or LDAP. So in that case all you have to do is change the authentication provider.

    Not to get in to much details I just say that I have configured typical SpringMVC context and provided basic security layer with digest authentication.

    As you can see almost everything is provided using default Spring classes with one exception AjaxDigestAuthenticationEntryPoint. Although it is my custom class  it  actually acts like Spring’s DigestAuthenticationEntryPoint and the only difference it does is that it sends Forbidded (403) http status code instead of Unauthorized(401) once authentication fails.  It was done to prevent browser from displaying unwanted pop-ups when 401 status is returned. 

    AjaxDigestAuthenticationEntryPoint.java:

     Java |     copy code | ?  
    01
    package pl.arrowgroup.restauth.security;
    02
    import java.io.IOException;
    03
     
    04
    import javax.servlet.ServletException;
    05
    import javax.servlet.http.HttpServletRequest;
    06
    import javax.servlet.http.HttpServletResponse;
    07
    import javax.servlet.http.HttpServletResponseWrapper;
    08
     
    09
    import org.springframework.security.core.AuthenticationException;
    10
    import org.springframework.security.web.authentication.www.DigestAuthenticationEntryPoint;
    11
     
    12
    public class AjaxDigestAuthenticationEntryPoint extends DigestAuthenticationEntryPoint{
    13
     
    14
      @Override
    15
      public void commence(HttpServletRequest request, HttpServletResponse response, 
    16
                 AuthenticationException authException) throws IOException, ServletException {
    17
        super.commence(request, new UnauthorizedHttpResponse(response), authException);
    18
      }
    19
     
    20
      private static class UnauthorizedHttpResponse extends HttpServletResponseWrapper{
    21
        public UnauthorizedHttpResponse(HttpServletResponse response) {
    22
          super(response);
    23
        }
    24
        @Override
    25
        public void sendError(int sc, String msg) throws IOException {
    26
          if(sc == HttpServletResponse.SC_UNAUTHORIZED){
    27
            sc = HttpServletResponse.SC_FORBIDDEN;
    28
          }
    29
          super.sendError(sc, msg);
    30
        }
    31
      }
    32
    }

    Web.xml

     XML |     copy code | ?  
    01
    <?xml version="1.0" encoding="UTF-8"?>
    02
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
    03
             xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    04
             xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    05
             version="2.5">
    06
       <display-name>${project.name} ${project.version} SVN@${buildNumber}</display-name>
    07
       <description>${project.description}</description>
    08
       <context-param>
    09
          <param-name>contextConfigLocation</param-name>
    10
          <param-value>/META-INF/spring/web-application-context.xml</param-value>
    11
       </context-param>
    12
       <context-param>
    13
          <param-name>log4jConfigLocation</param-name>
    14
          <param-value>classpath:log4j.xml</param-value>
    15
       </context-param>
    16
       <!-- ================================================================== -->
    17
     
    18
       <listener>
    19
          <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    20
       </listener>
    21
     
    22
       <!-- ================================================================== -->
    23
     
    24
       <filter>
    25
          <filter-name>characterEncodingFilter</filter-name>
    26
          <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    27
          <init-param>
    28
             <param-name>encoding</param-name>
    29
             <param-value>utf-8</param-value>
    30
          </init-param>
    31
       </filter>
    32
       <filter-mapping>
    33
          <filter-name>characterEncodingFilter</filter-name>
    34
          <url-pattern>/*</url-pattern>
    35
       </filter-mapping>
    36
     
    37
       <filter>
    38
          <filter-name>springSecurityFilterChain</filter-name>
    39
          <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    40
       </filter>
    41
       <filter-mapping>
    42
          <filter-name>springSecurityFilterChain</filter-name>
    43
          <url-pattern>/*</url-pattern>
    44
       </filter-mapping>
    45
     
    46
       <!-- ================================================================== -->
    47
     
    48
        <servlet>
    49
           <servlet-name>springServlet</servlet-name>
    50
           <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    51
           <init-param>
    52
              <param-name>contextConfigLocation</param-name>
    53
              <param-value>/META-INF/spring/servlet-context.xml</param-value>
    54
           </init-param>
    55
           <load-on-startup>1</load-on-startup>
    56
        </servlet>
    57
        <servlet-mapping>
    58
           <servlet-name>springServlet</servlet-name>
    59
           <url-pattern>/</url-pattern>
    60
        </servlet-mapping>
    61
    </web-app>

    No magic in the web.xml. Just typical Spring + Spring Security configuration.

    JavaScript client

    As you can see Spring provides almost everything in server side and if we were using Java as a Rest client we could use RestTemplate to have all the work made for us.  However we want to use JavaScript instead. In order to do that I had to write mechanism that parses incomming WWW-Authenticate header and generate Authorization header according to RFC 2617.

    digest-auth.js:

     Javascript |     copy code | ?  
    01
    /*
    02
     * A JavaScript implementation of the Digest Authentication
    03
     * Digest Authentication, as defined in RFC 2617.
    04
     * Version 1.0 Copyright (C) Maricn Michalski (http://marcin-michalski.pl) 
    05
     * Distributed under the BSD License
    06
     * 
    07
     * site: http://arrowgroup.eu
    08
     */
    09
     
    10
    $.Class("pl.arrowgroup.DigestAuthentication", {
    11
     MAX_ATTEMPTS : 1,
    12
     AUTHORIZATION_HEADER : "Authorization",
    13
     WWW_AUTHENTICATE_HEADER : 'WWW-Authenticate',
    14
     NC : "00000001", //currently nc value is fixed it is not incremented
    15
     HTTP_METHOD : "GET",
    16
     /**
    17
      * settings json:
    18
      *  - onSuccess - on success callback
    19
      *  - onFailure - on failure callback
    20
      *  - username - user name
    21
      *  - password - user password
    22
      *  - cnonce - client nonce
    23
      */
    24
     init : function(settings) {
    25
       this.settings = settings;
    26
     },
    27
     setCredentials: function(username, password){
    28
       this.settings.username = username;
    29
       this.settings.password = password;
    30
     },
    31
     call : function(uri){
    32
       this.attempts = 0;
    33
       this.invokeCall(uri);
    34
     },
    35
     invokeCall: function(uri,authorizationHeader){
    36
       var digestAuth = this;
    37
       $.ajax({
    38
             url: uri,
    39
             type: this.HTTP_METHOD,
    40
             beforeSend: function(request){
    41
              if(typeof authorizationHeader != 'undefined'){
    42
              request.setRequestHeader(digestAuth.AUTHORIZATION_HEADER, authorizationHeader);           
    43
              }
    44
             },
    45
             success: function(response) {
    46
              digestAuth.settings.onSuccess(response);          
    47
             },
    48
             error: function(response) { 
    49
              if(digestAuth.attempts == digestAuth.MAX_ATTEMPTS){
    50
          digestAuth.settings.onFailure(response);
    51
          return;
    52
          }
    53
              var paramParser = new pl.arrowgroup.HeaderParamsParser(response.getResponseHeader(digestAuth.WWW_AUTHENTICATE_HEADER));
    54
              var nonce = paramParser.getParam("nonce");
    55
              var realm = paramParser.getParam("realm");
    56
              var qop = paramParser.getParam("qop");
    57
              var response = digestAuth.calculateResponse(uri, nonce, realm, qop);
    58
              var authorizationHeaderValue = digestAuth.generateAuthorizationHeader(paramParser.headerValue, response, uri); 
    59
              digestAuth.attempts++;
    60
              digestAuth.invokeCall(uri, authorizationHeaderValue);
    61
             }             
    62
         });
    63
     },
    64
     calculateResponse : function(uri, nonce, realm, qop){
    65
       var a2 = this.HTTP_METHOD + ":" + uri;
    66
       var a2Md5 = hex_md5(a2);
    67
       var a1Md5 = hex_md5(this.settings.username + ":" + realm + ":" + this.settings.password);
    68
       var digest = a1Md5 + ":" + nonce + ":" + this.NC + ":" + this.settings.cnonce + ":" + qop + ":" +a2Md5;
    69
       return hex_md5(digest);
    70
     },
    71
     generateAuthorizationHeader : function(wwwAuthenticationHeader, response, uri){
    72
       return wwwAuthenticationHeader+', username="'+this.settings.username+'", uri="'+
    73
          uri+'", response="'+response+'", nc='+
    74
          this.NC+', cnonce="'+this.settings.cnonce+'"';
    75
       }
    76
    });
    77
    $.Class("pl.arrowgroup.HeaderParamsParser",{
    78
     init : function(headerValue) {
    79
       this.headerValue = headerValue;
    80
       this.headerParams = this.headerValue.split(",");
    81
     },
    82
     getParam: function(paramName){
    83
       var paramVal = null;
    84
       $.each(this.headerParams, function(index, value){
    85
         if(value.indexOf(paramName)>0){
    86
         paramVal = value.split(paramName+"=")[1];
    87
         paramVal = paramVal.substring(1, paramVal.length-1);
    88
         }
    89
       });
    90
       return paramVal;
    91
     }
    92
    });

    In order to prepare JavaScript client I had to use external JS library responsible for MD5 calculation.

    Example:
     
    Now when we have both client and server functionality let’s prepare some example.
     
    Server side UserController.java:
     Java |     copy code | ?  
    01
    package pl.arrowgroup.restauth.controllers;
    02
     
    03
    import java.util.Date;
    04
     
    05
    import javax.servlet.http.HttpServletRequest;
    06
    import javax.servlet.http.HttpServletResponse;
    07
     
    08
    import org.springframework.security.core.context.SecurityContextHolder;
    09
    import org.springframework.security.core.userdetails.User;
    10
    import org.springframework.stereotype.Controller;
    11
    import org.springframework.web.bind.annotation.RequestMapping;
    12
    import org.springframework.web.bind.annotation.ResponseBody;
    13
     
    14
     
    15
    @Controller
    16
    @RequestMapping("/user")
    17
    public class UserController {
    18
       @RequestMapping(value="/echo")
    19
       public @ResponseBody String echo(HttpServletRequest request, HttpServletResponse resp){ 
    20
         return "Hello "+((User) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername()+
    21
                " - Current Date is : "+new Date() +" - Visit us at : http://arrowgroup.eu";
    22
       }
    23
    }

    Client side html – test.html

     HTML |     copy code | ?  
    01  
    02
    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
    03
     <head >
    04
     <title>Ajax test call</title>
    05
         <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    06
         <script type="text/javascript" src="../../resources/js/jquery.min.js" ></script>
    07
         <script type="text/javascript" src="../../resources/js/jquery.class.min.js" ></script>
    08
         <script type="text/javascript" src="../../resources/js/md5-min.js" ></script>
    09
         <script type="text/javascript" src="../../resources/js/digest-auth.js" ></script>
    10
     
    11
         <script type="text/javascript">
    12
           var digestAuth = new pl.arrowgroup.DigestAuthentication(
    13
             {
    14
               onSuccess : function(response){
    15
                 $("#response").html(response);
    16
               },
    17
               onFailure : function(response){
    18
                 $("#response").html('Invalid credentials !!!');
    19
               },
    20
               cnonce : 'testCnonce'
    21
             }
    22
           );
    23
           function callREST(){
    24
             digestAuth.setCredentials($('#user').val(),$('#password').val());
    25
             digestAuth.call('/restauth/user/echo');
    26
           }
    27
         </script>
    28
     </head>
    29
     <body>
    30
       <div>
    31
         <h3>Test example of Digest Authentication using Ajax request and Spring Security</h3>  
    32
       </div>
    33
       <div>
    34
         <form >
    35
           <p>User <input id="user" type="text" value="marcin"></p>
    36
           <p>Password <input id="password" type="text" value="michalski"></p>
    37
           <p> <button onclick="callREST(); return false;" >Execute</button> 
    38
         </form>
    39
       </div>
    40
       <div id="response">
    41
       </div>
    42
       <div style="color: gray; font-size: 12px;">
    43
         Copyright 2012: <a href="http://arrowgroup.eu">ArrowGroup</a>, Author: <a href="http://marcin-michalski.pl">Marcin Michalski</a> 
    44
       </div>
    45
     </body>
    46
    </html>

    No when we enter the page and provide incorrect password we will be notified that provided password is incorrect:

    invalid

    And when we enter correct password we are able to access the controller method:

    valid
  • 相关阅读:
    笔记3
    笔记
    指令操作、例子
    python文件操作
    pandas处理excel
    Flask资源
    ImportError: DLL load failed while importing _ssl: 找不到指定的模块。 Failed
    WARNING: pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available.
    selenium 安装与 chromedriver安装
    ubuntu 更换清华源
  • 原文地址:https://www.cnblogs.com/AloneSword/p/4100790.html
Copyright © 2020-2023  润新知