使用Spring Security Oauth2完成RESTful服務password認證的過程

          摘要:Spring Security與Oauth2整合步驟中詳細描述了使用過程 ,但它對於入門者有些重量級,好比將用戶信息、ClientDetails、token存入數據庫而非內存 。配置過程比較複雜,通過幾天時間試驗終於成功,下面我將具體的使用 Spring Security Oauth2完成password認證的過程記錄下來與你們分享。
        關鍵字: HTTP Authentication, rest, spring security , spring mvc
        前提:IntelliJ IDEA ( 13.1.5 版本),  apache maven  3.2.3 版本),  Tomcat(7.0.56版本), Spring(3.2.4版本),  spring-security-oauth2(2.0.7 版本

 
 
1、首先須要使用Spring MVC完成RESTful API的發佈,這一步驟的詳細狀況可見個人另外一博文:應用Spring MVC 發佈restful服務是怎樣的一種體驗
2、在/webapp/WEB-INF/web.xml文件中添加相應的filter: org.springframework.web.filter.DelegatingFilterProxy mapping ,具體如如下所示。
<?xml version= "1.0"  encoding= "UTF-8" ?>
<web-app xmlns= "http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation= "http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version= "3.1" >
    <filter>
        <filter- name >springSecurityFilterChain</filter- name >
        <filter- class >org.springframework.web.filter.DelegatingFilterProxy</filter- class >
    </filter>
    <filter-mapping>
        <filter- name >springSecurityFilterChain</filter- name >
        < url -pattern>/*</ url -pattern>
    </filter-mapping>

    <context- param >
        < param - name >contextConfigLocation</ param - name >
        < param - value >
            /WEB-INF/security.xml
        </ param - value >
    </context- param >
    <listener>
        <listener- class >org.springframework.web.context.ContextLoaderListener</listener- class >
    </listener>

    <servlet>
        <servlet- name >restful</servlet- name >
        <servlet- class >org.springframework.web.servlet.DispatcherServlet</servlet- class >
        <load-on-startup> 1 </load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet- name >restful</servlet- name >
        < url -pattern>/</ url -pattern>
    </servlet-mapping>
</web-app>
3、在上一步的web.xml文件中能夠看到,Spring須要加載 /WEB-INF/下的security.xml文件,所以咱們在 /WEB-INF/下建立 security.xml文件,其主要內容以下所示。這裏比 使用Spring Security完成RESTful服務用戶認證中的 security.xml文件配置要複雜得多(見我以前的博文: 使用Spring Security完成RESTful服務用戶認證的過程 )。注意,這裏的配置文件中的  <security:http pattern= "/abcs/**" <security:intercept- url  pattern= "/abcs/**"  access= "ROLE_ABCS" />  access必須指定一個角色名稱,使用  use-expressions= "true"  isAuthenticated() 這裏是不容許的。所以,咱們須要在 實現UserDetails接口、實現 getAuthorities()方法時返回 此角色名稱,在擁有此角色的用戶認證經過後,才能夠訪問/abcs/**資源。
<?xml version= "1.0" encoding= "UTF-8" ?>
<beans xmlns= "http://www.springframework.org/schema/beans"
       xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
       xmlns:oauth2= "http://www.springframework.org/schema/security/oauth2"
       xmlns:mvc= "http://www.springframework.org/schema/mvc"
       xmlns:security= "http://www.springframework.org/schema/security"
       xsi:schemaLocation= "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd http://www.springframework.org/schema/security/oauth2  http://www.springframework.org/schema/security/spring-security-oauth2.xsd" >

    <mvc:annotation-driven/>
    <mvc:default-servlet-handler/>

    <bean id = "tokenStore" class = "org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore" />
    <bean id = "tokenServices" class = "org.springframework.security.oauth2.provider.token.DefaultTokenServices" >
        <property name = "tokenStore" ref= "tokenStore" />
        <property name = "supportRefreshToken" value = "true" />
        <!--<property name="clientDetailsService" ref="clientDetailsService"/>-->
    </bean>
    <bean id = "clinetAuthenticationEntryPoint"
          class = "org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint" />
    <bean id = "accessDeniedHandler"
          class = "org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler" />
    <bean id = "userApprovalHandler"
          class = "org.springframework.security.oauth2.provider.approval.DefaultUserApprovalHandler" />

    <!--client-->
    <bean id = "clientDetailsService" class = "com.anqi.dp.controllers.MyClientDetailsService" />
    <bean id = "clientDetailsUserDetailsService"
          class = "org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService" >
        <constructor-arg ref= "clientDetailsService" />
    </bean>
    <bean id = "clientCredentialsTokenEndpointFilter"
          class = "org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter" >
        <property name = "authenticationManager" ref= "clientAuthenticationManager" />
    </bean>
    <security:authentication-manager id = "clientAuthenticationManager" >
        <security:authentication-provider user-service-ref= "clientDetailsUserDetailsService" />
    </security:authentication-manager>
    <oauth2:authorization-server client-details-service-ref= "clientDetailsService" token-services-ref= "tokenServices"
                                 user-approval-handler-ref= "userApprovalHandler" >
        <oauth2:authorization- code />
        <oauth2:implicit/>
        <oauth2:refresh-token/>
        <oauth2:client-credentials/>
        <oauth2:password/>
    </oauth2:authorization-server>
    <security:http pattern= "/oauth/token" create-session= "stateless"
                   authentication-manager-ref= "clientAuthenticationManager" >
        <security:anonymous enabled = "false" />
        <security:http-basic entry-point-ref= "clinetAuthenticationEntryPoint" />
        <security:custom-filter ref= "clientCredentialsTokenEndpointFilter" before= "BASIC_AUTH_FILTER" />
        <security:access-denied-handler ref= "accessDeniedHandler" />
    </security:http>
     <!--client-->
     <!--user-->
    <bean id = "userService" class = "com.anqi.dp.controllers.UserService" />
    <security:authentication-manager alias= "authenticationManager" >
        <security:authentication-provider user-service-ref= "userService" >
            <!--<security:password-encoder hash="md5"/>-->
        </security:authentication-provider>
    </security:authentication-manager>
     <!--user-->

    <oauth2:resource-server id = "mobileResourceServer" resource- id = "mobile-resource" token-services-ref= "tokenServices" />
    <bean id = "accessDecisionManager" class = "org.springframework.security.access.vote.UnanimousBased" >
        <constructor-arg>
            <list>
                <bean class = "org.springframework.security.oauth2.provider.vote.ScopeVoter" />
                <bean class = "org.springframework.security.access.vote.RoleVoter" />
                <bean class = "org.springframework.security.access.vote.AuthenticatedVoter" />
            </list>
        </constructor-arg>
    </bean>
    <security:http pattern= "/abcs/**" create-session= "never" entry-point-ref= "clinetAuthenticationEntryPoint"
                   access-decision-manager-ref= "accessDecisionManager" >
        <security:anonymous enabled = "false" />
        <security:intercept- url pattern= "/abcs/**" access= "ROLE_ABCS" />
        <security:custom-filter ref= "mobileResourceServer" before= "PRE_AUTH_FILTER" />
        <security:access-denied-handler ref= "accessDeniedHandler" />
    </security:http>

</beans>
4、固然要新建com.anqi.dp.UserService類了,其在 security.xml文件中配置其爲 authenticationManager authentication-provider。 com.anqi.dp.UserService類實現自UserDetailsService接口,它須要實現一loadUserByUsername方法,在實現此方法的過程當中,又須要新建MyUserDetails類來實現UserDetails接口。實現 loadUserByUsername方法時,能夠本身依須要從關係數據庫、NoSQL 或者其它存放用戶信息的地方獲取。示例代碼能夠查看 以前的博文: 使用Spring Security完成RESTful服務用戶認證的過程  圖1 UserService示例及用戶名密碼登錄 。注意,由於配了 access= "ROLE_ABCS" ,所以須要   在擁有相應角色的用戶 getAuthorities()方法內返回 此角色名稱: SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ROLE_ABCS"); 。
還要新建com.anqi.dp.MyClientDetailsService類,其 security.xml文件中配置其爲 clientAuthenticationManager authentication-provider。 com.anqi.dp.MyClientDetailsService類實現自 ClientDetailsService接口 ,它須要實現一loadClientByClientId方法,在實現此方法的過程當中,又須要新建MyClientDetails類來實現ClientDetails接口。實現 loadClientByClientId 方法時,能夠本身依須要從關係數據庫、NoSQL 或者其它存放客戶端信息的地方獲取。示例代碼能夠查看 圖1  MyClientDetailsService 示例及token獲取。注意, 這裏的 getAuthorities()方法對 配的 access= "ROLE_ABCS"  沒有影響。
 
  圖1  MyClientDetailsService 示例及token獲取
5、通過以上的步驟,咱們就能夠進行RESTful服務發佈了,發佈成功後,須要進行用戶認證的試驗。
一、如圖1  MyClientDetailsService 示例及token獲取 所示,咱們使用REST Client工具對  http://127.0.0.1:8088/restfulservice/oauth/token  路徑發出POST請求,其中須要在Request Parameters中添加client_id、client_secret、grant_type與user_name、password鍵值對。 如此, 即進行了模擬的經過用戶名密碼獲取token的過程 。圖2中是client認證失敗時的Response,我將client_secret更改後的結果。圖3是user認證失敗時的 Response,我將password更改後的結果。 圖4是認證成功時的 Response。能夠看出,在認證成功時的 Response中存在access_token字段,這就是咱們獲取到的token
 
 
圖2 client認證失敗
 
圖3 user認證失敗
 
 
 
圖4 認證成功
 
 
二、咱們在認證成功的條件下,使用上面步驟中返回的 access_token   http://localhost:8088/restfulservice/abcs/6?access_token=a7f3e13e-cbb0-417d-a9f8-9764d11db00f   進行 GET 請求,便可以成功獲得返回結果, 我是使用瀏覽器進行HTTP請求的 。(具體的邏輯使用Spring MVC的Control完成,見我以前的博文:)。在調試時,能夠看到每次請求,進入對應Controller後,代碼均會轉入 UserDetails的String getUsername()方法中。
若是對請求路徑裏的 access_token值 稍做修改,如再   http://localhost:8088/restfulservice/abcs/6?access_token=a7f3e13e-cbb0-417d-a9f8-9764d11db008   進行 GET 請求,則返回不到正確結果,如圖5所示,即返回Invalid access token錯誤。
 
圖5  帶正確的access_token值請求返回的結果
 
 
 
圖6 帶不正確的access_token值請求返回的結果
 
 
若是刪除access_token,不帶 access_token值對 http://localhost:8088/restfulservice/abcs/6  進行GET請求時,返回的錯誤信息如圖7所示。
 
圖7 不帶 access_token值請求返回的結果
 
 
這就說明,咱們的用戶認證配置達到了預期效果。
 

最近有各類以前沒有碰到過的問題、技術,有時間整理好分享給你們。

html

相關文章
相關標籤/搜索