
This phrase welcomes us to the library for working with OAuth - ScribeJava (
https://github.com/scribejava/scribejava ). To be precise, the phrase is: “Who said OAuth / OAuth2 was difficult? Configuring ScribeJava is
so easy your grandma can do it! check it out: ”.
And it really seems to be true:
OAuth20Service service = new ServiceBuilder().apiKey(clientId).apiSecret(clientSecret) .callback("http://your.site.com/callback").grantType("authorization_code").build(HHApi.instance()); String authorizationUrl = service.getAuthorizationUrl(); OAuth2AccessToken accessToken = service.getAccessToken(code);
Done! These three lines are enough to start making OAuth requests. And the OAuth request itself can be done like this:
OAuthRequest request = new OAuthRequest(Verb.GET, "https://api.hh.ru/me", service); service.signRequest(accessToken, request); String response = request.send().getBody();
User data is in our hands (in the response variable). And not a bit of insight into how OAuth works in detail. Want asynchronous http requests? We have enough of the same three lines. Below we take this example.
1. OAuth? What is it about?
Most of the sites in one form or another need to register users: for comments, tracking orders, vacancy responses - it does not matter.
')
At the same time, people on the Internet usually show lazy behavior and do not like to fill in registration forms, especially if they have already done this somewhere. OAuth comes to the rescue of such sites. However, the article is not about the OAuth protocol itself, so we will talk about how to work with OAuth, without going into details and the mechanism of its work.
In a nutshell, OAuth was created to give authorization to a third-party server (site) to receive any data from another resource (for example, social network). Ie, for example, a Vkontakte user using OAuth can give a right to a certain site (for example, hh.ru) to request its data or to perform any actions on the Vkontakte network on its behalf. It should be noted that OAuth was not created to identify the user. However, among other things, we can almost always request data “about ourselves”, thus obtaining the user id and identifying it.
If you try to describe OAuth step by step, you’ll get something like this (using OAuth2 as an example - it is simpler).
- We register our application on a third-party site, we get client_id and client_secret - this is done once.
- When a user comes to us, our site forms a link to a third-party site where we want to get authorization to receive data about the user. The link necessarily contains the client_id of our application on this site. Further, our site gives the user this link.
- The user follows the link to a third-party site, logs in (if not already logged in), approves the rights requested by us (for example, receiving his full name) and returns to us with the additional GET parameter 'code'.
- Our site directly (server-server) sends the received GET parameter to a third-party site and receives a token (access_token) in response.
- We make requests to receive data or perform any activities on behalf of the user, and we add this access_token to each request.
2. Even your grandma can use OAuth
Let's try to make out in more detail an example from the beginning of the article by making an OAuth request on hh.ru. To do this, we need to create an OAuthService using the builder ServiceBuilder. This line of code will look like this:
OAuth20Service service = new ServiceBuilder() .apiKey(clientId) .apiSecret(clientSecret) .callback("http://your.site.com/callback") .grantType("authorization_code") .build(HHApi.instance());
You will only need to substitute your application's clientId and clientSecret, which you can get by registering a new application at
https://dev.hh.ru . You will also need to specify a callback url where the user will be redirected to with the code we need (code).
String authorizationUrl = service.getAuthorizationUrl();
We send the user to this address. In our case, it will look something like this:
hh.ru/oauth/authorize?response_type=code&client_id=UHKBSA...&redirect_uri=https%3A%2F%2Fhhid.ru%2Foauth2%2FcodeIf you open this address in the browser, the user will see the login form, then the form of issuing rights to the application. If he is already logged in and / or granted rights to the application earlier, he will immediately be redirected to the callback we specified. In our case, this:
hhid.ru/oauth2/code?code=I2R6O5 ...
This is the GET parameter 'code' we need. Change it to a token:
String code = "I2R6O5..."; OAuth2AccessToken accessToken = service.getAccessToken(code);
That's all! We have a token (OAuth2AccessToken accessToken), if we bring it to the console, we will see the insides:
OAuth2AccessToken { access_token=I55KQQ..., token_type=bearer, expires_in=1209599, refresh_token=PGELQV..., scope=null}
Now let's try to get some data. Create a query:
OAuthRequest request = new OAuthRequest(Verb.GET, "https://api.hh.ru/me", service);
Sign the request with a token:
service.signRequest(accessToken, request);
We send a request to hh.ru:
Response response = request.send();
Print the result:
System.out.println(response.getCode()); System.out.println(response.getBody());
Profit! In the console we will see something like this:
200 {"first_name": "", "last_name": "", "middle_name": null, "is_in_search": null, "is_anonymous": false, "resumes_url": null, "is_employer": false, "personal_manager": null, "email": "s.gromov@hh.ru", "manager": null, ...}
And we did not have to learn what parameters need to be transferred, how to do it, how to encrypt them, in what order to transfer, and many other nuances of both the OAuth protocol itself and its specific implementation from HeadHunter.
ps. The full code of the example being run can be seen here:
https://github.com/scribejava/scribejava/blob/master/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/HHExample.javaThe library is released in maven central, so it will be very easy to connect it to the project:
<dependency> <groupId>com.github.scribejava</groupId> <artifactId>scribejava-apis</artifactId> <version>2.3.0</version> </dependency>
If you have very strict requirements on the size of the final application, then you can take only the core part, without a collection of different APIs:
<dependency> <groupId>com.github.scribejava</groupId> <artifactId>scribejava-core</artifactId> <version>2.3.0</version> </dependency>
3.scribe-java -> SubScribe -> ScribeJava or how to fork and return the debt to opensource community
We received user data that can be used both for registering a new user and for authenticating the old one. This may be necessary not only for new sites that are too lazy to bother with registration, but also for old, already mature, such as hh.ru. It is with these thoughts that we entered 2013.

It should be noted that, despite the existence of relatively good specifications of the OAuth protocol, as is usually the case, each server has invented and invents its own modifications. Somewhere freely interpreting standards, somewhere taking advantage of the freedom provided by the specifications, and somewhere going against them for the sake of some of their ideas.
At hh.ru, we wanted to add inputs at once through several different social networks, and, of course, we wanted to write code that works with each of them to a minimum. Surely someone has already written everything! At a minimum, a list of addresses to which you need to send requests (and in fact a bunch of small nuances). In addition, I would like to keep the written code to a minimum, and, if necessary, when, for example, the social network decides to change the URL for which you need to go for the token, simply update the library version.
We studied the options that existed at that time, and it turned out that the easiest to use and, at the same time, the largest database of APIs that the library would work out of the box, was scribe-java from
https://github.com/fernandezpablo85/ . At that time, she had a few, but not all of the APIs, which we wanted. Well, not scary, you can add the missing and give them to the public.
From this we began. But having written our first PullRequest on a githaba, we learned that the author was already “tired” of such Pull Revests and answered us with a prepared article on the wiki that he would not add new APIs ;-( According to the author, scribe-java should to be a small simple library (possibly without OAuth2 at all, leaving only the first version of the protocol), and we wanted to have all the true “library” properties from it, a collection of all addresses of different APIs. Well, it's not a big deal if there’s something the library is not satisfied, you can always make a fork! Thus the project was born SubScribe t. With a heading of five points that indicated the main reasons for creating our fork:
Main reasons of fork here:
1.https: //github.com/fernandezpablo85/scribe-java/wiki/Scribe-scope-revised
2.We really think, OAuth2.0 should be here;
3.We really think, async http should be here for a high-load projects;
4.We really think, all APIs should be here. With all their specific stuff. It is easier to change the programmer;
5. Scribe should be multi-maven-module project. Core and APIs should not be deployed as separated artifacts.
©
https://github.com/hhru/subscribe/blob/a8450ec2ed35ecaa64ef03afc1bd077ce14d8d61/README.mdIn addition to the previously described, of the reasons for creating the fork, there are several more. Being one of the most loaded sites of the runet and the job sites of Europe, we really wanted to be able to work asynchronously. And it was not included in the plans of a simple original library. We also decided to eliminate the author’s “fear” that the library would become cumbersome due to the abundance of specific features of an individual specific API. We divided the project into two modules. After some gestures, on March 3rd, 2014, the first version of SubScribe (immediately 2.0) appeared in the central repository of Maven
http://central.maven.org/maven2/ru/hh/oauth/subscribe/subscribe/2.0/ . Where the project existed before version 3.4, released on June 30th, 2015. During this time, having gained a bit of his own popularity and new features, new APIs, he does not forget to backport all the goodies from the parent scribe-java.
It would have remained so, if at the beginning of autumn 2015 Pablo Fernandez (
https://github.com/fernandezpablo85 ), apparently, finally tired of his creation, would not have stumbled upon our fork. Pablo said that he was impressed and sees a lot of things that he would like to do himself, but didn’t get around, and offered to work out the details of the transfer of the project to us completely. Having got a little shaken up for decency, we accepted the offer, and so ScribeJava appeared - in effect renamed back the SubScribe fork. Since then, the library has a separate organization on github.com -
https://github.com/scribejava .
At the moment, ScribeJava is an open source project under the wing hh.ru. Included in the list of client libraries in java on the main page of the official website of the OAuth2 protocol:
http://oauth.net/2/ . It has 280 observers, 3,106 stars and 1,220 forks on github.com.
4. Add asynchrony and update the token using the example of working with Google
If you have a heavily loaded site and you want to save threads and / or just use the ning http client, then we can ask ScribeJava to use the asynchronous version of the work. For this you need to have ning http client in your classpath
<dependency> <groupId>com.ning</groupId> <artifactId>async-http-client</artifactId> <version>1.9.32</version> </dependency>
This time we will use the Asynchronous builder of the ServiceBuilderAsync service.
OAuth20Service service = new ServiceBuilderAsync() .apiKey(clientId) .apiSecret(clientSecret) .scope("profile")
The only difference here is the call to the asyncHttpClientConfig (clientConfig) method, to which we must give the config for the asynchronous ning http client. For example, let it be this:
AsyncHttpClientConfig clientConfig = new AsyncHttpClientConfig.Builder() .setMaxConnections(5) .setRequestTimeout(10_000) .setAllowPoolingConnections(false) .setPooledConnectionIdleTimeout(1_000) .setReadTimeout(1_000) .build();
Google also requires a state variable. It is necessary to protect against CRSF attacks, but this is beyond the interest of our article. Further work is no different from the example of working with api.hh.ru, discussed at the beginning. We send the user to the address:
String authorizationUrl = service.getAuthorizationUrl();
And for each method with HTTP campaign we add the postfix 'Async' inside. Those. instead of the getAccessToken method, we will call the getAccessTokenAsync method.
Future<OAuth2AccessToken> accessTokenFuture = service.getAccessTokenAsync("code", null);
In response, we get Future (asynchrony after all). Or we can optionally pass the second argument Callback, as we prefer.
Done! Simple, isn't it? Now you can send asynchronous requests (OAuthRequestAsync) to Google on behalf of the user:
OAuth2AccessToken accessToken = accessTokenFuture.get(); OAuthRequestAsync request = new OAuthRequestAsync(Verb.GET, "https://www.googleapis.com/plus/v1/people/me", service); service.signRequest(accessToken, request); Response response = request.sendAsync(null).get(); System.out.println(response.getCode()); System.out.println(response.getBody());
In the resulting OAuthRequestAsync, we called the sendAsync method, which, by analogy, optionally expects a Callback and returns us the Future. At the same time, we still have the opportunity to send synchronous requests in parallel with asynchronous. If we want to somehow asynchrony (or synchrony) requests, you can ask ScribeJava to do it through a static “configurator”:
ScribeJavaConfig.setForceTypeOfHttpRequests(ForceTypeOfHttpRequest.FORCE_ASYNC_ONLY_HTTP_REQUESTS);
In this case, when you try to use the synchronous version of ScribeJava, we will get an Exception. Other options are possible, for example, not throwing Exception, but logging about each such case. Or vice versa to require only synchronous work.
Consider here another useful OAuth point - refresh_token. The fact is that the access_token we get has a limited lifespan. And when it gets rotten, we need to get a new token. There are two options: either wait for the user and once again guide him through the whole mechanism, or use refresh_token (not all support it, but Google, by the example of which we will try it, supports it). So, to get a fresh access_token, all we need is just:
OAuth2AccessToken refreshedAccessToken accessToken = service.refreshAccessToken(accessToken.getRefreshToken());
or for the asynchronous option:
Future<OAuth2AccessToken> refreshedAccessTokenFuture = service.refreshAccessTokenAsync(accessToken.getRefreshToken(), null);
It is worth noting that in the case of Google, the refresh_token that needs to be passed to the refreshAccessToken method will not come unless specifically asked for it. To do this, you need to create an address to which the user will add a couple of parameters:
ps. This and other examples in the run form (with the static main method) are here:
https://github.com/scribejava/scribejava/tree/master/scribejava-apis/src/test/java/com/github/scribejava/apis/examples5. Useful links
1.ScribeJava on github.com
https://github.com/scribejava/scribejava2. documentation api.hh.ru
https://github.com/hhru/api3. Google documentation
https://developers.google.com/identity/protocols/OAuth2WebServer4.RFC OAuth2
http://tools.ietf.org/html/rfc67495.javadoc online
http://www.javadoc.io/doc/com.github.scribejava/scribejava-corePost Scriptum
Comments are very welcome. Especially in the form of pull requests on github.com