Giving Spring some REST
Craig Walls
[email protected]
Twitter: @habuma @springsocial
http://github.com/habuma
REST in One Slide
Resources (aka, the things)
Representations
HTTP Methods (aka, the verbs)
URIs and URLs
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Why REST?
Key piece of the Modern Application puzzle
More APIs / Fewer pages
Humans and browsers consume pages
Everything can consume APIs
(incl. browsers, JS, mobile apps, other apps,etc)
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Springs REST Story
Spring MVC 3.0+
Spring HATEOAS
Spring Security for OAuth (S2OAuth)
Spring RestTemplate
Spring Social
Spring Data REST
Spring REST Shell
WebSocket/STOMP Support (Spring 4)
Spring Boot
Email:
[email protected]Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Creating REST APIs
Lets write a simple REST controller...
@Controller
@RequestMapping("/books")
public class BooksController {
!
! private BookRepository bookRepository;
!
!
!
!
@Inject
public BooksController(BookRepository bookRepository) {
! this.bookRepository = bookRepository;! !
}
!
!
!
!
!
}
@RequestMapping(method=RequestMethod.GET)
public @ResponseBody List<Book> allBooks() {
! return bookRepository.findAll();
}
GET http://host/app/books
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
How Does @ResponseBody Work?
How does it know how to write it to the
response?
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Message Converters
StringHttpMessageConverter
Reads text/* into String; writes String into text/plain
FormHttpMessageConverter
Reads/writes application/x-www-form-urlencoded from/to MultiValueMap<String,String>
ByteArrayMessageConverter
Reads */* into byte[]; writes Object as application/octet-stream
Jaxb2RootElementHttpMessageConverter
Reads/writes text/xml or application/xml from/to JAXB-annotated objects
MappingJacksonHttpMessageConverter /
MappingJackson2HttpMessageConverter
Reads/writes application/json from/to Objects
SourceHttpMessageConverter
Reads/writes text/xml/application/xml from/to javax.xml.transform.Source
ResourceHttpMessageConverter
Reads/writes org.springframework.core.io.Resource objects
[AtomFeed|RssChannel]HttpMessageConverter
Reads/writes Rome Feed and RssChannels (application/atom+xml | rss+xml)
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Lets Add Another Endpoint
@Controller
@RequestMapping("/books")
public class BooksController {
!
...
!
!
!
!
!
}
@RequestMapping(value="/{id}", method=RequestMethod.GET)
public @ResponseBody Book bookById(@PathVariable("id") long id) {
! return bookRepository.findOne(id);
}
GET http://host/app/books/{id}
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
What About POSTing Resources?
@Controller
@RequestMapping("/books")
public class BooksController {
!
...
!
!
!
!
@RequestMapping(method=RequestMethod.POST)
public @ResponseBody Book postBook(@RequestBody Book book) {
! return bookRepository.save(book);
}!
POST http://host/app/books
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Can We PUT a Resource?
@Controller
@RequestMapping("/books")
public class BooksController {
!
...
!
!
!
!
@RequestMapping(value="/{id}" method=RequestMethod.PUT)
public void updateBook(@PathVariable("id") long id,
@RequestBody Book book) {
! bookRepository.save(book);
}
PUT http://host/app/books/{id}
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Deleting a Resource
@Controller
@RequestMapping("/books")
public class BooksController {
!
...
!
!
!
!
@RequestMapping(value="{id}", method=RequestMethod.DELETE)
public void deleteBook(@PathVariable("id") long id) {
! bookRepository.delete(id);
}
DELETE http://host/app/books/{id}
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Theres More Than the Resource...
@Controller
@RequestMapping("/books")
public class BooksController {
!
...
!
!
!
!
!
}
@RequestMapping(value="/{id}", method=RequestMethod.GET)
public @ResponseBody Book bookById(@PathVariable("id") long id) {
! return bookRepository.findOne(id);
}
What will happen if findById() returns null?
What should happen?
Email:
[email protected]Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Theres More Than the Resource...
@Controller
@RequestMapping("/books")
public class BooksController {
!
...
!
!
!
!
@RequestMapping(method=RequestMethod.POST)
public @ResponseBody Book postBook(@RequestBody Book book) {
! return bookRepository.save(book);
}!
What will the HTTP status code be?
What should it be?
Email:
[email protected]Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Returning a ResponseEntity
@Controller
@RequestMapping("/books")
public class BooksController {
!
...
!
!
!
!
}
@RequestMapping(value="/{id}", method=RequestMethod.GET)
public ResponseEntity<?> bookById(@PathVariable("id") long id) {
Book book = bookRepository.findOne(id);
if (book != null) {
return new ResponseEntity<Book>(book, HttpStatus.OK);
} else {
Error error = new Error("Book with ID " + id + " not found");
return new ResponseEntity<Error>(error, HttpStatus.NOT_FOUND);
}
}
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Returning a ResponseEntity
@Controller
@RequestMapping("/books")
public class BooksController {
!
...
!
!
!
!
@RequestMapping(value="/{id}", method=RequestMethod.GET)
public ResponseEntity<Book> bookById(@PathVariable("id") long id) {
Book book = bookRepository.findOne(id);
if (book != null) {
return new ResponseEntity<Book>(book, HttpStatus.OK);
}
throw new BookNotFoundException(id);
}
@ExceptionHandler(BookNotFoundException.class)
public ResponseEntity<Error> bookNotFound(BookNotFoundException e) {
Error error = new Error("Book with ID " + id + " not found");
return new ResponseEntity<Error>(error, HttpStatus.NOT_FOUND);
}
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Returning a ResponseEntity
@Controller
@RequestMapping("/books")
public class BooksController {
!
...
!
!
!
!
!
!
!
!
!
@RequestMapping(method=RequestMethod.POST)
public ResponseEntity<Book> postBook(@RequestBody Book book) {
! Book newBook = bookRepository.save(book);
! ResponseEntity<Book> bookEntity =
new ResponseEntity<Book>(newBook, HttpStatus.CREATED);
! String locationUrl =
ServletUriComponentsBuilder.fromCurrentContextPath().
! ! ! path("/books/" + newBook.getId()).build().toUriString();
! bookEntity.getHeaders().setLocation(URI.create(locationUrl));
! return bookEntity;
}
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Linking Resources
HATEOAS
Hypermedia As The Engine Of Application State
Responses carry links to related endpoints
API is self-descriptive
Client can learn about the API
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Self-Describing API
{
"links" : [
{
"rel" : "self",
"href" : "http://localhost:8080/BookApp/books/5"
},
{
"rel" : "all",
"href" : "http://localhost:8080/BookApp/books/"
},
{
"rel" : "author",
"href" : "http://localhost:8080/BookApp/authors/2"
}
],
"id" : 5,
"title" : "Spring in Action",
...
}
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Defining a Resource
public class BookResource extends ResourceSupport {
...
}
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Adding Links to a Resource
@RequestMapping(value="/{id}", method=RequestMethod.GET)
public ResponseEntity<BookResource> bookById(@PathVariable("id") long id) {
Book book = bookRepository.findOne(id);
if (book != null) {
BookResource resource = bookResourceAssembler.toResource(book);
resource.add(ControllerLinkerBuilder.linkTo(BooksController.class)
.withRel("all"));
resource.add(ControllerLinkerBuilder.linkTo(AuthorsController.class)
.slash(book.getAuthor().getId());
.withRel("author");
return new ResponseEntity<BookResource>(resource, HttpStatus.OK);
}
throw new BookNotFoundException(id);
}
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Assembling a Resource
public class BookResourceAssembler
extends ResourceAssemblerSupport<Book, BookResource> {
public BookResourceAssembler() {
super(BooksController.class, BookResource.class);
}
public BookResource toResource(Book book) {
return createResource(book);
}
public BookResource instantiateResource(Book book) {
return new BookResource(book);
}
}
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Asynchronous Controllers
Spring + WebSocket + STOMP
Spring 4.0.0.M1: Low-level WebSocket support
Spring 4.0.0.M2: Higher-level, STOMP support
Messaging, not request-handling
STOMP: Simple Text Oriented Messaging Protocol
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Handling messages
@Controller
public class PortfolioController {
...
!
!
!
!
!
@MessageMapping(value="/app/trade")
public void executeTrade(Trade trade, Principal principal) {
! trade.setUsername(principal.getName());
! this.tradeService.executeTrade(trade);
}
...
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Handling subscriptions
@Controller
public class PortfolioController {
...
!
!
@SubscribeEvent("/app/positions")
public List<PortfolioPosition> getPortfolios(Principal principal)
throws Exception {
Portfolio portfolio =
this.portfolioService.findPortfolio(principal.getName());
!
!
!
}
return portfolio.getPositions();
...
}
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Handling message exceptions
@Controller
public class PortfolioController {
...
!
!
!
!
!
@MessageExceptionHandler
@ReplyToUser(value="/queue/errors")
public String handleException(Throwable exception) {
! return exception.getMessage();
}
...
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
On the client side: Receiving messages
var socket = new SockJS('/spring-websocket-portfolio/portfolio');
var stompClient = Stomp.over(socket);
...
stompClient.subscribe("/app/positions", function(message) {
self.portfolio().loadPositions(JSON.parse(message.body));
});
stompClient.subscribe("/topic/price.stock.*", function(message) {
self.portfolio().processQuote(JSON.parse(message.body));
});
stompClient.subscribe("/queue/errors" + queueSuffix, function(message) {
self.pushNotification("Error " + message.body);
});
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
On the client side: Sending messages
var socket = new SockJS('/spring-websocket-portfolio/portfolio');
var stompClient = Stomp.over(socket);
...
var trade = {
"action" : self.action(),
"ticker" : self.currentRow().ticker,
"shares" : self.sharesToTrade()
};
stompClient.send("/app/trade", {}, JSON.stringify(trade));
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Configuring STOMP in Spring
Well...there are a lot of beans...
It gets better in 4.0.0.RC1 (see SPR-10835)...
But for now...
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Securing REST APIs
OAuth
An open standard for
authorization
Supported by Facebook,
Twitter, LinkedIn, TripIt,
Salesforce, and dozens more
Puts the user in control of
what resources are shared
http://oauth.net
Email:
[email protected]Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
The Many Versions of OAuth
OAuth 1.0
TripIt, NetFlix, DropBox, Gliffy, MySpace, ...
OAuth 1.0a
Twitter, LinkedIn, Evernote, Flickr,Yammer,Yelp!, ...
OAuth 2
Facebook, Foursquare, Google, GitHub, Instagram, ...
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
OAuth 2
Much simpler than OAuth 1.0(a)
No more request token
Leverages HTTPS instead of encrypting the token
No signature or canonicalization of the request
Much simpler Authorization header
Scoped authorization
Short-lived tokens, long-lived authorization
Separate roles of authorization server/resource server
Multiple grant types
Email:
[email protected]Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Authorization Code Grant
Like OAuth 1.0 flow
Starts with redirect to provider
for authorization
After authorization, redirects
back to client with code query
parameter
Code is exchanged for access
token
Client must be able to keep
tokens confidential
Commonly used for web
apps
Email:
[email protected]Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Implicit Grant
Simplified authorization flow
After authorization, redirects
back to client with access
token in fragment parameter
Reduced round-trips
No refresh token support
Commonly used by inbrowser JavaScript apps or
widgets
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Resource Owner Credentials Grant
Directly exchanges users
credentials for an access
token
Useful where the client is
well-trusted by the user and
where a browser redirect
would be awkward
Commonly used with mobile
apps
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Client Credentials Grant
Directly exchanges the
clients credentials for an
access token
For accessing client-owned
resources (with no user
involvement)
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
The OAuth 2 Authorization Header
Much simpler than OAuth 1.0(a)
Differs across OAuth 2 drafts...
Drafts 14-current
Authorization: Bearer e139a950-2fc5-4822-9266-8a2b572108c5
Drafts 12-13
Authorization: BEARER e139a950-2fc5-4822-9266-8a2b572108c5
Draft 10
Authorization: OAuth e139a950-2fc5-4822-9266-8a2b572108c5
Drafts 1-9
Authorization: Token token=e139a950-2fc5-4822-9266-8a2b572108c5
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
OAuth Provider Responsibilities
Authorization server
If supporting authorization code and/or implicit grant, must
serve an authorization page
Support an authorization endpoint for all supported grant
types
Not obligated to support all grant types
Validate access tokens on requests to resource endpoints
Produce and manage tokens
Resource server
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Spring Security OAuth (S2OAuth)
Based on Spring Security
Declarative model for OAuth
Provider-side support for authorization endpoints, token
management, and resource-level security
Also offers client-side OAuth
Implemented for both OAuth 1 and OAuth 2
http://www.springsource.org/spring-security-oauth
Email:
[email protected]Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Key Pieces of S2OAuth
Authorization Server
Implemented as Spring MVC controller
Implemented as servlet filters
Handles /oauth/authorize and /oauth/token endpoints
Resource Server
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Configuring the Authorization Server
<oauth:authorization-server
client-details-service-ref="clientDetails"
token-services-ref="tokenServices">
<oauth:authorization-code />
<oauth:implicit />
<oauth:refresh-token />
<oauth:client-credentials />
<oauth:password />
</oauth:authorization-server>
<oauth:client-details-service id="clientDetails">
<oauth:client client-id="tonr" secret="secret"
resource-ids="sparklr"
authorized-grant-types="authorization_code,implicit"
authorities="ROLE_CLIENT"
scope="read,write" />
</oauth:client-details-service>
<bean id="tokenServices"
class="o.sf.security.oauth2.provider.token.DefaultTokenServices">
<property name="tokenStore" ref="tokenStore" />
<property name="supportRefreshToken" value="true" />
<property name="clientDetailsService" ref="clientDetails"/>
</bean>
<bean id="tokenStore"
class="o.sf.security.oauth2.provider.token.InMemoryTokenStore" />
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Configuring the Authorization Server
<http pattern="/oauth/token"
create-session="stateless"
authentication-manager-ref="clientAuthenticationManager"
entry-point-ref="oauthAuthenticationEntryPoint"
xmlns="http://www.springframework.org/schema/security">
<intercept-url pattern="/oauth/token"
access="IS_AUTHENTICATED_FULLY" />
! <anonymous enabled="false" />
! <http-basic entry-point-ref="oauthAuthenticationEntryPoint" />
</http>
<authentication-manager id="clientAuthenticationManager"
xmlns="http://www.springframework.org/schema/security">
! <authentication-provider user-service-ref="clientDetailsUserService" />
</authentication-manager>
<bean id="clientDetailsUserService"
class="o.sf.security.oauth2.provider.client.ClientDetailsUserDetailsService">
<constructor-arg ref="clientDetails" />
</bean>
<bean id="oauthAuthenticationEntryPoint"
class="o.sf.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
<property name="realmName" value="sparklr2" />
</bean>
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Configuring the Resource Server
<http pattern="/photos/**"
create-session="never"
entry-point-ref="oauthAuthenticationEntryPoint"
!
access-decision-manager-ref="accessDecisionManager"
!
xmlns="http://www.springframework.org/schema/security">
! <anonymous enabled="false" />
!
!
!
!
<intercept-url pattern="/photos" access="ROLE_USER,SCOPE_READ" />
<intercept-url pattern="/photos/trusted/**"
access="ROLE_CLIENT,SCOPE_TRUST" />
<intercept-url pattern="/photos/user/**" access="ROLE_USER,SCOPE_TRUST" />
<intercept-url pattern="/photos/**" access="ROLE_USER,SCOPE_READ" />
! <custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
</http>
<bean id="accessDecisionManager"
class="org.springframework.security.access.vote.UnanimousBased"
xmlns="http://www.springframework.org/schema/beans">
<constructor-arg>
<list>
<bean class="o.sf.security.oauth2.provider.vote.ScopeVoter" />
<bean class="o.sf.security.access.vote.RoleVoter" />
<bean class="o.sf.security.access.vote.AuthenticatedVoter" />
</list>
</constructor-arg>
</bean>
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Consuming REST APIs
RestTemplate
Handles boilerplate HTTP connection code
Keeps your focus on the resources
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Using RestTemplate
RestTemplate restTemplate = new RestTemplate();
Book book = new Book("Spring in Action", "Gregg Walls");
Book newBook = restTemplate.postForObject(
"http://host/app/books", book, Book.class);
book = restTemplate.getForObject(
"http://host/app/books/{id}", Book.class, newBook.getId());
book.setAuthor("Craig Walls");
restTemplate.put("http://host/app/books/{id}",
Book.class, book.getId());
restTemplate.delete("http://host/app/books/{id}",
Book.class, book.getId());
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Tweeting with RestTemplate
RestTemplate rest = new RestTemplate();
MultiValueMap<String, Object> params =
new LinkedMultiValueMap<String, Object>();
params.add("status", "Hello Twitter!");
rest.postForObject("https://api.twitter.com/1/statuses/update.json",
params, String.class);
Oh no!
WARNING: POST request for "https://api.twitter.com/1/statuses/update.json" resulted in 401 (Unauthorized); invoking error
handler
org.springframework.web.client.HttpClientErrorException: 401 Unauthorized
!
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:75)
!
at org.springframework.web.client.RestTemplate.handleResponseError(RestTemplate.java:486)
!
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:443)
!
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:401)
!
at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:279)
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Configuring Spring Social
@Configuration
@EnableJdbcConnectionRepository
@EnableTwitter(appId="${twitter.consumerKey}", appSecret="${twitter.consumerSecret}")
@EnableFacebook(appId="${facebook.clientId}", appSecret="${facebook.clientSecret}")
public class SocialConfig {
!
! @Inject
! private ConnectionFactoryLocator connectionFactoryLocator;
!
! @Inject
! private ConnectionRepository connectionRepository;
!
!
!
!
@Bean
public ConnectController connectController() {
! return new ConnectController(connectionFactoryLocator, connectionRepository);
}
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Configuring Spring Social
<facebook:config app-id="${facebook.clientId}"
app-secret="${facebook.clientSecret}"
app-namespace="socialshowcase" />
<twitter:config app-id="${twitter.consumerKey}"
app-secret="${twitter.consumerSecret}"/>
<social:jdbc-connection-repository/>!
<bean id="connectController"
class="org.springframework.social.connect.web.ConnectController"
autowire="constructor" />
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Tweeting with Spring Social
public class TwitterTimelineController {
!
!
!
!
!
!
private final Twitter twitter;
!
!
!
!
!
@RequestMapping(value="/twitter/tweet", method=RequestMethod.POST)
public String postTweet(String message) {
! twitter.timelineOperations().updateStatus(message);
! return "redirect:/twitter";
}
@Inject
public TwitterTimelineController(Twitter twitter) {
! this.twitter = twitter;
}
Email: [email protected]
Twitter: @habuma
Blog: http://www.springinaction.com
Sample Code: http://github.com/habuma
Thank you!
REST Books Sample (a work in progress)
https://github.com/habuma/rest-books
Spring HATEOAS
https://github.com/SpringSource/spring-hateoas
Spring Security for OAuth (S2OAuth)
http://www.springsource.org/spring-security-oauth
Spring Social
http://www.springsource.org/spring-security-oauth
Spring Data REST
http://www.springsource.org/spring-data/rest
Spring REST Shell
https://github.com/SpringSource/rest-shell