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.端點和它們的目的

  • 試圖存取資源[REST API],但是這是在沒有任何授權[失敗]情況下。
    GET http://localhost:8080/SpringSecurityOAuth2Example/user/
  • 要求令牌[存取+重新整理]使用HTTP POST 在 /oauth/token 上 ,grant_type=password和資源所有者憑證作為  req-params 。此外在授權頭傳送用戶端憑據。

    POST http://localhost:8080/SpringSecurityOAuth2Example/oauth/token?grant_type=password&username=bill&password=abc123

  • 要求通過有效的重新整理令牌新的存取令牌,在 /oauth/token 上使用HTTP POST,以及 grant_type=refresh_token 一併行送重新整理令牌。此外在授權頭傳送用戶端憑據。

    POST http://localhost:8080/SpringSecurityOAuth2Example/oauth/token?grant_type=refresh_token&refresh_token=094b7d23-973f-4cc1-83ad-8ffd43de1845

  • 使用 access_token 查詢引數和要求提供存取資源的令牌。
    GET http://localhost:8080/SpringSecurityOAuth2Example/user/?access_token=3525d0e4-d881-49e7-9f91-bcfd18259109

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。

現在我們獲取頭。選擇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)。如下所示 -  


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

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

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

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

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

這意味著您需要重新整理申請新的存取令牌,如第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----------

工程目錄結構




pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

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

下載原始碼

參考