A trick for connecting spring security oauth2 and wso2 api manager
Overview
Wso2 api manager is a popular APIs gateway acting as a facade of a bunch of APIs residing on the certain backend server. API manager can be an independent server connecting the real backend. By leveraging the API manager, the api services are free from the burdens of some non-business logic such as scalability, security and analytics.
More info: http://wso2.com/api-management/try-it/
On the flip side, spring security oauth2 lies in the client side of APIs service, api consumers can naturally build the client based on spring security oauth2 library in that wso2 api manger follows the principles of OAuth2 to secure all API calls.
More info: http://projects.spring.io/spring-security-oauth/
In this article, I will demonstrate the basic steps to bridge a client based on spring-security-oauth2 to the api server side that's governed by the wso2 api manager. Moreover, there is a small trick to ensure the connection in-between really working.
In this article, I will demonstrate the basic steps to bridge a client based on spring-security-oauth2 to the api server side that's governed by the wso2 api manager. Moreover, there is a small trick to ensure the connection in-between really working.
Prerequisite
- download wso2 api manager, http://wso2.com/api-management/try-it/
- unzip package to install it onto target folder.
- follow the instructions to create and publisher a API based on http://httpbin.org/ip (for demo purpose, this api is simple enough. You can use more complicated ones as you want)
- Go to the api manager store to subscribe this api so that you can build client to consume this api.
- Verify api is working via api console.
- records all necessary parameters to fire up api call
1. token URL: https://localhost:8243/token
2. consumer key: 0d6YBfUzZfvDwxlQ768Cjl0voesa <substitute yours for it>
3. consumer secret: wfprQBED0MUiufJnUSTZVOwFxgoa <substitute yours for it>
4. username: admin <substitute yours for it>
5. password: admin <substitute yours for it>
6. API URL: https://localhost:8243/httpbin/1.0/ip
Set up wso2 API manager client underpinned by spring-security-oauth2
The benefits of using spring-security-oauth2 to build client is strait forward. The standard paradigm of OAuth2 requires two steps, firstly, get token from OAuth2 server; secondly, insert token as Authorization Header to all the sequential http API calls. Spring-security-oauth2 is capable of not only encapsulating the whole complexities of OAuth2, but also merging two steps into one step. It significantly mitigates the implementation efforts.
The working client can be demonstrated by this github repository: https://github.com/Bo-Ye/spring-oauth2-wso2 as long as aforementioned prerequisite is established.
The basic steps:
The code is pretty easy to read, first of all, create OAuth2 resource with recorded parameters, then declare OAuth2RestTemplate and fire up corresponding API call, as you can see, the explicit token retrieval is hidden under the hood. However, there are two points worthwhile to mention:
Let's compare the token header from API console and the printed token, the slight difference is the printed token begins with lower case bearer whereas the API console's Bearer is upper case. It turns out that wso2 api manager is case-sensitive of Bearer keyword while other OAuth2 implementations are not case-sensitive for Bearer keyword.
That's it! From this example, we can clearly see the Spring interceptor is a powerful and flexible way to provide diagnostic information as well as the neat solution for customisation of underlying code.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private static final Logger logger = Logger.getLogger(APIManagerClient.class); | |
private final static String TOKEN_URL = "https://localhost:8243/token"; | |
private final static String CLIENT_ID = "0d6YBfUzZfvDwxlQ768Cjl0voesa"; //substitute yours for it | |
private final static String CLIENT_SECRET = "wfprQBED0MUiufJnUSTZVOwFxgoa"; //substitute yours for it | |
private final static String USERNAME = "admin"; //substitute yours for it | |
private final static String PASSWORD = "admin"; //substitute yours for it | |
private final static String API_URL = "https://localhost:8243/httpbin/1.0/ip"; | |
public static void main(String[] args) throws NoSuchAlgorithmException, KeyManagementException { | |
new APIManagerClient().callAPI(); | |
} | |
private void setNoSSL(OAuth2RestTemplate oAuth2RestTemplate) throws KeyManagementException, NoSuchAlgorithmException { | |
//request factory | |
ClientHttpRequestFactory requestFactory = new SSLContextRequestFactory(); | |
oAuth2RestTemplate.setRequestFactory(requestFactory); | |
//provider | |
ResourceOwnerPasswordAccessTokenProvider provider = new ResourceOwnerPasswordAccessTokenProvider(); | |
provider.setRequestFactory(requestFactory); | |
oAuth2RestTemplate.setAccessTokenProvider(provider); | |
} | |
private void setInterceptor(OAuth2RestTemplate oAuth2RestTemplate) { | |
//set interceptors | |
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>(); | |
interceptors.add(new OAuth2RequestInterceptor()); | |
oAuth2RestTemplate.setInterceptors(interceptors); | |
} | |
private void callAPI() throws KeyManagementException, NoSuchAlgorithmException { | |
ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails(); | |
resource.setAccessTokenUri(TOKEN_URL); | |
resource.setClientId(CLIENT_ID); | |
resource.setClientSecret(CLIENT_SECRET); | |
resource.setGrantType("password"); | |
resource.setUsername(USERNAME); | |
resource.setPassword(PASSWORD); | |
//create rest template | |
OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(resource); | |
this.setNoSSL(oAuth2RestTemplate); //to ignore ssl | |
this.setInterceptor(oAuth2RestTemplate); // the critical trick to workaround OAuth2 token issue. | |
String result = oAuth2RestTemplate.getForObject(API_URL, String.class); | |
logger.info("Result is " + result); | |
} |
- This project doesn't use de-facto spring IOC in order to only highlight the OAuth2 logic.
- SSL is ignored for simplicity & demo purpose.
A trick to get around the token issue
Please pay attention to line 40:
Looks like the OAuth2 token didn't take effect because 401 Unauthorized exception is thrown up. In light of issue like this, Spring comes with a very handy mechanism called interceptor that is able to reveal the underlying nuts and bolts of each http request, therefore, we can add interceptor to see what's happening in request headers, body, etc. The following shows information after interception:
this.setInterceptor(oAuth2RestTemplate); // the critical trick to workaround OAuth2 token issue.Technically, this is not mandatory, however, without this line, the running result is following:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2015-12-03 22:38:29 DEBUG ResourceOwnerPasswordAccessTokenProvider:159 - Retrieving token from https://localhost:8243/token | |
2015-12-03 22:38:29 DEBUG RestTemplate:78 - Created POST request for "https://localhost:8243/token" | |
2015-12-03 22:38:29 DEBUG ResourceOwnerPasswordAccessTokenProvider:221 - Encoding and sending form: {grant_type=[password], username=[admin], password=[admin]} | |
2015-12-03 22:38:29 DEBUG RestTemplate:579 - POST request for "https://localhost:8243/token" resulted in 200 (OK) | |
2015-12-03 22:38:29 DEBUG HttpMessageConverterExtractor:92 - Reading [interface org.springframework.security.oauth2.common.OAuth2AccessToken] as "application/json" using [org.springframework.http.converter.json.MappingJacksonHttpMessageConverter@258e2e41] | |
2015-12-03 22:38:29 DEBUG OAuth2RestTemplate:78 - Created GET request for "https://localhost:8243/httpbin/1.0/ip" | |
2015-12-03 22:38:29 DEBUG OAuth2RestTemplate:679 - Setting request Accept header to [text/plain, application/json, application/*+json, */*] | |
2015-12-03 22:38:29 WARN OAuth2RestTemplate:591 - GET request for "https://localhost:8243/httpbin/1.0/ip" resulted in 401 (Unauthorized); invoking error handler | |
Exception in thread "main" org.springframework.web.client.HttpClientErrorException: 401 Unauthorized | |
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91) | |
at org.springframework.security.oauth2.client.http.OAuth2ErrorHandler.handleError(OAuth2ErrorHandler.java:165) | |
at org.springframework.web.client.RestTemplate.handleResponseError(RestTemplate.java:598) | |
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:556) | |
at org.springframework.security.oauth2.client.OAuth2RestTemplate.doExecute(OAuth2RestTemplate.java:128) | |
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:512) | |
at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:243) | |
at com.boye.spring.APIManagerClient.callAPI(APIManagerClient.java:58) | |
at com.boye.spring.APIManagerClient.main(APIManagerClient.java:26) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2015-12-03 22:50:54 DEBUG OAuth2RequestInterceptor:27 - Authorization : bearer 79fdcbcfc3ea51b6c0edea9f1f448cb | |
2015-12-03 22:50:54 DEBUG OAuth2RequestInterceptor:27 - Accept : text/plain, application/json, application/*+json, */* | |
2015-12-03 22:50:54 DEBUG OAuth2RequestInterceptor:27 - Content-Length : 0 |
That's it! From this example, we can clearly see the Spring interceptor is a powerful and flexible way to provide diagnostic information as well as the neat solution for customisation of underlying code.
Comments
Post a Comment