Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

JDD2014: What you won't read in books about implementing REST services - Jakub Kubryński

348 views

Published on

REST is as plain as the nose on your face. However, often exploring the secrets of this pattern ends up with the positive completion of two "Hello, World" class challenges. During this lecture we will focus on common problems and ways of handling them. We will deal with the security and best practices on topics like HATEOAS or versioning.

Published in: Software
  • Be the first to comment

  • Be the first to like this

JDD2014: What you won't read in books about implementing REST services - Jakub Kubryński

  1. 1. The thin line between RESTful and AWful Jakub Kubrynski jk@devskiller.com / @jkubrynski 1 / 48
  2. 2. jk@devskiller.com / @jkubrynski 2 / 48
  3. 3. "The Code is more what you'd call guidelines than actual rules. Welcome aboard the Black Pearl, Miss Turner" -- Cpt. Hector Barbossa to Elizabeth Swann RT Ben Hale jk@devskiller.com / @jkubrynski 3 / 48
  4. 4. Formal REST constraints Client-Server Stateless Cache Interface / Uniform Contract Layered System jk@devskiller.com / @jkubrynski 4 / 48
  5. 5. Richardson maturity model http://martinfowler.com/articles/richardsonMaturityModel.html jk@devskiller.com / @jkubrynski 5 / 48
  6. 6. POST vs PUT jk@devskiller.com / @jkubrynski 6 / 48
  7. 7. POST vs PUT POST creates new resources jk@devskiller.com / @jkubrynski 7 / 48
  8. 8. POST vs PUT POST creates new resources PUT updates existing resources PUT can create resource if ID is already known jk@devskiller.com / @jkubrynski 8 / 48
  9. 9. Maybe PATCH? no "out of the box" support jk@devskiller.com / @jkubrynski 9 / 48
  10. 10. Maybe PATCH? no "out of the box" support partial update @RequestMapping(value = "/{id}", method = PATCH) public void updateArticle(HttpServletRequest request, @PathVariable("id") String id) { Article currentArticle = repository.findOne(id); Article updatedArticle = objectMapper.readerForUpdating(currentArticle) .readValue(request.getReader()); repository.save(updatedArticle); } jk@devskiller.com / @jkubrynski 10 / 48
  11. 11. Caching be aware - especially IE caches aggressively jk@devskiller.com / @jkubrynski 11 / 48
  12. 12. Caching be aware - especially IE caches aggressively disable caching @Configuration public class RestConfig extends WebMvcConfigurerAdapter { @Override public void addInterceptors(InterceptorRegistry registry) { WebContentInterceptor webContentInterceptor = new WebContentInterceptor(); webContentInterceptor.setCacheSeconds(0); registry.addInterceptor(webContentInterceptor); } } jk@devskiller.com / @jkubrynski 12 / 48
  13. 13. Cache headers cache-control: public, max-age=0, no-cache public / private no-cache no-store max-age s-maxage jk@devskiller.com / @jkubrynski 13 / 48
  14. 14. Cache headers cache-control: public, max-age=0, no-cache public / private no-cache no-store max-age s-maxage ETag If-None-Match: "0d41d8cd98f00b204e9800998ecf8427e" Spring brings ShallowEtagHeaderFilter jk@devskiller.com / @jkubrynski 14 / 48
  15. 15. Compression reduces response size dramatically in Tomcat extend Connector with compression="on" compressionMinSize="2048" noCompressionUserAgents="gozilla, traviata" compressableMimeType="text/html,text/xml" jk@devskiller.com / @jkubrynski 15 / 48
  16. 16. HATEOAS self-descriptive client understands hypermedia { "name": "Alice", "links": [ { "rel": "self", "href": "/customers/1213" }, { "rel": "parent", "href": "/customers/14" }, { "rel": "currentOrder", "href": "/orders/14312" } ] } HTTP/1.1 201 Created Location: http://api.mydomain.com/orders/1234 jk@devskiller.com / @jkubrynski 16 / 48
  17. 17. HATEOAS in Spring public class Customer extends ResourceSupport { ... } // or wrap entity into Resource object jk@devskiller.com / @jkubrynski 17 / 48
  18. 18. HATEOAS in Spring public class Customer extends ResourceSupport { ... } // or wrap entity into Resource object import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*; public HttpEntity<Customer> get(@PathVariable("id") String customerId) { Customer customer = repository.findOne(customerId); String pId = customer.getBoss(); String oId = customer.currentOrderId(); customer.add(linkTo(methodOn(CustomerController.class).get(customerId)).withSelfRel()); customer.add(linkTo(methodOn(CustomerController.class).get(pId)).withRel("parent")); customer.add(linkTo(methodOn(OrderController.class).get(oId)).withRel("currentOrder")); return new ResponseEntity<Customer>(customer, HttpStatus.OK); } public ResponseEntity create(@RequestBody Customer customer) { String id = repository.save(customer); return ResponseEntity.created(linkTo(CustomerController.class).slash(id).toUri()) .build(); } jk@devskiller.com / @jkubrynski 18 / 48
  19. 19. @DanaDanger HTTP codes classification 20x: cool 30x: ask that dude over there 40x: you fucked up 50x: we fucked up jk@devskiller.com / @jkubrynski 19 / 48
  20. 20. Exceptions include detailed information { "status": 400, "code": 40483, "message": "Incorrect body signature", "moreInfo": "http://www.mycompany.com/errors/40483" } jk@devskiller.com / @jkubrynski 20 / 48
  21. 21. Exceptions include detailed information { "status": 400, "code": 40483, "message": "Incorrect body signature", "moreInfo": "http://www.mycompany.com/errors/40483" } hide stacktrace jk@devskiller.com / @jkubrynski 21 / 48
  22. 22. Handling Spring MVC exceptions @ControllerAdvice public class MyExceptionHandler extends ResponseEntityExceptionHandler { /* Handling framework exceptions */ @Override protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) { LOG.error("Spring MVC exception occurred", ex); return super.handleExceptionInternal(ex, body, headers, status, request); } /* Handling application exceptions */ @ResponseStatus(value = HttpStatus.NOT_FOUND) @ExceptionHandler(ResourceNotFoundException.class) public void handleResourceNotFound() { } } jk@devskiller.com / @jkubrynski 22 / 48
  23. 23. API Versioning don't even think about api.domain.com/v2/orders URIs to the same resources should be fixed between versions jk@devskiller.com / @jkubrynski 23 / 48
  24. 24. API Versioning don't even think about api.domain.com/v2/orders URIs to the same resources should be fixed between versions use Content-Type 1 version: application/vnd.domain+json 2 version: application/vnd.domain.v2+json jk@devskiller.com / @jkubrynski 24 / 48
  25. 25. Filtering and sorting GET /reviews?rating=5 GET /reviews?rating=5&sortAsc=author jk@devskiller.com / @jkubrynski 25 / 48
  26. 26. Filtering and sorting GET /reviews?rating=5 GET /reviews?rating=5&sortAsc=author Dynamic queries are easier in POST body jk@devskiller.com / @jkubrynski 26 / 48
  27. 27. Filtering and sorting GET /reviews?rating=5 GET /reviews?rating=5&sortAsc=author Dynamic queries are easier in POST body POST /reviews/searches GET /reviews/searches/23?page=2 jk@devskiller.com / @jkubrynski 27 / 48
  28. 28. Documentation runnable with examples Swagger jk@devskiller.com / @jkubrynski 28 / 48
  29. 29. jk@devskiller.com / @jkubrynski 29 / 48
  30. 30. Stateless or not? password hashing cost session replication load-balancing jk@devskiller.com / @jkubrynski 30 / 48
  31. 31. Stateless or not? password hashing cost session replication load-balancing ... stateless session? jk@devskiller.com / @jkubrynski 31 / 48
  32. 32. Avoiding session creation in Spring @EnableWebSecurity public class SpringSecurity extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers("/secure/**").fullyAuthenticated() .and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER) .and() .httpBasic(); } } jk@devskiller.com / @jkubrynski 32 / 48
  33. 33. Security SQL Injection XSS CSRF XXE jk@devskiller.com / @jkubrynski 33 / 48
  34. 34. HQL Injection List<Product> products = em.createQuery( "SELECT p FROM Product p where p.category = '" + categ + "'", Product.class) .getResultList(); jk@devskiller.com / @jkubrynski 34 / 48
  35. 35. HQL Injection List<Product> products = em.createQuery( "SELECT p FROM Product p where p.category = '" + categ + "'", Product.class) .getResultList(); categ = ' OR '1'='1 jk@devskiller.com / @jkubrynski 35 / 48
  36. 36. HQL Injection List<Product> products = em.createQuery( "SELECT p FROM Product p where p.category = '" + categ + "'", Product.class) .getResultList(); categ = ' OR '1'='1 SELECT __fields__ FROM products WHERE category = '' OR '1'='1' jk@devskiller.com / @jkubrynski 36 / 48
  37. 37. HQL Injection List<Product> products = em.createQuery( "SELECT p FROM Product p where p.category = '" + categ + "'", Product.class) .getResultList(); categ = ' OR '1'='1 SELECT __fields__ FROM products WHERE category = '' OR '1'='1' List<Product> products = em.createQuery( "SELECT p FROM Product p where p.category = :categ", Product.class) .setParameter("categ", categ) .getResultList(); jk@devskiller.com / @jkubrynski 37 / 48
  38. 38. HQL Injection List<Product> products = em.createQuery( "SELECT p FROM Product p where p.category = '" + categ + "'", Product.class) .getResultList(); categ = ' OR '1'='1 SELECT __fields__ FROM products WHERE category = '' OR '1'='1' List<Product> products = em.createQuery( "SELECT p FROM Product p where p.category = :categ", Product.class) .setParameter("categ", categ) .getResultList(); SELECT __fields__ FROM products WHERE category = ' OR ''1''=''1''' jk@devskiller.com / @jkubrynski 38 / 48
  39. 39. CSRF - Cross-site request forgery <img src="https://api.mybank.com/transfers/from/1233/to/1234/amount/5000"> <form action="https://api.mybank.com/transfers" method="POST"> <input type="hidden" name="from" value="1233"/> <input type="hidden" name="to" value="1234"/> <input type="hidden" name=amount" value="5000"/> <input type="submit" value="Celebrity Nude Photos!"/> </form> jk@devskiller.com / @jkubrynski 39 / 48
  40. 40. CSRF - Cross-site request forgery <img src="https://api.mybank.com/transfers/from/1233/to/1234/amount/5000"> <form action="https://api.mybank.com/transfers" method="POST"> <input type="hidden" name="from" value="1233"/> <input type="hidden" name="to" value="1234"/> <input type="hidden" name=amount" value="5000"/> <input type="submit" value="Celebrity Nude Photos!"/> </form> One time request tokens Correct CORS headers jk@devskiller.com / @jkubrynski 40 / 48
  41. 41. CORS - Cross Origin Requests Sharing Preflight request OPTIONS /cors HTTP/1.1 Origin: http://www.domain.com Access-Control-Request-Method: PUT Access-Control-Request-Headers: X-Custom-Header Host: api.mydomain.org Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0... Preflight response Access-Control-Allow-Origin: http://www.domain.com Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: X-Custom-Header Content-Type: text/html; charset=utf-8 jk@devskiller.com / @jkubrynski 41 / 48
  42. 42. XML External Entity <?xml version="1.0" encoding="utf-8"?> <comment> <text>Yeah! I like it!</text> </comment> jk@devskiller.com / @jkubrynski 42 / 48
  43. 43. XML External Entity <?xml version="1.0" encoding="utf-8"?> <comment> <text>Yeah! I like it!</text> </comment> <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE myentity [ <!ENTITY a "Yeah! I like it!"> ]> <comment> <text>&a;</text> </comment> jk@devskiller.com / @jkubrynski 43 / 48
  44. 44. XML External Entity <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE myentity [ <!ENTITY a SYSTEM "/etc/passwd"> ]> <comment> <text>&a;</text> </comment> jk@devskiller.com / @jkubrynski 44 / 48
  45. 45. XML External Entity <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE myentity [ <!ENTITY a SYSTEM "/etc/passwd"> ]> <comment> <text>&a;</text> </comment> <?xml version="1.0" encoding="utf-8"?> <comment> <text>root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin adm:x:3:4:adm:/var/adm:/sbin/nologin lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin sync:x:5:0:sync:/sbin:/bin/sync shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown halt:x:7:0:halt:/sbin:/sbin/halt ..... </text> </comment> jk@devskiller.com / @jkubrynski 45 / 48
  46. 46. XML External Entity <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE myentity [ <!ENTITY a "abcdefghij1234567890" > <!ENTITY b "&a;&a;&a;&a;&a;&a;&a;&a;&a;&a" > <!ENTITY c "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;" > <!ENTITY d "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;" > ... <!ENTITY h "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;" > ]> <comment> <text>&h;</text> </comment> jk@devskiller.com / @jkubrynski 46 / 48
  47. 47. http://knowyourmeme.com/photos/531557 thx to @mihn jk@devskiller.com / @jkubrynski 47 / 48
  48. 48. Thanks! jk@devskiller.com / @jkubrynski 48 / 48

×