The document discusses RESTful API design and security best practices. It covers REST constraints like being stateless and cacheable. It also discusses topics like POST vs PUT, filtering queries, API documentation, handling exceptions, authentication, and preventing vulnerabilities like SQL injection, cross-site scripting (XSS), cross-site request forgery (CSRF), and XML external entities (XXE).
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
6. POST vs PUT
jk@devskiller.com / @jkubrynski 6 / 48
7. POST vs PUT
POST creates new resources
jk@devskiller.com / @jkubrynski 7 / 48
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. Maybe PATCH?
no "out of the box" support
jk@devskiller.com / @jkubrynski 9 / 48
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. Caching
be aware - especially IE caches aggressively
jk@devskiller.com / @jkubrynski 11 / 48
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
17. HATEOAS in Spring
public class Customer extends ResourceSupport { ... }
// or wrap entity into Resource object
jk@devskiller.com / @jkubrynski 17 / 48
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. @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. 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. 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
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. 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. Filtering and sorting
GET /reviews?rating=5
GET /reviews?rating=5&sortAsc=author
jk@devskiller.com / @jkubrynski 25 / 48
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. 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
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. 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. 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. 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. 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