天天看點

使用Spring Security和OAuth2實作RESTful服務安全認證

* 使用者注冊和登入

* Email驗證

* Password 丢失

采取的技術有以下:

* OAuth2 Protocol 

* Spring Integration 

* Spring Data 

* Jersey/JAX-RS 

* Gradle / Groovy 

通過以下方式建構項目:

> cd oauth2-provider 

> ./gradlew clean build integrationTest

運作Web項目:

這個應用是基于MongoDB作為持久層,在運作應用之前确認mongod是運作在端口27017.

運作指令:

> ./gradlew tomcatRun

1. 建立一個使用者:

curl -v -X POST \

   -H "Content-Type: application/json" \

   -H "Authorization: Basic MzUzYjMwMmM0NDU3NGY1NjUwNDU2ODdlNTM0ZTdkNmE6Mjg2OTI0Njk3ZTYxNWE2NzJhNjQ2YTQ5MzU0NTY0NmM=" \

   -d '{"user":{"emailAddress":"[email protected]"}, "password":"password"}' \

   'http://localhost:8080/oauth2-provider/v1.0/users'

結果應該是:

{"apiUser":

   {"emailAddress":"[email protected]",

   "firstName":null,

   "lastName":null,

   "age":null,

   "id":"8a34d009-3558-4c8c-a8da-1ad2b2a393c7",

   "name":"[email protected]"},

   "oauth2AccessToken":

   {"access_token":"7e0e4708-7837-4a7e-9f87-81c6429b02ac",

   "token_type":"bearer", 

   "refresh_token":"d0f248ab-e30f-4a85-860c-bd1e388a39b5",

   "expires_in":5183999,

   "scope":"read write"

   }

}

2. 請求一個access token:

   'http://localhost:8080/oauth2-provider/oauth/token?grant_type=password&[email protected]&password=password'

{

  "access_token":"a838780e-35ef-4bd5-92c0-07a45aa74948",

  "token_type":"bearer",

  "refresh_token":"ab06022f-247c-450a-a11e-2ffab116e3dc",

  "expires_in":5183999

3. 重新整理一個token:

   'http://localhost:8080/oauth2-provider/oauth/token?grant_type=refresh_token&refresh_token=ab06022f-247c-450a-a11e-2ffab116e3dc'

   "access_token":"4835cd11-8bb7-4b76-b857-55c6e7f36fc4",

   "token_type":"bearer",

   "refresh_token":"ab06022f-247c-450a-a11e-2ffab116e3dc",

   "expires_in":5183999

一個Jersey 處理所有資源調用:

<servlet-mapping>  

        <servlet-name>jersey-servlet</servlet-name>  

        <url-pattern>/*</url-pattern>  

</servlet-mapping>  

Spring servlet處理所有oauth 調用:

        <servlet-name>spring</servlet-name>  

        <url-pattern>/oauth/*</url-pattern>  

spring security配合定義一個過濾器:

<filter>  

    <filter-name>springSecurityFilterChain</filter-name>  

    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  

    <init-param>  

        <param-name>contextAttribute</param-name>  

        <param-value>org.springframework.web.servlet.FrameworkServlet.CONTEXT.spring</param-value>  

    </init-param>  

</filter>  

對根目錄下所有url進行 過濾:

<filter-mapping>  

    <url-pattern>/*</url-pattern>  

</filter-mapping>  

<oauth:authorization-server client-details-service-ref="client-details-service" token-services-ref="tokenServices">  

        <oauth:refresh-token/>  

        <oauth:password/>  

    </oauth:authorization-server>  

預設的token端點是/oauth/token ,隻有 password flow 和重新整理 token 支援。

使用Spring security 保護token端點:

<http pattern="/oauth/token" create-session="stateless" authentication-manager-ref="clientAuthenticationManager"  

      xmlns="http://www.springframework.org/schema/security">  

    <anonymous enabled="false"/>  

    <http-basic entry-point-ref="clientAuthenticationEntryPoint"/>  

    <access-denied-handler ref="oauthAccessDeniedHandler"/>  

</http>  

下面配置授權authentication 管理器和用戶端服務:

<bean id="clientCredentialsTokenEndpointFilter"  

      class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">  

    <property name="authenticationManager" ref="clientAuthenticationManager"/>  

</bean>  

<authentication-manager id="clientAuthenticationManager" xmlns="http://www.springframework.org/schema/security">  

    <authentication-provider user-service-ref="client-details-user-service"/>  

</authentication-manager>  

<bean id="client-details-user-service" class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">  

    <constructor-arg ref="client-details-service" />  

Resource Owner Password flow 需要管理使用者的授權管理器

<bean id="passwordEncoder" class="org.springframework.security.crypto.password.StandardPasswordEncoder"/>  

<sec:authentication-manager alias="userAuthenticationManager">  

    <sec:authentication-provider user-service-ref="userService">  

        <sec:password-encoder ref="passwordEncoder"/>  

    </sec:authentication-provider>  

</sec:authentication-manager>  

密碼 password encoder是用于加密密碼。使用者服務必須實作一個UserDetailsService ,能根據使用者名傳回使用者。

@Override  

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {  

    notNull(username, "Mandatory argument 'username' missing.");  

    User user = userRepository.findByEmailAddress(username.toLowerCase());  

    if (user == null) {  

        throw new AuthenticationException();  

    }  

    return user;  

}  

<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="client-details-service"/>  

<oauth:resource-server id="resourceServerFilter" token-services-ref="tokenServices"/>  

這個服務提供基于通路token獲得使用者的資訊。URL格式:

/v1.0/users/{id}/someresource 

@Path("/v1.0/me")  

@Component  

@Produces({MediaType.APPLICATION_JSON})  

@Consumes({MediaType.APPLICATION_JSON})  

public class MeResource extends BaseResource {  

    @RolesAllowed({"ROLE_USER"})  

    @GET  

    public ApiUser getUser(final @Context SecurityContext securityContext) {  

        User requestingUser = loadUserFromSecurityContext(securityContext);  

        if(requestingUser == null) {  

            throw new UserNotFoundException();  

        }  

        return new ApiUser(requestingUser);  

    protected User loadUserFromSecurityContext(SecurityContext securityContext) {  

        OAuth2Authentication requestingUser = (OAuth2Authentication) securityContext.getUserPrincipal();  

        Object principal = requestingUser.getUserAuthentication().getPrincipal();  

        User user = null;  

        if(principal instanceof User) {  

            user = (User)principal;  

        } else {  

            user = userRepository.findByEmailAddress((String)principal);  

        return user;  

測試:

curl -v -X GET \

  -H "Content-Type: application/json" \

  -H "Authorization: Bearer [your token here]" \

  'http://localhost:8080/oauth2-provider/v1.0/me'

參考:https://github.com/tcompiegne/couchbase-token-store-spring-oauth2

            https://github.com/tcompiegne/oauth2-server-spring-couchbase

轉自:http://www.jdon.com/dl/best/securing-rest-services-with-spring.html.html