spring rest
DESCRIPTION
Restful API's in Spring Framework by Craig WallsTRANSCRIPT
Craig [email protected]: @habuma @springsocialhttp://github.com/habuma
Giving Spring some REST
Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: 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 puzzleMore APIs / Fewer “pages”
Humans and browsers consume pagesEverything 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
Spring’s REST Story
Spring MVC 3.0+Spring HATEOAS
Spring Security for OAuth (S2OAuth)Spring RestTemplate
Spring SocialSpring Data RESTSpring REST Shell
WebSocket/STOMP Support (Spring 4)Spring Boot
Creating REST APIs
Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma
Let’s 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 ConvertersStringHttpMessageConverter
Reads “text/*” into String; writes String into “text/plain”
FormHttpMessageConverterReads/writes “application/x-www-form-urlencoded” from/to MultiValueMap<String,String>
ByteArrayMessageConverterReads “*/*” into byte[]; writes Object as “application/octet-stream”
Jaxb2RootElementHttpMessageConverterReads/writes “text/xml” or “application/xml” from/to JAXB-annotated objects
MappingJacksonHttpMessageConverter / MappingJackson2HttpMessageConverter
Reads/writes “application/json” from/to Objects
SourceHttpMessageConverterReads/writes “text/xml”/”application/xml” from/to javax.xml.transform.Source
ResourceHttpMessageConverterReads/writes org.springframework.core.io.Resource objects
[AtomFeed|RssChannel]HttpMessageConverterReads/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
Let’s 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
There’s 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
There’s 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); }
}
Asynchronous Controllers
Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma
Spring + WebSocket + STOMP
Spring 4.0.0.M1: Low-level WebSocket supportSpring 4.0.0.M2: Higher-level, STOMP support
Messaging, not request-handlingSTOMP: Simple Text Oriented Messaging Protocol
Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma
Handling messages
@Controllerpublic 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
@Controllerpublic 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
@Controllerpublic 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...
Securing REST APIs
Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma
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 in-browser 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 user’s 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 client’s 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
Authorization: Bearer e139a950-2fc5-4822-9266-8a2b572108c5
Much simpler than OAuth 1.0(a)Differs across OAuth 2 drafts...
Authorization: BEARER e139a950-2fc5-4822-9266-8a2b572108c5
Authorization: OAuth e139a950-2fc5-4822-9266-8a2b572108c5
Authorization: Token token=”e139a950-2fc5-4822-9266-8a2b572108c5”
Drafts 14-current
Drafts 12-13
Draft 10
Drafts 1-9
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
• Produce and manage tokens
• Resource server
• Validate access tokens on requests to resource endpoints
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
• Handles /oauth/authorize and /oauth/token endpoints
• Resource Server
• Implemented as servlet filters
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>
Consuming REST APIs
Email: [email protected] Twitter: @habuma Blog: http://www.springinaction.com Sample Code: http://github.com/habuma
RestTemplate
Handles boilerplate HTTP connection codeKeeps 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);
WARNING: POST request for "https://api.twitter.com/1/statuses/update.json" resulted in 401 (Unauthorized); invoking error handlerorg.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)
Oh no!
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;!! @Inject! public TwitterTimelineController(Twitter twitter) {! ! this.twitter = twitter;! }
! @RequestMapping(value="/twitter/tweet", method=RequestMethod.POST)! public String postTweet(String message) {! ! twitter.timelineOperations().updateStatus(message);! ! return "redirect:/twitter";! }
}
Thank you!REST Books Sample (a work in progress)
https://github.com/habuma/rest-books
Spring HATEOAShttps://github.com/SpringSource/spring-hateoas
Spring Security for OAuth (S2OAuth)http://www.springsource.org/spring-security-oauth
Spring Socialhttp://www.springsource.org/spring-security-oauth
Spring Data RESThttp://www.springsource.org/spring-data/rest
Spring REST Shellhttps://github.com/SpringSource/rest-shell