camel-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From conflue...@apache.org
Subject [CONF] Apache Camel > Tutorial-OAuth
Date Wed, 14 Apr 2010 05:24:00 GMT
<html>
<head>
    <base href="http://cwiki.apache.org/confluence">
            <link rel="stylesheet" href="/confluence/s/1520/1/1/_/styles/combined.css?spaceKey=CAMEL&amp;forWysiwyg=true" type="text/css">
    </head>
<body style="background-color: white" bgcolor="white">
<div id="pageContent">
<div id="notificationFormat">
<div class="wiki-content">
<div class="email">
     <h2><a href="http://cwiki.apache.org/confluence/display/CAMEL/Tutorial-OAuth">Tutorial-OAuth</a></h2>
     <h4>Page <b>edited</b> by             <a href="http://cwiki.apache.org/confluence/display/~mrt1nz">Martin Krasser</a>
    </h4>
     
          <br/>
     <div class="notificationGreySide">
         <h2><a name="Tutorial-OAuth-CamelOAuthTutorial"></a>Camel OAuth Tutorial</h2>

<h3><a name="Tutorial-OAuth-Overview"></a>Overview</h3>

<p>Goal of this tutorial is to demonstrate how to implement an <a href="http://code.google.com/apis/accounts/docs/OAuth.html" rel="nofollow">OAuth</a> consumer with Apache Camel. In this tutorial, the OAuth consumer is a simple web application running on Google App Engine. It reads data from a user's Google Calendar i.e. it displays the names of the user's public and private calendars. The OAuth-based authorization process that allows the application to access a user's calendars is implemented using Camel's <a href="/confluence/display/CAMEL/gauth" title="gauth">gauth</a> component. The application is accessible online at <a href="http://gauthcloud.appspot.com/oauth/calendar" rel="nofollow">http://gauthcloud.appspot.com/oauth/calendar</a> (<a href="#Tutorial-OAuth-Deployment">later</a>, it will be explained how to build and deploy the application yourself). Play with it by following these steps:</p>

<ul>
	<li>Goto the application's main page at <a href="http://gauthcloud.appspot.com/oauth/calendar" rel="nofollow">http://gauthcloud.appspot.com/oauth/calendar</a>. If you haven't used the application before, you'll see a message that no OAuth access token is available. <br clear="all" /> <br clear="all" /> <img src="/confluence/download/attachments/16416889/gauth-01.png" align="absmiddle" border="1" /> <br clear="all" /> <br clear="all" /></li>
</ul>


<ul>
	<li>Follow the link next to this message to start the OAuth authorization process. You will be redirected to Google Accounts. A message is shown that a third party application is requesting access permissions. In order to grant (or deny) access, login to your Google account. This step may be skipped by Google if you already logged into Google Accounts before. <br clear="all" /> <br clear="all" /> <img src="/confluence/download/attachments/16416889/gauth-02.png" align="absmiddle" border="1" /> <br clear="all" /> <br clear="all" /></li>
</ul>


<ul>
	<li>After login, Google displays a message that <tt>gauthcloud.appspot.com</tt> wants to access your Google Calendar. Press the <tt>Grant access</tt> button to continue. No worries, the sample application will only read the names of your public and private Google calendars. No further reads or updates are made to your calendars. If you don't want <tt>gauthcloud.appspot.com</tt> to access your calendar, press <tt>Deny access</tt>. <br clear="all" /> <br clear="all" /> <img src="/confluence/download/attachments/16416889/gauth-03.png" align="absmiddle" border="1" /> <br clear="all" /> <br clear="all" /></li>
</ul>


<ul>
	<li>If you presses <tt>Grant access</tt>, Google redirects you back to the application that now displays the names of your public and private calendars. The following screenshot shows the list of <a href="http://people.apache.org/~krasserm" rel="nofollow">my</a> calendars (some of them have German names). Using OAUth, the web application gained access to your calendar data without ever having seen your Google username and password. These have been entered at a Google site directly. <br clear="all" /> <br clear="all" /> <img src="/confluence/download/attachments/16416889/gauth-04.png" align="absmiddle" border="1" /> <br clear="all" /> <br clear="all" /></li>
</ul>


<ul>
	<li>The result of a successful OAuth authorization process is an OAuth access token that is issued to the web application. The application stores this token for the duration of one hour. However, you can invalidate the access token at any time by either following the link below the calendar list or by going directly to <a href="https://www.google.com/accounts/IssuedAuthSubTokens" rel="nofollow">https://www.google.com/accounts/IssuedAuthSubTokens</a>. This will display a list of applications for which you granted access to your Google account. Among these, there's also an entry for <tt>gauthcloud.appspot.com</tt>. Click <tt>Revoke Access</tt> to invalidate the access token immediately. <br clear="all" /> <br clear="all" /> <img src="/confluence/download/attachments/16416889/gauth-05.png" align="absmiddle" border="1" /> <br clear="all" /> <br clear="all" /></li>
</ul>


<ul>
	<li>If you go back to the application or reload the page with the calendar list, a message is shown that the OAuth access token is invalid. Follow the OAuth authorization process again to renew the token. <br clear="all" /> <br clear="all" /> <img src="/confluence/download/attachments/16416889/gauth-06.png" align="absmiddle" border="1" /> <br clear="all" /> <br clear="all" /></li>
</ul>


<div class='panelMacro'><table class='infoMacro'><colgroup><col width='24'><col></colgroup><tr><td valign='top'><img src="/confluence/images/icons/emoticons/information.gif" width="16" height="16" align="absmiddle" alt="" border="0"></td><td><b>Standalone web applications</b><br /><p>Camel's OAuth support is not limited to web applications running on Google App Engine. It works for standalone web applications as well as long as they are accessible from the internet so that Google can make OAuth callbacks.</p></td></tr></table></div>

<h3><a name="Tutorial-OAuth-Architecture"></a>Architecture</h3>

<p>The following figure sketches the architcture of the distributed web application and an example sequence of interactions.</p>

<p><img src="/confluence/download/attachments/16416889/gauth-arch-01.png" align="absmiddle" border="0" /></p>

<h4><a name="Tutorial-OAuth-Applicationcomponents"></a>Application components</h4>

<table class='confluenceTable'><tbody>
<tr>
<th class='confluenceTh'>Component</th>
<th class='confluenceTh'>Description</th>
</tr>
<tr>
<td class='confluenceTd'>OAuth demo application</td>
<td class='confluenceTd'>A Spring MVC-based web application with a single controller (<tt>TutorialController</tt>) and a facade for accessing the Google calendar service (<tt>TutorialService</tt>). The <tt>TutorialController</tt> selects views for either displaying calendar data or error messages.</td>
</tr>
<tr>
<td class='confluenceTd'>OAuth integration layer</td>
<td class='confluenceTd'>A Camel-based integration layer for doing all the OAuth-specific interactions with Google Accounts.</td>
</tr>
<tr>
<td class='confluenceTd'>Google Accounts</td>
<td class='confluenceTd'>The <a href="http://code.google.com/apis/accounts/" rel="nofollow">Google Accounts</a> service.</td>
</tr>
<tr>
<td class='confluenceTd'>Google Calendar</td>
<td class='confluenceTd'>The <a href="http://code.google.com/apis/calendar/" rel="nofollow">Google Calendar</a> service.</td>
</tr>
</tbody></table>

<h4><a name="Tutorial-OAuth-Sequenceofactions"></a>Sequence of actions</h4>

<table class='confluenceTable'><tbody>
<tr>
<th class='confluenceTh'>Step</th>
<th class='confluenceTh'>Description</th>
</tr>
<tr>
<td class='confluenceTd'>1</td>
<td class='confluenceTd'>The user navigates to the main page of the demo application. The <tt>TutorialController</tt> detects that there's no OAuth access token available and renders a corresponding error message including a link for initiating an OAuth authorization process. The link is targeted at an HTTP endpoint implemented by the OAuth integration layer.</td>
</tr>
<tr>
<td class='confluenceTd'>2a</td>
<td class='confluenceTd'>The user triggers the authorization process by following that link. </td>
</tr>
<tr>
<td class='confluenceTd'>2b</td>
<td class='confluenceTd'>The integration layer obtains an unauthorized request token from the Google Accounts API</td>
</tr>
<tr>
<td class='confluenceTd'>3</td>
<td class='confluenceTd'>It then redirects the user to a Google Accounts web page for login.</td>
</tr>
<tr>
<td class='confluenceTd'>4</td>
<td class='confluenceTd'>The user logs in and grants <tt>gauthcloud.appspot.com</tt> access to his Google calendar.</td>
</tr>
<tr>
<td class='confluenceTd'>5a</td>
<td class='confluenceTd'>Google redirects the user back to the OAUth integration layer together with an authorized request token.</td>
</tr>
<tr>
<td class='confluenceTd'>5b</td>
<td class='confluenceTd'>The integration layer upgrades the token to an OAuth access token and stores this token in a cookie. The cookie expiration time is set to one hour.</td>
</tr>
<tr>
<td class='confluenceTd'>6</td>
<td class='confluenceTd'>The user is redirected to the <tt>TutorialController</tt>.</td>
</tr>
<tr>
<td class='confluenceTd'>7a</td>
<td class='confluenceTd'>The <tt>TutorialController</tt> can now obtain a valid access token from the cookie and uses it to obtain the user's calendar data (via the <tt>TutorialService</tt>).</td>
</tr>
<tr>
<td class='confluenceTd'>7b</td>
<td class='confluenceTd'>After having obtained the calendar data, the <tt>TutorialController</tt> selects a view for rendering the list of calendar names.</td>
</tr>
</tbody></table>

<div class='panelMacro'><table class='noteMacro'><colgroup><col width='24'><col></colgroup><tr><td valign='top'><img src="/confluence/images/icons/emoticons/warning.gif" width="16" height="16" align="absmiddle" alt="" border="0"></td><td><b>Storage of access tokens</b><br /><p>In production systems it is <b>not</b> recommended to store access tokens in cookies. The recommended approach is to store them in a database. The demo application is only doing that to keep the example as simple as possible. However, an attacker could not use an access token alone to get access to a user's calendar data because the application's consumer secret is necessary for that as well. The consumer secret never leaves the demo application.</p></td></tr></table></div>

<h3><a name="Tutorial-OAuth-Deployment"></a>Deployment</h3>

<p>This section explains how to build and deploy the web application yourself.</p>

<h4><a name="Tutorial-OAuth-Prerequisites"></a>Prerequisites</h4>

<ul>
	<li><a href="https://appengine.google.com/" rel="nofollow">Sign up</a>  for a Google App Engine account if you don't have one.</li>
	<li>Create a new application via the <a href="https://appengine.google.com/" rel="nofollow">admin console</a> or reuse an existing one for uploading the example.</li>
	<li>Optional: <a href="http://code.google.com/apis/accounts/docs/RegistrationForWebAppsAuto.html" rel="nofollow">register</a> the application for use with Google's OAuth. After registration you should have access to a <em>consumer key</em> and a <em>consumer secret</em>. If you decide not to registere your application, use <tt>anonymous</tt> for the consumer key and the consumer secret. In this case Google will display a warning message on the authorization page which is acceptable for testing-purposes.</li>
	<li>Install the <a href="http://code.google.com/appengine/downloads.html" rel="nofollow">Google App Engine SDK for Java</a>. This tutorial has been tested with version 1.3.2.</li>
</ul>


<h4><a name="Tutorial-OAuth-Buildfromsources"></a>Build from sources</h4>

<p>Checkout the sources with</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>svn co http://svn.apache.org/repos/asf/camel/trunk/examples/camel-example-gauth camel-example-gauth
</pre>
</div></div>

<p>Open <tt>camel-example-gauth/pom.xml</tt> file and define values for the application properties e.g.</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>pom.xml</b></div><div class="codeContent panelContent">
<pre class="code-xml">
&lt;project xmlns=<span class="code-quote">"http://maven.apache.org/POM/4.0.0"</span> 
  <span class="code-keyword">xmlns:xsi</span>=<span class="code-quote">"http://www.w3.org/2001/XMLSchema-instance"</span> 
  xsi:schemaLocation=<span class="code-quote">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"</span>&gt;

    ...
    <span class="code-tag">&lt;properties&gt;</span>
        <span class="code-tag"><span class="code-comment">&lt;!-- application properties --&gt;</span></span>
        <span class="code-tag">&lt;gae.application.name&gt;</span>gauthclaud<span class="code-tag">&lt;/gae.application.name&gt;</span>
        <span class="code-tag">&lt;gae.consumer.key&gt;</span>gauthcloud.appspot.com<span class="code-tag">&lt;/gae.consumer.key&gt;</span>
        <span class="code-tag">&lt;gae.consumer.secret&gt;</span>g2e...ue<span class="code-tag">&lt;/gae.consumer.secret&gt;</span>
        ...
    <span class="code-tag">&lt;/properties&gt;</span>
    ...

<span class="code-tag">&lt;/project&gt;</span>
</pre>
</div></div>

<p>or </p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>pom.xml</b></div><div class="codeContent panelContent">
<pre class="code-xml">
&lt;project xmlns=<span class="code-quote">"http://maven.apache.org/POM/4.0.0"</span> 
  <span class="code-keyword">xmlns:xsi</span>=<span class="code-quote">"http://www.w3.org/2001/XMLSchema-instance"</span> 
  xsi:schemaLocation=<span class="code-quote">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"</span>&gt;

    ...
    <span class="code-tag">&lt;properties&gt;</span>
        <span class="code-tag"><span class="code-comment">&lt;!-- application properties --&gt;</span></span>
        <span class="code-tag">&lt;gae.application.name&gt;</span>gauthclaud<span class="code-tag">&lt;/gae.application.name&gt;</span>
        <span class="code-tag">&lt;gae.consumer.key&gt;</span>anonymous<span class="code-tag">&lt;/gae.consumer.key&gt;</span>
        <span class="code-tag">&lt;gae.consumer.secret&gt;</span>anonymous<span class="code-tag">&lt;/gae.consumer.secret&gt;</span>
        ...
    <span class="code-tag">&lt;/properties&gt;</span>
    ...

<span class="code-tag">&lt;/project&gt;</span>
</pre>
</div></div>

<p>if you don't want to register your application. Then go to the <tt>camel-example-gauth</tt> directory and enter</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>mvn install
</pre>
</div></div>

<p>This will create the application <tt>war</tt> file in the target directory. </p>

<h4><a name="Tutorial-OAuth-DeploytoAppengine"></a>Deploy to Appengine</h4>

<p>Finally use the <tt>appcfg</tt> command-line tool of the App Engine SDK to deploy the application.</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent panelContent">
<pre>appcfg update target/camel-example-gauth-&lt;version&gt;
</pre>
</div></div>

<p>where <tt>version</tt> needs to be replaced with the version of Camel you're using. You will be prompted for the email address and password of your Google App Engine account. After deployment the example application is ready to use. </p>

<div class='panelMacro'><table class='noteMacro'><colgroup><col width='24'><col></colgroup><tr><td valign='top'><img src="/confluence/images/icons/emoticons/warning.gif" width="16" height="16" align="absmiddle" alt="" border="0"></td><td><b>Potential issue when using appcfg from the GAE SDK</b><br /><p>It is important that you run <tt>appcfg</tt> with a <tt>java</tt> executable that is part of a JDK. If it is part of a JRE only then JSP compilation won't work. This is explained on the <a href="http://groups.google.com/group/google-appengine-java/msg/9b2f85fdff04c5ef" rel="nofollow">appengine-java mailing list</a>. Editing <tt>appcfg.sh</tt> or <tt>appcfg.cmd</tt> and pointing to an appropriate <tt>java</tt> executable should do the trick.</p></td></tr></table></div>

<h3><a name="Tutorial-OAuth-Codewalkthrough"></a>Code walkthrough</h3>

<h4><a name="Tutorial-OAuth-Applicationcontroller"></a>Application controller</h4>

<p>Entry point to the demo application is the <tt>TutorialController</tt>. It tries to obtain an OAuth access token from a cookie and interacts with the <tt>TutorialService</tt> for getting a user's calendar data from the Google Calendar API. Error messages (authentication failures) are displayed to the user by selecting the <tt>authorize.jsp</tt> view. This view also contains a link for starting the OAuth authorization process as shown above. A list of calendar names is displayed to the user by selecting the <tt>calendar.jsp</tt> view.</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>TutorialController.java</b></div><div class="codeContent panelContent">
<pre class="code-java">
<span class="code-keyword">package</span> org.apache.camel.example.gauth;

<span class="code-keyword">import</span> java.util.List;
<span class="code-keyword">import</span> javax.servlet.http.Cookie;
<span class="code-keyword">import</span> javax.servlet.http.HttpServletRequest;
<span class="code-keyword">import</span> javax.servlet.http.HttpServletResponse;

<span class="code-keyword">import</span> com.google.gdata.util.AuthenticationException;
<span class="code-keyword">import</span> org.springframework.beans.factory.annotation.Autowired;
<span class="code-keyword">import</span> org.springframework.stereotype.Controller;
<span class="code-keyword">import</span> org.springframework.ui.ModelMap;
<span class="code-keyword">import</span> org.springframework.web.bind.annotation.RequestMapping;
<span class="code-keyword">import</span> org.springframework.web.bind.annotation.RequestMethod;

/**
 * Single controller <span class="code-keyword">for</span> the demo application that handles GET requests. Obtains OAuth access
 * token and access token secret from cookies and uses them to obtain calendar names from the
 * Google Calendar API. If the interaction with the calendar API fails due to invalid or non-
 * existing OAuth tokens an error message is displayed in authorize.jsp. If it succeeds the
 * calendar names are displayed in calendar.jsp.
 * &lt;p&gt;
 * In production systems it is &lt;em&gt;not&lt;/em&gt; recommended to store access tokens in cookies. The
 * recommended approach is to store them in a database. The demo application is only doing that
 * to keep the example as simple as possible. However, an attacker could not use an access token
 * alone to get access to a user's calendar data because the application's consumer secret is
 * necessary <span class="code-keyword">for</span> that as well. The consumer secret never leaves the demo application.
 */
@Controller
@RequestMapping(<span class="code-quote">"/calendar"</span>)
<span class="code-keyword">public</span> class TutorialController {

    @Autowired
    <span class="code-keyword">private</span> TutorialService service;
    
    @SuppressWarnings(<span class="code-quote">"unchecked"</span>)
    @RequestMapping(method = RequestMethod.GET)
    <span class="code-keyword">public</span> <span class="code-object">String</span> handleGet(
            HttpServletRequest request, 
            HttpServletResponse response, 
            ModelMap model) <span class="code-keyword">throws</span> Exception {

        List&lt;<span class="code-object">String</span>&gt; calendarNames = <span class="code-keyword">null</span>;

        <span class="code-comment">// Get OAuth tokens from cookies
</span>        <span class="code-object">String</span> accessToken = getAccessToken(request);
        <span class="code-object">String</span> accessTokenSecret = getAccessTokenSecret(request);
        
        <span class="code-keyword">if</span> (accessToken == <span class="code-keyword">null</span>) {
            model.put(<span class="code-quote">"message"</span>, <span class="code-quote">"No OAuth access token available"</span>);
            <span class="code-keyword">return</span> <span class="code-quote">"/WEB-INF/jsp/authorize.jsp"</span>;
        }
        
        <span class="code-keyword">try</span> {
            <span class="code-comment">// Get calendar names from Google Calendar API
</span>            calendarNames = service.getCalendarNames(accessToken, accessTokenSecret);
        } <span class="code-keyword">catch</span> (AuthenticationException e) {
            model.put(<span class="code-quote">"message"</span>, <span class="code-quote">"OAuth access token invalid"</span>);
            <span class="code-keyword">return</span> <span class="code-quote">"/WEB-INF/jsp/authorize.jsp"</span>;
        }
        
        model.put(<span class="code-quote">"calendarNames"</span>, calendarNames);
        <span class="code-keyword">return</span> <span class="code-quote">"/WEB-INF/jsp/calendar.jsp"</span>;        
    }
    
    <span class="code-keyword">private</span> <span class="code-keyword">static</span> <span class="code-object">String</span> getAccessToken(HttpServletRequest request) {
        <span class="code-keyword">return</span> getCookieValue(request.getCookies(), <span class="code-quote">"TUTORIAL-ACCESS-TOKEN"</span>);
    }
    
    <span class="code-keyword">private</span> <span class="code-keyword">static</span> <span class="code-object">String</span> getAccessTokenSecret(HttpServletRequest request) {
        <span class="code-keyword">return</span> getCookieValue(request.getCookies(), <span class="code-quote">"TUTORIAL-ACCESS-TOKEN-SECRET"</span>);
    }

    <span class="code-comment">// <span class="code-keyword">rest</span> of code not shown ...
</span>    
}
</pre>
</div></div>

<h4><a name="Tutorial-OAuth-Applicationservice"></a>Application service</h4>

<p>Access to the Google Calendar API is encapsulated in the <tt>TutorialService</tt> class. It uses Google's <a href="http://code.google.com/p/gdata-java-client/" rel="nofollow">GData client library</a> for abstracting from low-level protocol details. The <tt>getCalendarNames</tt> method is paramterized with an OAuth access token and access token secret. If the access token is invalid then an <tt>AuthenticationException</tt> is thrown by the GData client. The exception is handled by the <tt>TutorialController</tt>.</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>TutorialService.java</b></div><div class="codeContent panelContent">
<pre class="code-java">
<span class="code-keyword">package</span> org.apache.camel.example.gauth;

<span class="code-keyword">import</span> java.net.URL;
<span class="code-keyword">import</span> java.util.ArrayList;
<span class="code-keyword">import</span> java.util.List;
<span class="code-keyword">import</span> java.util.Properties;

<span class="code-keyword">import</span> com.google.gdata.client.authn.oauth.OAuthHmacSha1Signer;
<span class="code-keyword">import</span> com.google.gdata.client.authn.oauth.OAuthParameters;
<span class="code-keyword">import</span> com.google.gdata.client.calendar.CalendarService;
<span class="code-keyword">import</span> com.google.gdata.data.calendar.CalendarEntry;
<span class="code-keyword">import</span> com.google.gdata.data.calendar.CalendarFeed;

/**
 * Facade <span class="code-keyword">for</span> getting calendar names from the Google Calendar API. The access is made on
 * behalf of a user by providing an OAuth access token and access token secret.
 */
<span class="code-keyword">public</span> class TutorialService {

    <span class="code-keyword">private</span> Properties credentials;

    /**
     * Sets properties that contains the application's consumer key and consumer secret.
     *
     * @param credentials consumer key and consumer secret.
     */
    <span class="code-keyword">public</span> void setCredentials(Properties credentials) {
        <span class="code-keyword">this</span>.credentials = credentials;
    }

    /**
     * Obtains a list of names of a user's <span class="code-keyword">public</span> and <span class="code-keyword">private</span> calendars from the Google
     * Calendar API.
     * 
     * @param accessToken OAuth access token.
     * @param accessTokenSecret OAuth access token secret.
     * @<span class="code-keyword">return</span> list of names of a user's <span class="code-keyword">public</span> and <span class="code-keyword">private</span> calendars.
     */
    <span class="code-keyword">public</span> List&lt;<span class="code-object">String</span>&gt; getCalendarNames(<span class="code-object">String</span> accessToken, <span class="code-object">String</span> accessTokenSecret) <span class="code-keyword">throws</span> Exception {
        CalendarService calendarService = <span class="code-keyword">new</span> CalendarService(<span class="code-quote">"apache-camel-2.3"</span>); 
        OAuthParameters params = getOAuthParams(accessToken, accessTokenSecret);
        calendarService.setOAuthCredentials(params, <span class="code-keyword">new</span> OAuthHmacSha1Signer());
        URL feedUrl = <span class="code-keyword">new</span> URL(<span class="code-quote">"http:<span class="code-comment">//www.google.com/calendar/feeds/<span class="code-keyword">default</span>/"</span>);
</span>        CalendarFeed resultFeed = calendarService.getFeed(feedUrl, CalendarFeed.class);

        ArrayList&lt;<span class="code-object">String</span>&gt; result = <span class="code-keyword">new</span> ArrayList&lt;<span class="code-object">String</span>&gt;();
        <span class="code-keyword">for</span> (<span class="code-object">int</span> i = 0; i &lt; resultFeed.getEntries().size(); i++) {
            CalendarEntry entry = resultFeed.getEntries().get(i);
            result.add(entry.getTitle().getPlainText());
        }
        <span class="code-keyword">return</span> result;
    }
    
    <span class="code-keyword">private</span> OAuthParameters getOAuthParams(<span class="code-object">String</span> accessToken, <span class="code-object">String</span> accessTokenSecret) {
        OAuthParameters params = <span class="code-keyword">new</span> OAuthParameters();
        params.setOAuthConsumerKey(credentials.getProperty(<span class="code-quote">"consumer.key"</span>));
        params.setOAuthConsumerSecret(credentials.getProperty(<span class="code-quote">"consumer.secret"</span>));
        params.setOAuthToken(accessToken);
        params.setOAuthTokenSecret(accessTokenSecret);
        <span class="code-keyword">return</span> params;
    }
    
}
</pre>
</div></div>

<p>The </p>

<h4><a name="Tutorial-OAuth-Integrationlayer"></a>Integration layer</h4>

<p>The integration layer uses Camel's <a href="/confluence/display/CAMEL/gauth" title="gauth">gauth</a> component to implement the consumer part of the OAuth authorization process. It cleanly separates OAuth integration logic from other parts of the application and is implemented by the <tt>TutorialRouteBuilder</tt> class.</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>TutorialRouteBuilder.java</b></div><div class="codeContent panelContent">
<pre class="code-java">
<span class="code-keyword">package</span> org.apache.camel.example.gauth;

<span class="code-keyword">import</span> java.net.URLEncoder;

<span class="code-keyword">import</span> org.apache.camel.builder.RouteBuilder;

/**
 * Builds the OAuth-specific routes (<span class="code-keyword">implements</span> the OAuth integration layer) of the demo application.
 */
<span class="code-keyword">public</span> class TutorialRouteBuilder <span class="code-keyword">extends</span> RouteBuilder {

    <span class="code-keyword">private</span> <span class="code-object">String</span> application;

    /**
     * Sets the name of the GAE application.
     *
     * @param application a GAE application name.
     */
    <span class="code-keyword">public</span> void setApplication(<span class="code-object">String</span> application) {
        <span class="code-keyword">this</span>.application = application;
    }

    @Override
    <span class="code-keyword">public</span> void configure() <span class="code-keyword">throws</span> Exception {

        <span class="code-comment">// Callback URL <span class="code-keyword">for</span> sending back an authorized access token.
</span>        <span class="code-object">String</span> encodedCallback = URLEncoder.encode(<span class="code-object">String</span>.format(<span class="code-quote">"https:<span class="code-comment">//%s.appspot.com/camel/handler"</span>, application), <span class="code-quote">"UTF-8"</span>);
</span>        <span class="code-comment">// Google should issue an access token that is scoped to calendar feeds.
</span>        <span class="code-object">String</span> encodedScope = URLEncoder.encode(<span class="code-quote">"http:<span class="code-comment">//www.google.com/calendar/feeds/"</span>, <span class="code-quote">"UTF-8"</span>);
</span>
        <span class="code-comment">// Route <span class="code-keyword">for</span> obtaining an unauthorized request token from Google Accounts. The
</span>        <span class="code-comment">// response redirects the browser to an authorization page provided by Google.
</span>        from(<span class="code-quote">"ghttp:<span class="code-comment">///authorize"</span>)
</span>            .to(<span class="code-quote">"gauth:authorize?callback="</span> + encodedCallback + <span class="code-quote">"&amp;scope="</span> + encodedScope);

        
        <span class="code-comment">// Handles callbacks from Google Accounts which contain an authorized request token.
</span>        <span class="code-comment">// The authorized request token is upgraded to an access token which is stored in
</span>        <span class="code-comment">// the response message header. The TutorialTokenProcessor is application-specific
</span>        <span class="code-comment">// and stores the access token (plus access token secret) is cookies. It further
</span>        <span class="code-comment">// redirects the user to the application's main location (/oauth/calendar).
</span>        from(<span class="code-quote">"ghttp:<span class="code-comment">///handler"</span>)
</span>            .to(<span class="code-quote">"gauth:upgrade"</span>)
            .process(<span class="code-keyword">new</span> TutorialTokenProcessor());
    }

}
</pre>
</div></div>

<p>This class implements two routes:</p>

<ol>
	<li>The first route obtains an unauthorized access token from Google Accounts and redirects the user to an authorization page provided by Google.</li>
	<li>The second route handles OAuth callbacks from Google and upgrades an authorized request tokens to access tokens.</li>
</ol>


<p>The last step in the second route is an application-specific processor (<tt>TutorialTokenProcessor</tt>) that stores the access token in a cookie and redirects the user to the main page of the demo application (which is served by the <tt>TutorialController</tt>).</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>TutorialTokenProcessor.java</b></div><div class="codeContent panelContent">
<pre class="code-java">
<span class="code-keyword">package</span> org.apache.camel.example.gauth;

<span class="code-keyword">import</span> javax.servlet.http.Cookie;
<span class="code-keyword">import</span> javax.servlet.http.HttpServletResponse;

<span class="code-keyword">import</span> org.apache.camel.Exchange;
<span class="code-keyword">import</span> org.apache.camel.Processor;

<span class="code-keyword">import</span> <span class="code-keyword">static</span> org.apache.camel.component.gae.auth.GAuthUpgradeBinding.GAUTH_ACCESS_TOKEN;
<span class="code-keyword">import</span> <span class="code-keyword">static</span> org.apache.camel.component.gae.auth.GAuthUpgradeBinding.GAUTH_ACCESS_TOKEN_SECRET;

/**
 * Reads an OAuth access token plus access token secret from a Camel message and stores them in
 * cookies. These cookies are needed by {@link org.apache.camel.example.gauth.TutorialController}
 * <span class="code-keyword">for</span> accessing a user's calendar via the Google Calendar API. The cookies are valid <span class="code-keyword">for</span> one
 * hour. Finally, it generates an HTTP 302 response that redirects the user to the application's
 * main location (/oauth/calendar).
 * &lt;p&gt;
 * In production systems it is &lt;em&gt;not&lt;/em&gt; recommended to store access tokens in cookies. The 
 * recommended approach is to store them in a database. The demo application is only doing that
 * to keep the example as simple as possible. However, an attacker could not use an access token
 * alone to get access to a user's calendar data because the application's consumer secret is
 * necessary <span class="code-keyword">for</span> that as well. The consumer secret never leaves the demo application.
 */
<span class="code-keyword">public</span> class TutorialTokenProcessor <span class="code-keyword">implements</span> Processor {

    <span class="code-keyword">private</span> <span class="code-keyword">static</span> <span class="code-keyword">final</span> <span class="code-object">int</span> ONE_HOUR = 3600;
    
    <span class="code-keyword">public</span> void process(Exchange exchange) <span class="code-keyword">throws</span> Exception {
        <span class="code-object">String</span> accessToken = exchange.getIn().getHeader(GAUTH_ACCESS_TOKEN, <span class="code-object">String</span>.class);
        <span class="code-object">String</span> accessTokenSecret = exchange.getIn().getHeader(GAUTH_ACCESS_TOKEN_SECRET, <span class="code-object">String</span>.class);
    
        <span class="code-keyword">if</span> (accessToken != <span class="code-keyword">null</span>) {
            HttpServletResponse servletResponse = exchange.getIn().getHeader(
                    Exchange.HTTP_SERVLET_RESPONSE, HttpServletResponse.class);
            
            Cookie accessTokenCookie = <span class="code-keyword">new</span> Cookie(<span class="code-quote">"TUTORIAL-ACCESS-TOKEN"</span>, accessToken);
            Cookie accessTokenSecretCookie = <span class="code-keyword">new</span> Cookie(<span class="code-quote">"TUTORIAL-ACCESS-TOKEN-SECRET"</span>, accessTokenSecret); 
            
            accessTokenCookie.setPath(<span class="code-quote">"/oauth/"</span>);
            accessTokenCookie.setMaxAge(ONE_HOUR);
            
            accessTokenSecretCookie.setPath(<span class="code-quote">"/oauth/"</span>);
            accessTokenSecretCookie.setMaxAge(ONE_HOUR);
            
            servletResponse.addCookie(accessTokenCookie);
            servletResponse.addCookie(accessTokenSecretCookie);
        }
        
        exchange.getOut().setHeader(Exchange.HTTP_RESPONSE_CODE, 302);
        exchange.getOut().setHeader(<span class="code-quote">"Location"</span>, <span class="code-quote">"/oauth/calendar"</span>);
    }

}
</pre>
</div></div>

<p>For further details about implementing OAuth integration layers in web application refer to the <a href="/confluence/display/CAMEL/gauth" title="gauth">gauth</a> component documentation.</p>

<h4><a name="Tutorial-OAuth-Configuration"></a>Configuration</h4>

<p>The <tt>TutorialController</tt> and <tt>TutorialService</tt> are set up in their own Spring application context. The <tt>TutorialController</tt> is scanned from the classpath using Spring's <tt>&lt;ctx:component-scan&gt;</tt> element. It is an <a href="http://static.springsource.org/spring/docs/2.5.x/reference/mvc.html#mvc-annotation" rel="nofollow">annotation-based Spring MVC controller</a>.</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>context-web.xml</b></div><div class="codeContent panelContent">
<pre class="code-xml">
&lt;beans xmlns=<span class="code-quote">"http://www.springframework.org/schema/beans"</span>
    <span class="code-keyword">xmlns:xsi</span>=<span class="code-quote">"http://www.w3.org/2001/XMLSchema-instance"</span>
    <span class="code-keyword">xmlns:ctx</span>=<span class="code-quote">"http://www.springframework.org/schema/context"</span>
    <span class="code-keyword">xmlns:util</span>=<span class="code-quote">"http://www.springframework.org/schema/util"</span>
    xsi:schemaLocation="
http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context 
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-2.5.xsd"&gt;

    <span class="code-tag">&lt;ctx:component-scan base-package=<span class="code-quote">"org.apache.camel.example.gauth"</span>/&gt;</span>

    <span class="code-tag">&lt;util:properties id=<span class="code-quote">"credentials"</span> location=<span class="code-quote">"classpath:context.properties"</span>/&gt;</span>
        
    <span class="code-tag">&lt;bean id=<span class="code-quote">"tutorialService"</span> class=<span class="code-quote">"org.apache.camel.example.gauth.TutorialService"</span>&gt;</span>
        <span class="code-tag">&lt;property name=<span class="code-quote">"credentials"</span> ref=<span class="code-quote">"credentials"</span> /&gt;</span>
    <span class="code-tag">&lt;/bean&gt;</span>
    
<span class="code-tag">&lt;/beans&gt;</span></pre>
</div></div>

<p>The integration layer and its <tt>CamelContext</tt> is configured in <tt>context-camel.xml</tt>. This application context also configures the <a href="/confluence/display/CAMEL/gauth" title="gauth">gauth</a> component with an application specific consumer key and consumer secret. These are read from a <tt>context.properties</tt> file.</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>context-camel.xml</b></div><div class="codeContent panelContent">
<pre class="code-xml">
&lt;beans xmlns=<span class="code-quote">"http://www.springframework.org/schema/beans"</span>
       <span class="code-keyword">xmlns:xsi</span>=<span class="code-quote">"http://www.w3.org/2001/XMLSchema-instance"</span>
       <span class="code-keyword">xmlns:context</span>=<span class="code-quote">"http://www.springframework.org/schema/context"</span>
       <span class="code-keyword">xmlns:camel</span>=<span class="code-quote">"http://camel.apache.org/schema/spring"</span>
       xsi:schemaLocation="
http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://camel.apache.org/schema/spring
http://camel.apache.org/schema/spring/camel-spring.xsd"&gt;
    
    <span class="code-tag">&lt;context:property-placeholder location=<span class="code-quote">"classpath:context.properties"</span>/&gt;</span>
    
    <span class="code-tag">&lt;camel:camelContext id=<span class="code-quote">"camelContext"</span>&gt;</span>
        <span class="code-tag">&lt;camel:jmxAgent id=<span class="code-quote">"agent"</span> disabled=<span class="code-quote">"true"</span> /&gt;</span>
        <span class="code-tag">&lt;camel:routeBuilder ref=<span class="code-quote">"tutorialRouteBuilder"</span>/&gt;</span>
    <span class="code-tag">&lt;/camel:camelContext&gt;</span>
    
    &lt;bean id=<span class="code-quote">"tutorialRouteBuilder"</span> 
        class=<span class="code-quote">"org.apache.camel.example.gauth.TutorialRouteBuilder"</span>&gt;
        <span class="code-tag">&lt;property name=<span class="code-quote">"application"</span> value=<span class="code-quote">"${application.name}"</span> /&gt;</span>
    <span class="code-tag">&lt;/bean&gt;</span>
    
    <span class="code-tag">&lt;bean id=<span class="code-quote">"gauth"</span> class=<span class="code-quote">"org.apache.camel.component.gae.auth.GAuthComponent"</span>&gt;</span>
        <span class="code-tag">&lt;property name=<span class="code-quote">"consumerKey"</span> value=<span class="code-quote">"${consumer.key}"</span> /&gt;</span>
        <span class="code-tag">&lt;property name=<span class="code-quote">"consumerSecret"</span> value=<span class="code-quote">"${consumer.secret}"</span> /&gt;</span>
    <span class="code-tag">&lt;/bean&gt;</span>
    
<span class="code-tag">&lt;/beans&gt;</span>
</pre>
</div></div>

<p>Both application contexts are referenced in the application's <tt>web.xml</tt>.</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader" style="border-bottom-width: 1px;"><b>web.xml</b></div><div class="codeContent panelContent">
<pre class="code-xml">
&lt;web-app 
xmlns=<span class="code-quote">"http://java.sun.com/xml/ns/javaee"</span>
<span class="code-keyword">xmlns:xsi</span>=<span class="code-quote">"http://www.w3.org/2001/XMLSchema-instance"</span>
<span class="code-keyword">xmlns:web</span>=<span class="code-quote">"http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"</span>
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd<span class="code-quote">" version="</span>2.5"&gt;
    
    <span class="code-tag">&lt;servlet&gt;</span>
        <span class="code-tag">&lt;servlet-name&gt;</span>CamelServlet<span class="code-tag">&lt;/servlet-name&gt;</span>
        <span class="code-tag">&lt;servlet-class&gt;</span>org.apache.camel.component.servlet.CamelHttpTransportServlet<span class="code-tag">&lt;/servlet-class&gt;</span>
        <span class="code-tag">&lt;init-param&gt;</span>
            <span class="code-tag">&lt;param-name&gt;</span>contextConfigLocation<span class="code-tag">&lt;/param-name&gt;</span>
            <span class="code-tag">&lt;param-value&gt;</span>context-camel.xml<span class="code-tag">&lt;/param-value&gt;</span>
        <span class="code-tag">&lt;/init-param&gt;</span>
    <span class="code-tag">&lt;/servlet&gt;</span>

    <span class="code-tag">&lt;servlet&gt;</span>
        <span class="code-tag">&lt;servlet-name&gt;</span>oauth<span class="code-tag">&lt;/servlet-name&gt;</span>
        <span class="code-tag">&lt;servlet-class&gt;</span>org.springframework.web.servlet.DispatcherServlet<span class="code-tag">&lt;/servlet-class&gt;</span>
        <span class="code-tag">&lt;init-param&gt;</span>
            <span class="code-tag">&lt;param-name&gt;</span>contextConfigLocation<span class="code-tag">&lt;/param-name&gt;</span>
            <span class="code-tag">&lt;param-value&gt;</span>/WEB-INF/classes/context-web.xml<span class="code-tag">&lt;/param-value&gt;</span>
        <span class="code-tag">&lt;/init-param&gt;</span>       
    <span class="code-tag">&lt;/servlet&gt;</span>
    
    <span class="code-tag">&lt;servlet-mapping&gt;</span>
        <span class="code-tag">&lt;servlet-name&gt;</span>oauth<span class="code-tag">&lt;/servlet-name&gt;</span>
        <span class="code-tag">&lt;url-pattern&gt;</span>/oauth/*<span class="code-tag">&lt;/url-pattern&gt;</span>
    <span class="code-tag">&lt;/servlet-mapping&gt;</span>
    <span class="code-tag">&lt;servlet-mapping&gt;</span>
        <span class="code-tag">&lt;servlet-name&gt;</span>CamelServlet<span class="code-tag">&lt;/servlet-name&gt;</span>
        <span class="code-tag">&lt;url-pattern&gt;</span>/camel/*<span class="code-tag">&lt;/url-pattern&gt;</span>
    <span class="code-tag">&lt;/servlet-mapping&gt;</span>
    
<span class="code-tag">&lt;/web-app&gt;</span>
</pre>
</div></div>

     </div>
     <div id="commentsSection" class="wiki-content pageSection">
       <div style="float: right;">
            <a href="http://cwiki.apache.org/confluence/users/viewnotifications.action" class="grey">Change Notification Preferences</a>
       </div>

       <a href="http://cwiki.apache.org/confluence/display/CAMEL/Tutorial-OAuth">View Online</a>
       |
       <a href="http://cwiki.apache.org/confluence/pages/diffpagesbyversion.action?pageId=16416889&revisedVersion=4&originalVersion=3">View Change</a>
              |
       <a href="http://cwiki.apache.org/confluence/display/CAMEL/Tutorial-OAuth?showComments=true&amp;showCommentArea=true#addcomment">Add Comment</a>
            </div>
</div>
</div>
</div>
</div>
</body>
</html>

Mime
View raw message