...
Open camel-example-gauth/pom.xml
file and define values for the application properties e.g.
Code Block |
---|
|
<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/maven-v4_0_0.xsd">
...
<properties>
<!-- application properties -->
<gae.application.name>gauthclaud</gae.application.name>
<gae.consumer.key>gauthcloud.appspot.com</gae.consumer.key>
<gae.consumer.secret>g2e...ue</gae.consumer.secret>
...
</properties>
...
</project>
|
or
Code Block |
---|
|
<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/maven-v4_0_0.xsd">
...
<properties>
<!-- application properties -->
<gae.application.name>gauthclaud</gae.application.name>
<gae.consumer.key>anonymous</gae.consumer.key>
<gae.consumer.secret>anonymous</gae.consumer.secret>
...
</properties>
...
</project>
|
...
Entry point to the demo application is the TutorialController
. It tries to obtain an OAuth access token from a cookie and interacts with the TutorialService
for getting a user's calendar data from the Google Calendar API. Error messages (authentication failures) are displayed to the user by selecting the authorize.jsp
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 calendar.jsp
view.
Code Block |
java |
---|
| java |
---|
title | TutorialController.java |
---|
| java |
---|
|
package org.apache.camel.example.gauth;
import java.util.List;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.gdata.util.AuthenticationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* Single controller for 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.
* <p>
* In production systems it is <em>not</em> 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.
*/
@Controller
@RequestMapping("/calendar")
public class TutorialController {
@Autowired
private TutorialService service;
@SuppressWarnings("unchecked")
@RequestMapping(method = RequestMethod.GET)
public String handleGet(
HttpServletRequest request,
HttpServletResponse response,
ModelMap model) throws Exception {
List<String> calendarNames = null;
// Get OAuth tokens from cookies
String accessToken = getAccessToken(request);
String accessTokenSecret = getAccessTokenSecret(request);
if (accessToken == null) {
model.put("message", "No OAuth access token available");
return "/WEB-INF/jsp/authorize.jsp";
}
try {
// Get calendar names from Google Calendar API
calendarNames = service.getCalendarNames(accessToken, accessTokenSecret);
} catch (AuthenticationException e) {
model.put("message", "OAuth access token invalid");
return "/WEB-INF/jsp/authorize.jsp";
}
model.put("calendarNames", calendarNames);
return "/WEB-INF/jsp/calendar.jsp";
}
private static String getAccessToken(HttpServletRequest request) {
return getCookieValue(request.getCookies(), "TUTORIAL-ACCESS-TOKEN");
}
private static String getAccessTokenSecret(HttpServletRequest request) {
return getCookieValue(request.getCookies(), "TUTORIAL-ACCESS-TOKEN-SECRET");
}
// rest of code not shown ...
}
|
...
Access to the Google Calendar API is encapsulated in the TutorialService
class. It uses Google's GData client library for abstracting from low-level protocol details. The getCalendarNames
method is paramterized with an OAuth access token and access token secret. If the access token is invalid then an AuthenticationException
is thrown by the GData client. The exception is handled by the TutorialController
.
Code Block |
---|
| java | java |
---|
title | TutorialService.java |
---|
| java |
---|
|
package org.apache.camel.example.gauth;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import com.google.gdata.client.authn.oauth.OAuthHmacSha1Signer;
import com.google.gdata.client.authn.oauth.OAuthParameters;
import com.google.gdata.client.calendar.CalendarService;
import com.google.gdata.data.calendar.CalendarEntry;
import com.google.gdata.data.calendar.CalendarFeed;
/**
* Facade for 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.
*/
public class TutorialService {
private Properties credentials;
/**
* Sets properties that contains the application's consumer key and consumer secret.
*
* @param credentials consumer key and consumer secret.
*/
public void setCredentials(Properties credentials) {
this.credentials = credentials;
}
/**
* Obtains a list of names of a user's public and private calendars from the Google
* Calendar API.
*
* @param accessToken OAuth access token.
* @param accessTokenSecret OAuth access token secret.
* @return list of names of a user's public and private calendars.
*/
public List<String> getCalendarNames(String accessToken, String accessTokenSecret) throws Exception {
CalendarService calendarService = new CalendarService("apache-camel-2.3");
OAuthParameters params = getOAuthParams(accessToken, accessTokenSecret);
calendarService.setOAuthCredentials(params, new OAuthHmacSha1Signer());
URL feedUrl = new URL("http://www.google.com/calendar/feeds/default/");
CalendarFeed resultFeed = calendarService.getFeed(feedUrl, CalendarFeed.class);
ArrayList<String> result = new ArrayList<String>();
for (int i = 0; i < resultFeed.getEntries().size(); i++) {
CalendarEntry entry = resultFeed.getEntries().get(i);
result.add(entry.getTitle().getPlainText());
}
return result;
}
private OAuthParameters getOAuthParams(String accessToken, String accessTokenSecret) {
OAuthParameters params = new OAuthParameters();
params.setOAuthConsumerKey(credentials.getProperty("consumer.key"));
params.setOAuthConsumerSecret(credentials.getProperty("consumer.secret"));
params.setOAuthToken(accessToken);
params.setOAuthTokenSecret(accessTokenSecret);
return params;
}
}
|
...
The integration layer uses Camel's gauth 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 TutorialRouteBuilder
class.
Code Block |
java |
---|
| java |
---|
title | TutorialRouteBuilder.java |
---|
| java |
---|
|
package org.apache.camel.example.gauth;
import java.net.URLEncoder;
import org.apache.camel.builder.RouteBuilder;
/**
* Builds the OAuth-specific routes (implements the OAuth integration layer) of the demo application.
*/
public class TutorialRouteBuilder extends RouteBuilder {
private String application;
/**
* Sets the name of the GAE application.
*
* @param application a GAE application name.
*/
public void setApplication(String application) {
this.application = application;
}
@Override
public void configure() throws Exception {
// Callback URL for sending back an authorized access token.
String encodedCallback = URLEncoder.encode(
String.format("https://%s.appspot.com/camel/handler", application), "UTF-8");
// Google should issue an access token that is scoped to calendar feeds.
String encodedScope = URLEncoder.encode("http://www.google.com/calendar/feeds/", "UTF-8");
// Route for obtaining an unauthorized request token from Google Accounts. The
// response redirects the browser to an authorization page provided by Google.
from("ghttp:///authorize")
.to("gauth:authorize?callback=" + encodedCallback + "&scope=" + encodedScope);
// Handles callbacks from Google Accounts which contain an authorized request token.
// The authorized request token is upgraded to an access token which is stored in
// the response message header. The TutorialTokenProcessor is application-specific
// and stores the access token (plus access token secret) is cookies. It further
// redirects the user to the application's main location (/oauth/calendar).
from("ghttp:///handler")
.to("gauth:upgrade")
.process(new TutorialTokenProcessor());
}
}
|
...
The last step in the second route is an application-specific processor (TutorialTokenProcessor
) 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 TutorialController
).
Code Block |
---|
| java | java |
---|
title | TutorialTokenProcessor.java |
---|
| java |
---|
|
package org.apache.camel.example.gauth;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import static org.apache.camel.component.gae.auth.GAuthUpgradeBinding.GAUTH_ACCESS_TOKEN;
import static 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}
* for accessing a user's calendar via the Google Calendar API. The cookies are valid for one
* hour. Finally, it generates an HTTP 302 response that redirects the user to the application's
* main location (/oauth/calendar).
* <p>
* In production systems it is <em>not</em> 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.
*/
public class TutorialTokenProcessor implements Processor {
private static final int ONE_HOUR = 3600;
public void process(Exchange exchange) throws Exception {
String accessToken = exchange.getIn().getHeader(GAUTH_ACCESS_TOKEN, String.class);
String accessTokenSecret = exchange.getIn().getHeader(GAUTH_ACCESS_TOKEN_SECRET, String.class);
if (accessToken != null) {
HttpServletResponse servletResponse = exchange.getIn().getHeader(
Exchange.HTTP_SERVLET_RESPONSE, HttpServletResponse.class);
Cookie accessTokenCookie = new Cookie("TUTORIAL-ACCESS-TOKEN", accessToken);
Cookie accessTokenSecretCookie = new Cookie("TUTORIAL-ACCESS-TOKEN-SECRET", accessTokenSecret);
accessTokenCookie.setPath("/oauth/");
accessTokenCookie.setMaxAge(ONE_HOUR);
accessTokenSecretCookie.setPath("/oauth/");
accessTokenSecretCookie.setMaxAge(ONE_HOUR);
servletResponse.addCookie(accessTokenCookie);
servletResponse.addCookie(accessTokenSecretCookie);
}
exchange.getOut().setHeader(Exchange.HTTP_RESPONSE_CODE, 302);
exchange.getOut().setHeader("Location", "/oauth/calendar");
}
}
|
...
The TutorialController
and TutorialService
are set up in their own Spring application context. The TutorialController
is scanned from the classpath using Spring's <ctx:component-scan>
element. It is an annotation-based Spring MVC controller.
Code Block |
xml |
---|
| xml |
---|
title | context-web.xml |
---|
| xml |
---|
|
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ctx="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
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">
<ctx:component-scan base-package="org.apache.camel.example.gauth"/>
<util:properties id="credentials" location="classpath:context.properties"/>
<bean id="tutorialService" class="org.apache.camel.example.gauth.TutorialService">
<property name="credentials" ref="credentials" />
</bean>
</beans> |
The integration layer and its CamelContext
is configured in context-camel.xml
. This application context also configures the gauth component with an application specific consumer key and consumer secret. These are read from a context.properties
file.
Code Block |
---|
| xml | xml |
---|
title | context-camel.xml |
---|
| xml |
---|
|
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:camel="http://camel.apache.org/schema/spring"
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">
<context:property-placeholder location="classpath:context.properties"/>
<camel:camelContext id="camelContext">
<camel:jmxAgent id="agent" disabled="true" />
<camel:routeBuilder ref="tutorialRouteBuilder"/>
</camel:camelContext>
<bean id="tutorialRouteBuilder"
class="org.apache.camel.example.gauth.TutorialRouteBuilder">
<property name="application" value="${application.name}" />
</bean>
<bean id="gauth" class="org.apache.camel.component.gae.auth.GAuthComponent">
<property name="consumerKey" value="${consumer.key}" />
<property name="consumerSecret" value="${consumer.secret}" />
</bean>
</beans>
|
Both application contexts are referenced in the application's web.xml
.
Code Block |
---|
|
<web-app
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
<servlet>
<servlet-name>CamelServlet</servlet-name>
<servlet-class>org.apache.camel.component.servlet.CamelHttpTransportServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>context-camel.xml</param-value>
</init-param>
</servlet>
<servlet>
<servlet-name>oauth</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/context-web.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>oauth</servlet-name>
<url-pattern>/oauth/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>CamelServlet</servlet-name>
<url-pattern>/camel/*</url-pattern>
</servlet-mapping>
</web-app>
|