Having taken Bruce Phillips Shiro tutorial’s app and added a login via Facebook option I’m now going to have a bash at implementing a “log in via twitter” option, I then will hopefully be able to step back and see what generlisations might be appropriate to make some of the code more generic between Facebook and Twitter logins, and also genererally for OAuth.
So first Create a Twitter Application
Do this on dev.twitter.com , with an application name, description and website.
There’s also an optional field for a call back URL, I’ve filled this in with the URL of the servlet that will be handling twitter login’s for my application, although twitter does say ” OAuth 1.0a applications should explicitly specify their oauth_callback URL on the request token step, regardless of the value given here”. So I may have to revisit this to determine which URL exactly should be entered.
This gives a consumer key and secret, and 4 URLS (Request token URL, Authorize URL , Access token URL and Callback URL ), so make a note of these, I’m assuming they may come in useful. Also note twitter’s “rules of the road”, which include things like display of a users twitter avatar if you’re using the twitter login mechanism, I’m not sure if Facebook makes the same requirement but if so will have to check.
Get Twitter4j
I’ll be using twitter4j to do a lot of the work, I’m already using it elsewhere, and been very happy so far with it’s ease of use.
http://twitter4j.org/en/index.html
I’ll be using the Sign in Via Twitter example as my guide (EDIT – I pretty much copy it)
http://twitter4j.org/en/code-examples.html#signinwithtwitter
Code
The first thing to do is add a log in via twitter link to our webpage, unlike the facebook one which is a link to facebook, this will be a link to a servlet implemented by our app.
The example above uses the following link.
<a href="TwitterLogin"><img src="./images/Sign-in-with-Twitter-darker.png"/></a>
Taking the Twitter4j example I then ended up with a TwitterLoginServlet (which maps to “TwitterLogin” linked to above) and a TwitterCallbackServlet.
I did find I had to fix the code below to hardcode the URL of the call back servlet when the app was deployed, possibly because I’m using Nginx as a reverse proxy so it was ending up with 127.0.0.1 instead of the host name, maybe I could fix Nginx configuration to avoid this.
Twitter properties is just a convenience class used in this demo to hold the properties given when the app was created, twitter4J would read in properties from a properties file on the class path by default.
package uk.co.mrdw.shiroexp.servlets; import java.io.IOException; import java.util.Properties; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import twitter4j.Twitter; import twitter4j.TwitterException; import twitter4j.TwitterFactory; import twitter4j.auth.RequestToken; import twitter4j.conf.ConfigurationBuilder; public class TwitterLoginServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println( "TwitterLoginServlet:doGet" ); ConfigurationBuilder cb = new ConfigurationBuilder(); Properties props = new TwitterProperties().getProperties(); cb.setDebugEnabled(true) .setOAuthConsumerKey((String)props.get("twitterConsumerKey")) .setOAuthConsumerSecret((String)props.get("twitterConsumerSecret")) .setOAuthRequestTokenURL((String)props.get("twitterRequestTokenURL")) .setOAuthAuthorizationURL((String)props.get("twitterAuthorizeURL")) .setOAuthAccessTokenURL((String)props.get("twitterAccessTokenURL")); TwitterFactory tf = new TwitterFactory(cb.build()); Twitter twitter = tf.getInstance(); request.getSession().setAttribute("twitter", twitter); try { StringBuffer callbackURL = request.getRequestURL(); System.out.println( "TwitterLoginServlet:callbackURL:"+callbackURL ); int index = callbackURL.lastIndexOf("/"); callbackURL.replace(index, callbackURL.length(), "").append("/TwitterCallback"); RequestToken requestToken = twitter.getOAuthRequestToken(callbackURL.toString()); request.getSession().setAttribute("requestToken", requestToken); System.out.println( "requestToken.getAuthenticationURL():"+requestToken.getAuthenticationURL() ); response.sendRedirect(requestToken.getAuthenticationURL()); } catch (TwitterException e) { throw new ServletException(e); } } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse * response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("Unexpected doPost ..."); } }
and the callback servlet (URL it’s mapped to needs to match the callbackURL generated in the login servlet above) …
package uk.co.mrdw.shiroexp.servlets; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import twitter4j.Twitter; import twitter4j.TwitterException; import twitter4j.auth.RequestToken; public class TwitterCallbackServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Twitter twitter = (Twitter) request.getSession().getAttribute("twitter"); RequestToken requestToken = (RequestToken) request.getSession().getAttribute("requestToken"); System.out.println( "TwitterCallbackServlet:requestToken:"+requestToken); String verifier = request.getParameter("oauth_verifier"); try { twitter.getOAuthAccessToken(requestToken, verifier); request.getSession().removeAttribute("requestToken"); } catch (TwitterException e) { throw new ServletException(e); } response.sendRedirect(request.getContextPath() + "/"); } }
This is pretty much a straight copy of the Twitter4J example.
Just to prove it was working I was able to add the following to the index.jsp
<% <%@ page import="twitter4j.Twitter"%> <%@ page import="twitter4j.ProfileImage"%> .... Twitter twitter = (Twitter) request.getSession().getAttribute("twitter"); if(twitter!=null){ String imageUrl = ""; try { imageUrl = twitter.getProfileImage(twitter.getScreenName(), ProfileImage.NORMAL).getURL(); } catch (Exception e1) { // TODO Auto-generated catch block e1.printStackTrace(); } %> <p>Twitter ID:<%=twitter.getId()%></p> <p>Twitter Screen Name:<%=twitter.getScreenName()%></p> <p><img src="<%=imageUrl %>"/> </p> <% } %>
So that’s the basics of a twitter login to a Java web application working – I know it’s not actually logging in via Shiro at them moment, or doing anything useful, that will come ( I’ll also need to check the Twitter “Rules of the Road” before deploying and make sure I’m complying).
Haven’t looked at what there is in common with the Facebook login yet from a point of view of making the Shiro implementation more generic,
but I can see differences:
Unlike the Facebook example this works wherever its deployed – Facebook uses the URL configured for the application to call back to.
The login link on the web page goes to the web app, not to twitter.
Using Twitter4j has hidden a lot of the nuts and bolts, which is good, but may mean less chance of sharing OAuth code between Facebook and Twitter implementations.
Next step login via google I think, then try and put it all together in a Shiro implementation.
Is it possible to use this code to login to some other application via twitter oauth? Application suppose logining using twitter.
Not sure exactly what you’re asking.
If you’re writing an application then you should be able to use this code to allow twitter users to login in to it via twitter, but you can’t use it to login to other peoples applications.
Thank you very much, Mike! This was exactly what I was looking for.