Secure Spring REST API使用OAuth2

Spring REST API 這一次使用的是 OAuth2,這篇文章簡單介紹在一個 REST API 中使用 Spring OAuth2 需要什麼。我們將使用兩個不同的客戶端[Postman和基於Java應用程序的Spring RestTemplate]來訪問OAuth2保護的REST資源。

如果你已經熟悉 OAuth2 概念,那麼您可以直接跳過理論部分,直接進入代碼實現。與往常一樣,完整的代碼可以在本文的末尾處下載。

其它你可能會喜歡的文章:

OAuth2是什麼?

OAuth2用戶是一個標準化的授權協議/框架。按照官方的 OAuth2定義

OAuth 2.0授權框架使第三方應用程序來獲取對HTTP服務的有限訪問機會。無論是通過編排資源所有者和HTTP服務之間的交互批准的資源所有者,或通過允許第三方應用程序來獲取自己的訪問權限。

大牌玩家像谷歌,Facebook和其他公司已經開始使用自己的OAuth2相當一段時間了。其它企業也正向着使用OAuth2的步伐快速移動。

Spring Security OAuth項目提供所有可能開發使用的Spring OAuth2用戶兼容實現所需的API。 Official Spring security oauth項目提供了實現 OAuth2 一個完整的例子。這個篇文章的代碼示例是在這個官方提供的例子的基礎上修改。這篇文章的目的是隻使用所需最低限度的功能,以演示我們的REST API,僅此而已。我也還在學習,所以如果有什麼不對的地方隨時糾正我。

至少你應該知道 OAuth2 的四個關鍵概念:

1. OAuth2角色

在OAuth2中用戶定義了四個角色:

  • 資源擁有乾(resource owner):

    能夠准許訪問受保護資源的實體。當資源的所有者是一個人,它被稱爲終端用戶。

  • 資源服務器(resource server):

    服務器託管受保護的資源,能夠接受和響應使用訪問令牌保護資源的請求。

  • 客戶端(client):

    應用程序使資源所有者的請求有授權訪問受保護資源。這可能是一個移動應用程序要求您的權限來訪問您的Facebook訂閱源,REST客戶端試圖訪問REST API,

    如一個網站[Stackoverflow]提供使用Facebook帳戶或是類似QQ第三方帳號登錄來替代使用網站帳號登錄。

  • authorization server:

    服務器在成功認證資源所有者和獲得授權之後發出訪問令牌給客戶端。

在我們的例子中,REST API只能通過資源服務器,這個請求訪問需要一個訪問令牌。

2.授權OAuth2給予類型

授權給予是代表資源所有者的授權(訪問其受保護的資源),用於客戶端以獲得訪問令牌的憑證。該規範定義了四種類型的給予:

  • 授權碼

  • 隱性的

  • 資源所有者密碼憑據

  • 客戶端憑據

我們將使用資源所有者密碼憑據授予類型。原因很簡單,我們沒有執行那些重定向到一個登錄頁面視圖。

只有使用了客戶端[Postman或基於RestTemplate的Java客戶端]有資源所有者的憑證,他們提供這些憑證到授權服務器[客戶端和憑據一起]以最終獲得訪問令牌[和可選刷新令牌]

然後使用該令牌來訪問資源。

一個常見的例子是Gmail應用[客戶]在智能手機登錄,它需要您的憑證,並用它們連接到Gmail服務器。

這也表明,"密碼憑據給予"是最適合在當客戶端和服務器來自同一家公司的信任是存在的,

而不想提供您的憑證給第三方。

3.OAuth2令牌

令牌是實現指定隨機字符串,由授權服務器生成,並在客戶端請求時將它們發出。

  • Access Token : 發送的每個請求,有效期一般是一個很短的壽命[例如:一個小時]
  • Refresh Token : 主要用於獲取新的訪問令牌,而不是每個請求都發送,通常比訪問令牌生命更長。

在HTTPS個詞 : 對於任何形式的安全實現,從基本身份驗證到一個完全成熟的OAuth2實現,HTTPS是必須具備的。如果沒有HTTPS,不管你的實現是什麼,安全性都是容易受到損害的。

4. OAuth2用戶訪問令牌範圍

客戶端可以要求使用範圍指定訪問權限的資源[訪問訂閱與用戶Facebook賬戶照片],授權服務器顯示的訪問權限實際上是授予客戶端[只允許資源所有者訂閱]。

讓我們來看看代碼


讓我們來使用Spring Security實現必要安全過濾,才能進入REST資源來實現OAuth。

1.資源服務器

資源服務器承載資源[REST API],客戶端感興趣的資源位於  /user/ 。@EnableResourceServer註釋,適用在OAuth2資源服務器,實現了Spring Security的過濾器驗證的請求傳入OAuth2令牌。 ResourceServerConfigurerAdapter類實現 ResourceServerConfigurer 提供的方法來調整 OAuth2安全保護的訪問規則和路徑。

package com.yiibai.springmvc.security;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler;

@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

private static final String RESOURCE\_ID = "my\_rest\_api";

@Override
public void configure(ResourceServerSecurityConfigurer resources) {
    resources.resourceId(RESOURCE\_ID).stateless(false);
}

@Override
public void configure(HttpSecurity http) throws Exception {
    http.
    anonymous().disable()
    .requestMatchers().antMatchers("/user/\*\*")
    .and().authorizeRequests()
    .antMatchers("/user/\*\*").access("hasRole('ADMIN')")
    .and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}

}

2.授權服務器

如果憑據 OK 授權服務器是一個負責驗證憑據,提供令牌[刷新令牌以及訪問令牌]。它還包含有關注冊客戶和訪問範圍以及授權類型的信息。 令牌存儲用於存儲令牌。我們將使用內存來存儲令牌。@EnableAuthorizationServer使一個授權服務器(即,AuthorizationEndpoint和TokenEndpoint)在當前的應用程序上下文。

AuthorizationServerConfigurerAdapter類實現AuthorizationServerConfigurer它提供了所有必要的方法來配置一個授權服務器。

package com.yiibai.springmvc.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.approval.UserApprovalHandler;
import org.springframework.security.oauth2.provider.token.TokenStore;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

private static String REALM="MY\_OAUTH\_REALM";

@Autowired
private TokenStore tokenStore;

@Autowired
private UserApprovalHandler userApprovalHandler;

@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

    clients.inMemory()
        .withClient("my-trusted-client")
        .authorizedGrantTypes("password", "authorization\_code", "refresh\_token", "implicit")
        .authorities("ROLE\_CLIENT", "ROLE\_TRUSTED\_CLIENT")
        .scopes("read", "write", "trust")
        .secret("secret")
        .accessTokenValiditySeconds(120).//Access token is only valid for 2 minutes.
        refreshTokenValiditySeconds(600);//Refresh token is only valid for 10 minutes.
}

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    endpoints.tokenStore(tokenStore).userApprovalHandler(userApprovalHandler)
            .authenticationManager(authenticationManager);
}

@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
    oauthServer.realm(REALM+"/client");
}

}

上面的配置:

  • 註冊一個客戶端,客戶端ID是「my-trusted-client'和密碼爲'secret',客戶端允許的角色和範圍;

  • 指定任何生成的訪問令牌的有效期只有120秒;

  • 指定任何刷新生成令牌的有效期只有600秒

3.安全配置

結合一切在一起。端點 /oauth/token 用於請求令牌[訪問或刷新]。資源所有者[bill,bob] 在這裏配置。

package com.yiibai.springmvc.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.TokenApprovalStore;
import org.springframework.security.oauth2.provider.approval.TokenStoreUserApprovalHandler;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;

@Configuration
@EnableWebSecurity
public class OAuth2SecurityConfiguration extends WebSecurityConfigurerAdapter {

@Autowired
private ClientDetailsService clientDetailsService;

@Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
    .withUser("bill").password("abc123").roles("ADMIN").and()
    .withUser("bob").password("abc123").roles("USER");
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
    .csrf().disable()
    .anonymous().disable()
      .authorizeRequests()
      .antMatchers("/oauth/token").permitAll();
}

@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
}


@Bean
public TokenStore tokenStore() {
    return new InMemoryTokenStore();
}

@Bean
@Autowired
public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore){
    TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
    handler.setTokenStore(tokenStore);
    handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));
    handler.setClientDetailsService(clientDetailsService);
    return handler;
}

@Bean
@Autowired
public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception {
    TokenApprovalStore store = new TokenApprovalStore();
    store.setTokenStore(tokenStore);
    return store;
}

}

此外,使用全局安全方法,則可激活@PreFilter,@PostFilter,@ PreAuthorize@PostAuthorize註釋。

package com.yiibai.springmvc.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler;

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Autowired
private OAuth2SecurityConfiguration securityConfig;

@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
    return new OAuth2MethodSecurityExpressionHandler();
}

}

4.端點和它們的目的

5. Rest API

簡單的Spring REST API,在前面教程文章中我們使用的那樣。

package com.yiibai.springmvc.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;

import com.yiibai.springmvc.model.User;
import com.yiibai.springmvc.service.UserService;

@RestController
public class HelloWorldRestController {

@Autowired
UserService userService;  //Service which will do all data retrieval/manipulation work


//-------------------Retrieve All Users--------------------------------------------------------

@RequestMapping(value = "/user/", method = RequestMethod.GET)
public ResponseEntity<List<User>> listAllUsers() {
    List<User> users = userService.findAllUsers();
    if(users.isEmpty()){
        return new ResponseEntity<List<User>>(HttpStatus.NO\_CONTENT);//You many decide to return HttpStatus.NOT\_FOUND
    }
    return new ResponseEntity<List<User>>(users, HttpStatus.OK);
}


//-------------------Retrieve Single User--------------------------------------------------------

@RequestMapping(value = "/user/{id}", method = RequestMethod.GET, produces = {MediaType.APPLICATION\_JSON\_VALUE,MediaType.APPLICATION\_XML\_VALUE})
public ResponseEntity<User> getUser(@PathVariable("id") long id) {
    System.out.println("Fetching User with id " + id);
    User user = userService.findById(id);
    if (user == null) {
        System.out.println("User with id " + id + " not found");
        return new ResponseEntity<User>(HttpStatus.NOT\_FOUND);
    }
    return new ResponseEntity<User>(user, HttpStatus.OK);
}



//-------------------Create a User--------------------------------------------------------

@RequestMapping(value = "/user/", method = RequestMethod.POST)
public ResponseEntity<Void> createUser(@RequestBody User user, UriComponentsBuilder ucBuilder) {
    System.out.println("Creating User " + user.getName());

    if (userService.isUserExist(user)) {
        System.out.println("A User with name " + user.getName() + " already exist");
        return new ResponseEntity<Void>(HttpStatus.CONFLICT);
    }

    userService.saveUser(user);

    HttpHeaders headers = new HttpHeaders();
    headers.setLocation(ucBuilder.path("/user/{id}").buildAndExpand(user.getId()).toUri());
    return new ResponseEntity<Void>(headers, HttpStatus.CREATED);
}


//------------------- Update a User --------------------------------------------------------

@RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)
public ResponseEntity<User> updateUser(@PathVariable("id") long id, @RequestBody User user) {
    System.out.println("Updating User " + id);

    User currentUser = userService.findById(id);

    if (currentUser==null) {
        System.out.println("User with id " + id + " not found");
        return new ResponseEntity<User>(HttpStatus.NOT\_FOUND);
    }

    currentUser.setName(user.getName());
    currentUser.setAge(user.getAge());
    currentUser.setSalary(user.getSalary());

    userService.updateUser(currentUser);
    return new ResponseEntity<User>(currentUser, HttpStatus.OK);
}

//------------------- Delete a User --------------------------------------------------------

@RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
public ResponseEntity<User> deleteUser(@PathVariable("id") long id) {
    System.out.println("Fetching & Deleting User with id " + id);

    User user = userService.findById(id);
    if (user == null) {
        System.out.println("Unable to delete. User with id " + id + " not found");
        return new ResponseEntity<User>(HttpStatus.NOT\_FOUND);
    }

    userService.deleteUserById(id);
    return new ResponseEntity<User>(HttpStatus.NO\_CONTENT);
}


//------------------- Delete All Users --------------------------------------------------------

@RequestMapping(value = "/user/", method = RequestMethod.DELETE)
public ResponseEntity<User> deleteAllUsers() {
    System.out.println("Deleting All Users");

    userService.deleteAllUsers();
    return new ResponseEntity<User>(HttpStatus.NO\_CONTENT);
}

}

6.運行應用程序

運行它,並使用兩種不同的客戶端進行測試。

客戶端1: Postman

嘗試不使用任何驗證信息來直拉訪問資源: http://localhost:8080/SpringSecurityOAuth2/user/,將得到401。

Secure

現在我們獲取頭。選擇HTTP方法爲 POST,Authorization Type:Basic Auth ,URL:http://localhost:8080/SpringSecurityOAuth2/oauth/token?grant_type=password&username=bill&password=abc123 ,然後再將客戶端憑據 [my-trusted-client/secret]添加到授權頭。點擊"update request"(更新請求),發送POST請求後,您會在響應中收到訪問令牌(access-token),以及刷新令牌(refresh-token)。如下所示 -  

Secure

保存這些令牌在需要它們時。現在可以使用這個訪問令牌[有效期爲2分鐘]來訪問資源。現在我們再使用這個 token 來訪問資源,把它添加到URL中如:http://localhost:8080/SpringSecurityOAuth2/user/?access_token=7fbb77ae-3d8f-4d78-b8de-3222353f680b 得到結果如下所示 -

Secure

2分鐘後,訪問令牌被過期,那麼進一步的資源請求將失敗。

Secure

我們需要一個新的訪問令牌。觸發一個 post 以後用刷新令牌來獲得一個新的訪問令牌。請求URL:http://localhost:8080/SpringSecurityOAuth2/oauth/token?grant_type=refresh_token&refresh_token=fefcf12c-2683-4f1a-a446-941666dcfe23

Secure

使用這個新的訪問令牌(c8edfa2f-d2aa-4f1b-81e1-32df3fefe9a8)繼續訪問資源。把它添加到URL中如:http://localhost:8080/SpringSecurityOAuth2/user/?access_token=be5c7dec-ae17-403d-ab66-86cf5262f159 得到結果如下所示 -

Secure

新令牌(Refresh-token)也會過期[10分鐘]。在這之後,您會看到刷新請求失敗。

Secure

這意味着您需要刷新申請新的訪問令牌,如第2步中。

客戶端2:基於RestTemplate的Java應用程序

sendTokenRequest 方法用於獲得實際令牌。訪問令牌(access-token )我們從響應中獲得了,之後將它應用到每個請求中。如果需要,您可以在下面的例子中很容易地實現 refresh-token 流程。

package com.yiibai.springmvc;

import java.net.URI;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;

import org.apache.commons.codec.binary.Base64;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.web.client.RestTemplate;

import com.yiibai.springmvc.model.AuthTokenInfo;
import com.yiibai.springmvc.model.User;

public class SpringRestClient {

public static final String REST\_SERVICE\_URI = "http://localhost:8080/SpringSecurityOAuth2";

public static final String AUTH\_SERVER\_URI = "http://localhost:8080/SpringSecurityOAuth2/oauth/token";

public static final String QPM\_PASSWORD\_GRANT = "?grant\_type=password&username=bill&password=abc123";

public static final String QPM\_ACCESS\_TOKEN = "?access\_token=";

/\*
 \* Prepare HTTP Headers.
 \*/
private static HttpHeaders getHeaders(){
    HttpHeaders headers = new HttpHeaders();
    headers.setAccept(Arrays.asList(MediaType.APPLICATION\_JSON));
    return headers;
}

/\*
 \* Add HTTP Authorization header, using Basic-Authentication to send client-credentials.
 \*/
private static HttpHeaders getHeadersWithClientCredentials(){
    String plainClientCredentials="my-trusted-client:secret";
    String base64ClientCredentials = new String(Base64.encodeBase64(plainClientCredentials.getBytes()));

    HttpHeaders headers = getHeaders();
    headers.add("Authorization", "Basic " + base64ClientCredentials);
    return headers;
}    

/\*
 \* Send a POST request \[on /oauth/token\] to get an access-token, which will then be send with each request.
 \*/
@SuppressWarnings({ "unchecked"})
private static AuthTokenInfo sendTokenRequest(){
    RestTemplate restTemplate = new RestTemplate(); 

    HttpEntity<String> request = new HttpEntity<String>(getHeadersWithClientCredentials());
    ResponseEntity<Object> response = restTemplate.exchange(AUTH\_SERVER\_URI+QPM\_PASSWORD\_GRANT, HttpMethod.POST, request, Object.class);
    LinkedHashMap<String, Object> map = (LinkedHashMap<String, Object>)response.getBody();
    AuthTokenInfo tokenInfo = null;

    if(map!=null){
        tokenInfo = new AuthTokenInfo();
        tokenInfo.setAccess\_token((String)map.get("access\_token"));
        tokenInfo.setToken\_type((String)map.get("token\_type"));
        tokenInfo.setRefresh\_token((String)map.get("refresh\_token"));
        tokenInfo.setExpires\_in((int)map.get("expires\_in"));
        tokenInfo.setScope((String)map.get("scope"));
        System.out.println(tokenInfo);
        //System.out.println("access\_token ="+map.get("access\_token")+", token\_type="+map.get("token\_type")+", refresh\_token="+map.get("refresh\_token")
        //+", expires\_in="+map.get("expires\_in")+", scope="+map.get("scope"));;
    }else{
        System.out.println("No user exist----------");

    }
    return tokenInfo;
}

/\*
 \* Send a GET request to get list of all users.
 \*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private static void listAllUsers(AuthTokenInfo tokenInfo){
    Assert.notNull(tokenInfo, "Authenticate first please......");

    System.out.println("\\nTesting listAllUsers API-----------");
    RestTemplate restTemplate = new RestTemplate(); 

    HttpEntity<String> request = new HttpEntity<String>(getHeaders());
    ResponseEntity<List> response = restTemplate.exchange(REST\_SERVICE\_URI+"/user/"+QPM\_ACCESS\_TOKEN+tokenInfo.getAccess\_token(),
            HttpMethod.GET, request, List.class);
    List<LinkedHashMap<String, Object>> usersMap = (List<LinkedHashMap<String, Object>>)response.getBody();

    if(usersMap!=null){
        for(LinkedHashMap<String, Object> map : usersMap){
            System.out.println("User : id="+map.get("id")+", Name="+map.get("name")+", Age="+map.get("age")+", Salary="+map.get("salary"));;
        }
    }else{
        System.out.println("No user exist----------");
    }
}

/\*
 \* Send a GET request to get a specific user.
 \*/
private static void getUser(AuthTokenInfo tokenInfo){
    Assert.notNull(tokenInfo, "Authenticate first please......");
    System.out.println("\\nTesting getUser API----------");
    RestTemplate restTemplate = new RestTemplate();
    HttpEntity<String> request = new HttpEntity<String>(getHeaders());
    ResponseEntity<User> response = restTemplate.exchange(REST\_SERVICE\_URI+"/user/1"+QPM\_ACCESS\_TOKEN+tokenInfo.getAccess\_token(),
            HttpMethod.GET, request, User.class);
    User user = response.getBody();
    System.out.println(user);
}

/\*
 \* Send a POST request to create a new user.
 \*/
private static void createUser(AuthTokenInfo tokenInfo) {
    Assert.notNull(tokenInfo, "Authenticate first please......");
    System.out.println("\\nTesting create User API----------");
    RestTemplate restTemplate = new RestTemplate();
    User user = new User(0,"Sarah",51,134);
    HttpEntity<Object> request = new HttpEntity<Object>(user, getHeaders());
    URI uri = restTemplate.postForLocation(REST\_SERVICE\_URI+"/user/"+QPM\_ACCESS\_TOKEN+tokenInfo.getAccess\_token(),
            request, User.class);
    System.out.println("Location : "+uri.toASCIIString());
}

/\*
 \* Send a PUT request to update an existing user.
 \*/
private static void updateUser(AuthTokenInfo tokenInfo) {
    Assert.notNull(tokenInfo, "Authenticate first please......");
    System.out.println("\\nTesting update User API----------");
    RestTemplate restTemplate = new RestTemplate();
    User user  = new User(1,"Tomy",33, 70000);
    HttpEntity<Object> request = new HttpEntity<Object>(user, getHeaders());
    ResponseEntity<User> response = restTemplate.exchange(REST\_SERVICE\_URI+"/user/1"+QPM\_ACCESS\_TOKEN+tokenInfo.getAccess\_token(),
            HttpMethod.PUT, request, User.class);
    System.out.println(response.getBody());
}

/\*
 \* Send a DELETE request to delete a specific user.
 \*/
private static void deleteUser(AuthTokenInfo tokenInfo) {
    Assert.notNull(tokenInfo, "Authenticate first please......");
    System.out.println("\\nTesting delete User API----------");
    RestTemplate restTemplate = new RestTemplate();
    HttpEntity<String> request = new HttpEntity<String>(getHeaders());
    restTemplate.exchange(REST\_SERVICE\_URI+"/user/3"+QPM\_ACCESS\_TOKEN+tokenInfo.getAccess\_token(),
            HttpMethod.DELETE, request, User.class);
}


/\*
 \* Send a DELETE request to delete all users.
 \*/
private static void deleteAllUsers(AuthTokenInfo tokenInfo) {
    Assert.notNull(tokenInfo, "Authenticate first please......");
    System.out.println("\\nTesting all delete Users API----------");
    RestTemplate restTemplate = new RestTemplate();
    HttpEntity<String> request = new HttpEntity<String>(getHeaders());
    restTemplate.exchange(REST\_SERVICE\_URI+"/user/"+QPM\_ACCESS\_TOKEN+tokenInfo.getAccess\_token(),
            HttpMethod.DELETE, request, User.class);
}

public static void main(String args\[\]){
    AuthTokenInfo tokenInfo = sendTokenRequest();
    listAllUsers(tokenInfo);

    getUser(tokenInfo);

    createUser(tokenInfo);
    listAllUsers(tokenInfo);

    updateUser(tokenInfo);
    listAllUsers(tokenInfo);

    deleteUser(tokenInfo);
    listAllUsers(tokenInfo);

    deleteAllUsers(tokenInfo);
    listAllUsers(tokenInfo);
}

}

上面的代碼會產生以下的輸出:

AuthTokenInfo [access_token=fceed386-5923-4bf8-b193-1d76f95da4c4, token_type=bearer, refresh_token=29d28ee2-9d09-483f-a2d6-7f93e7a31667, expires_in=71, scope=read write trust]

Testing listAllUsers API-----------
User : id=1, Name=Sam, Age=30, Salary=70000.0
User : id=2, Name=Tom, Age=40, Salary=50000.0
User : id=3, Name=Jerome, Age=45, Salary=30000.0
User : id=4, Name=Silvia, Age=50, Salary=40000.0

Testing getUser API----------
User [id=1, name=Sam, age=30, salary=70000.0]

Testing create User API----------
Location : http://localhost:8080/SpringSecurityOAuth2Example/user/5

Testing listAllUsers API-----------
User : id=1, Name=Sam, Age=30, Salary=70000.0
User : id=2, Name=Tom, Age=40, Salary=50000.0
User : id=3, Name=Jerome, Age=45, Salary=30000.0
User : id=4, Name=Silvia, Age=50, Salary=40000.0
User : id=5, Name=Sarah, Age=51, Salary=134.0

Testing update User API----------
User [id=1, name=Tomy, age=33, salary=70000.0]

Testing listAllUsers API-----------
User : id=1, Name=Tomy, Age=33, Salary=70000.0
User : id=2, Name=Tom, Age=40, Salary=50000.0
User : id=3, Name=Jerome, Age=45, Salary=30000.0
User : id=4, Name=Silvia, Age=50, Salary=40000.0
User : id=5, Name=Sarah, Age=51, Salary=134.0

Testing delete User API----------

Testing listAllUsers API-----------
User : id=1, Name=Tomy, Age=33, Salary=70000.0
User : id=2, Name=Tom, Age=40, Salary=50000.0
User : id=4, Name=Silvia, Age=50, Salary=40000.0
User : id=5, Name=Sarah, Age=51, Salary=134.0

Testing all delete Users API----------

Testing listAllUsers API-----------
No user exist----------

工程目錄結構

Secure

Secure
pom.xml


4.0.0

<groupId>com.yiibai.springmvc</groupId>
<artifactId>SpringSecurityOAuth2</artifactId>
<version>1.0.0</version>
<packaging>war</packaging>

<name>SpringSecurityOAuth2Example</name>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <springframework.version>4.3.1.RELEASE</springframework.version>
    <springsecurity.version>4.1.1.RELEASE</springsecurity.version>
    <springsecurityoauth2.version>2.0.10.RELEASE</springsecurityoauth2.version>
    <jackson.library>2.7.5</jackson.library>
</properties>

<dependencies>
    <!-- Spring -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${springframework.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>${springframework.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>${springframework.version}</version>
    </dependency>

    <!-- Spring Security -->
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-web</artifactId>
        <version>${springsecurity.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-config</artifactId>
        <version>${springsecurity.version}</version>
    </dependency>

    <!-- Spring Security OAuth2-->
    <dependency>
        <groupId>org.springframework.security.oauth</groupId>
        <artifactId>spring-security-oauth2</artifactId>
        <version>${springsecurityoauth2.version}</version>
    </dependency>

    <!-- Jackson libraries -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>${jackson.library}</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.dataformat</groupId>
        <artifactId>jackson-dataformat-xml</artifactId>
        <version>${jackson.library}</version>
    </dependency>

    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
    </dependency>
</dependencies>

<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.2</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>            
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.4</version>
                <configuration>
                    <warSourceDirectory>src/main/webapp</warSourceDirectory>
                    <warName>SpringSecurityOAuth2</warName>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    <finalName>SpringSecurityOAuth2</finalName>
</build>

下載源代碼

16 - SpringSecurityOAuth2.zip

參考