BAEL-684 Adding spring JPA to resource servers

This commit is contained in:
tschiman 2017-02-11 19:52:16 +01:00 committed by slavisa-baeldung
parent 5f0f26e374
commit 2ee0a8a7a1
18 changed files with 487 additions and 133 deletions

View File

@ -1,69 +0,0 @@
package com.baeldung.spring.cloud.bootstrap.gateway;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
public class GatewayApplicationLiveTest {
@Test
public void testAccess() throws Exception {
TestRestTemplate testRestTemplate = new TestRestTemplate();
String testUrl = "http://localhost:8080";
ResponseEntity<String> response = testRestTemplate.getForEntity(testUrl + "/book-service/books", String.class);
Assert.assertEquals(HttpStatus.OK, response.getStatusCode());
Assert.assertNotNull(response.getBody());
//try the protected resource and confirm the redirect to login
response = testRestTemplate.getForEntity(testUrl + "/book-service/books/1", String.class);
Assert.assertEquals(HttpStatus.FOUND, response.getStatusCode());
Assert.assertEquals("http://localhost:8080/login", response.getHeaders().get("Location").get(0));
//login as user/password
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
form.add("username", "user");
form.add("password", "password");
response = testRestTemplate.postForEntity(testUrl + "/login", form, String.class);
//extract the session from the cookie and propagate it to the next request
String sessionCookie = response.getHeaders().get("Set-Cookie").get(0).split(";")[0];
HttpHeaders headers = new HttpHeaders();
headers.add("Cookie", sessionCookie);
HttpEntity<String> httpEntity = new HttpEntity<>(headers);
//request the protected resource
response = testRestTemplate.exchange(testUrl + "/book-service/books/1", HttpMethod.GET, httpEntity, String.class);
Assert.assertEquals(HttpStatus.OK, response.getStatusCode());
Assert.assertNotNull(response.getBody());
//request the admin protected resource to determine it is still protected
response = testRestTemplate.exchange(testUrl + "/rating-service/ratings/all", HttpMethod.GET, httpEntity, String.class);
Assert.assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode());
//login as the admin
form.clear();
form.add("username", "admin");
form.add("password", "admin");
response = testRestTemplate.postForEntity(testUrl + "/login", form, String.class);
//extract the session from the cookie and propagate it to the next request
sessionCookie = response.getHeaders().get("Set-Cookie").get(0).split(";")[0];
headers = new HttpHeaders();
headers.add("Cookie", sessionCookie);
httpEntity = new HttpEntity<>(headers);
//request the protected resource
response = testRestTemplate.exchange(testUrl + "/rating-service/ratings/all", HttpMethod.GET, httpEntity, String.class);
Assert.assertEquals(HttpStatus.OK, response.getStatusCode());
Assert.assertNotNull(response.getBody());
//request the discovery resources as the admin
response = testRestTemplate.exchange(testUrl + "/discovery", HttpMethod.GET, httpEntity, String.class);
Assert.assertEquals(HttpStatus.OK, response.getStatusCode());
}
}

View File

@ -0,0 +1,201 @@
package com.baeldung.spring.cloud.bootstrap.gateway;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.apache.http.entity.ContentType;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
public class IntegrationTest {
private TestRestTemplate testRestTemplate = new TestRestTemplate();
private String testUrl = "http://localhost:8080";
@Test
public void testAccess() throws Exception {
ResponseEntity<String> response = testRestTemplate.getForEntity(testUrl + "/book-service/books", String.class);
Assert.assertEquals(HttpStatus.OK, response.getStatusCode());
Assert.assertNotNull(response.getBody());
//try the protected resource and confirm the redirect to login
response = testRestTemplate.getForEntity(testUrl + "/book-service/books/1", String.class);
Assert.assertEquals(HttpStatus.FOUND, response.getStatusCode());
Assert.assertEquals("http://localhost:8080/login", response.getHeaders().get("Location").get(0));
//login as user/password
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
form.add("username", "user");
form.add("password", "password");
response = testRestTemplate.postForEntity(testUrl + "/login", form, String.class);
//extract the session from the cookie and propagate it to the next request
String sessionCookie = response.getHeaders().get("Set-Cookie").get(0).split(";")[0];
HttpHeaders headers = new HttpHeaders();
headers.add("Cookie", sessionCookie);
HttpEntity<String> httpEntity = new HttpEntity<>(headers);
addBook();
//request the protected resource
response = testRestTemplate.exchange(testUrl + "/book-service/books/1", HttpMethod.GET, httpEntity, String.class);
Assert.assertEquals(HttpStatus.OK, response.getStatusCode());
Assert.assertNotNull(response.getBody());
addRatings();
//request the admin protected resource to determine it is still protected
response = testRestTemplate.exchange(testUrl + "/rating-service/ratings", HttpMethod.GET, httpEntity, String.class);
Assert.assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode());
//login as the admin
form.clear();
form.add("username", "admin");
form.add("password", "admin");
response = testRestTemplate.postForEntity(testUrl + "/login", form, String.class);
//extract the session from the cookie and propagate it to the next request
sessionCookie = response.getHeaders().get("Set-Cookie").get(0).split(";")[0];
headers = new HttpHeaders();
headers.add("Cookie", sessionCookie);
httpEntity = new HttpEntity<>(headers);
//request the protected resource
response = testRestTemplate.exchange(testUrl + "/rating-service/ratings", HttpMethod.GET, httpEntity, String.class);
Assert.assertEquals(HttpStatus.OK, response.getStatusCode());
Assert.assertNotNull(response.getBody());
//request the discovery resources as the admin
response = testRestTemplate.exchange(testUrl + "/discovery", HttpMethod.GET, httpEntity, String.class);
Assert.assertEquals(HttpStatus.OK, response.getStatusCode());
}
private void addRatings() {
//login as user/password
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
form.add("username", "user");
form.add("password", "password");
ResponseEntity<String> response = testRestTemplate.postForEntity(testUrl + "/login", form, String.class);
//extract the session from the cookie and propagate it to the next request
String sessionCookie = response.getHeaders().get("Set-Cookie").get(0).split(";")[0];
HttpHeaders headers = new HttpHeaders();
headers.add("Cookie", sessionCookie);
headers.add("ContentType", ContentType.APPLICATION_JSON.getMimeType());
Rating rating = new Rating(1L, 4);
HttpEntity<Rating> httpEntity = new HttpEntity<>(rating, headers);
//request the protected resource
ResponseEntity<Rating> bookResponse = testRestTemplate.postForEntity(testUrl + "/rating-service/ratings", httpEntity, Rating.class);
Assert.assertEquals(HttpStatus.OK, bookResponse.getStatusCode());
Assert.assertEquals(rating.getBookId(), bookResponse.getBody().getBookId());
Assert.assertEquals(rating.getStars(), bookResponse.getBody().getStars());
}
private void addBook(){
//login as user/password
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
form.add("username", "admin");
form.add("password", "admin");
ResponseEntity<String> response = testRestTemplate.postForEntity(testUrl + "/login", form, String.class);
//extract the session from the cookie and propagate it to the next request
String sessionCookie = response.getHeaders().get("Set-Cookie").get(0).split(";")[0];
HttpHeaders headers = new HttpHeaders();
headers.add("Cookie", sessionCookie);
headers.add("ContentType", ContentType.APPLICATION_JSON.getMimeType());
Book book = new Book("Baeldung", "How to spring cloud");
HttpEntity<Book> httpEntity = new HttpEntity<>(book, headers);
//request the protected resource
ResponseEntity<Book> bookResponse = testRestTemplate.postForEntity(testUrl + "/book-service/books", httpEntity, Book.class);
Assert.assertEquals(HttpStatus.OK, bookResponse.getStatusCode());
Assert.assertEquals(book.getAuthor(), bookResponse.getBody().getAuthor());
Assert.assertEquals(book.getTitle(), bookResponse.getBody().getTitle());
}
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Book {
private Long id;
private String author;
private String title;
public Book() {
}
public Book(String author, String title) {
this.author = author;
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Rating {
private Long id;
private Long bookId;
private int stars;
public Rating() {
}
public Rating(Long bookId, int stars) {
this.bookId = bookId;
this.stars = stars;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getBookId() {
return bookId;
}
public void setBookId(Long bookId) {
this.bookId = bookId;
}
public int getStars() {
return stars;
}
public void setStars(int stars) {
this.stars = stars;
}
}
}

View File

@ -42,6 +42,17 @@
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

@ -3,35 +3,11 @@ package com.baeldung.spring.cloud.bootstrap.svcbook;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.List;
@SpringBootApplication
@EnableEurekaClient
@RestController
@RequestMapping("/books")
public class BookServiceApplication {
public static void main(String[] args) {
SpringApplication.run(BookServiceApplication.class, args);
}
private List<Book> bookList = Arrays.asList(
new Book(1L, "Baeldung goes to the market", "Tim Schimandle"),
new Book(2L, "Baeldung goes to the park", "Slavisa")
);
@GetMapping("")
public List<Book> findAllBooks() {
return bookList;
}
@GetMapping("/{bookId}")
public Book findBook(@PathVariable Long bookId) {
return bookList.stream().filter(b -> b.getId().equals(bookId)).findFirst().orElse(null);
}
}

View File

@ -2,6 +2,7 @@ package com.baeldung.spring.cloud.bootstrap.svcbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@ -22,8 +23,11 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
http.httpBasic()
.disable()
.authorizeRequests()
.antMatchers("/books").permitAll()
.antMatchers("/books/*").hasAnyRole("USER", "ADMIN")
.antMatchers(HttpMethod.GET, "/books").permitAll()
.antMatchers(HttpMethod.GET, "/books/*").permitAll()
.antMatchers(HttpMethod.POST, "/books").hasRole("ADMIN")
.antMatchers(HttpMethod.PATCH, "/books/*").hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE, "/books/*").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.csrf()

View File

@ -1,16 +1,21 @@
package com.baeldung.spring.cloud.bootstrap.svcbook;
package com.baeldung.spring.cloud.bootstrap.svcbook.book;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
@JsonIgnoreProperties(ignoreUnknown = true)
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String author;
private String title;
public Book(Long id, String title, String author) {
this.id = id;
this.author = author;
this.title = title;
}
public Book() {
}

View File

@ -0,0 +1,40 @@
package com.baeldung.spring.cloud.bootstrap.svcbook.book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private BookService bookService;
@GetMapping("")
public List<Book> findAllBooks() {
return bookService.findAllBooks();
}
@GetMapping("/{bookId}")
public Book findBook(@PathVariable Long bookId) {
return bookService.findBookById(bookId);
}
@PostMapping("")
public Book createBook(@RequestBody Book book) {
return bookService.createBook(book);
}
@DeleteMapping("/{bookId}")
public void deleteBook(@PathVariable Long bookId) {
bookService.deleteBook(bookId);
}
@PatchMapping("/{bookId")
public Book updateBook(@RequestBody Map<String, String> updates, @PathVariable Long bookId) {
return bookService.updateBook(updates, bookId);
}
}

View File

@ -0,0 +1,11 @@
package com.baeldung.spring.cloud.bootstrap.svcbook.book;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
class BookNotFoundException extends RuntimeException {
BookNotFoundException(String message) {
super(message);
}
}

View File

@ -0,0 +1,6 @@
package com.baeldung.spring.cloud.bootstrap.svcbook.book;
import org.springframework.data.jpa.repository.JpaRepository;
interface BookRepository extends JpaRepository<Book, Long>{
}

View File

@ -0,0 +1,55 @@
package com.baeldung.spring.cloud.bootstrap.svcbook.book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Service
@Transactional(readOnly = true)
public class BookService {
@Autowired
private BookRepository bookRepository;
public List<Book> findAllBooks() {
return bookRepository.findAll();
}
public Book findBookById(Long bookId) {
return Optional.ofNullable(bookRepository.findOne(bookId))
.orElseThrow(() -> new BookNotFoundException("Book not found. ID: " + bookId));
}
@Transactional(propagation = Propagation.REQUIRED)
public Book createBook(Book book) {
Book newBook = new Book();
newBook.setTitle(book.getTitle());
newBook.setAuthor(book.getAuthor());
return bookRepository.save(newBook);
}
@Transactional(propagation = Propagation.REQUIRED)
public void deleteBook(Long bookId) {
bookRepository.delete(bookId);
}
@Transactional(propagation = Propagation.REQUIRED)
public Book updateBook(Map<String, String> updates, Long bookId) {
Book book = findBookById(bookId);
updates.keySet().forEach(key -> {
switch (key) {
case "author":
book.setAuthor(updates.get(key));
break;
case "title":
book.setTitle(updates.get(key));
}
});
return bookRepository.save(book);
}
}

View File

@ -42,6 +42,17 @@
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

@ -3,39 +3,11 @@ package com.baeldung.spring.cloud.bootstrap.svcrating;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@SpringBootApplication
@EnableEurekaClient
@RestController
@RequestMapping("/ratings")
public class RatingServiceApplication {
public static void main(String[] args) {
SpringApplication.run(RatingServiceApplication.class, args);
}
private List<Rating> ratingList = Arrays.asList(
new Rating(1L, 1L, 2),
new Rating(2L, 1L, 3),
new Rating(3L, 2L, 4),
new Rating(4L, 2L, 5)
);
@GetMapping("")
public List<Rating> findRatingsByBookId(@RequestParam Long bookId) {
return bookId == null || bookId.equals(0L) ? Collections.EMPTY_LIST : ratingList.stream().filter(r -> r.getBookId().equals(bookId)).collect(Collectors.toList());
}
@GetMapping("/all")
public List<Rating> findAllRatings() {
return ratingList;
}
}

View File

@ -2,6 +2,7 @@ package com.baeldung.spring.cloud.bootstrap.svcrating;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@ -22,8 +23,11 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
http.httpBasic()
.disable()
.authorizeRequests()
.antMatchers("/ratings").hasRole("USER")
.antMatchers("/ratings/all").hasRole("ADMIN")
.regexMatchers("^/ratings\\?bookId.*$").authenticated()
.antMatchers(HttpMethod.POST,"/ratings").authenticated()
.antMatchers(HttpMethod.PATCH,"/ratings/*").hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE,"/ratings/*").hasRole("ADMIN")
.antMatchers(HttpMethod.GET,"/ratings").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.csrf()

View File

@ -1,6 +1,18 @@
package com.baeldung.spring.cloud.bootstrap.svcrating;
package com.baeldung.spring.cloud.bootstrap.svcrating.rating;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
@JsonIgnoreProperties(ignoreUnknown = true)
public class Rating {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private Long bookId;
private int stars;

View File

@ -0,0 +1,38 @@
package com.baeldung.spring.cloud.bootstrap.svcrating.rating;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/ratings")
public class RatingController {
@Autowired
private RatingService ratingService;
@GetMapping("")
public List<Rating> findRatingsByBookId(@RequestParam(required = false, defaultValue = "0") Long bookId) {
if (bookId.equals(0L)) {
return ratingService.findAllRatings();
}
return ratingService.findRatingsByBookId(bookId);
}
@PostMapping("")
public Rating createRating(@RequestBody Rating rating) {
return ratingService.createRating(rating);
}
@DeleteMapping("/{ratingId}")
public void deleteRating(@PathVariable Long ratingId) {
ratingService.deleteRating(ratingId);
}
@PatchMapping("/{ratingId")
public Rating updateRating(@RequestBody Map<String, String> updates, @PathVariable Long ratingId) {
return ratingService.updateRating(updates, ratingId);
}
}

View File

@ -0,0 +1,11 @@
package com.baeldung.spring.cloud.bootstrap.svcrating.rating;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
class RatingNotFoundException extends RuntimeException {
RatingNotFoundException(String message) {
super(message);
}
}

View File

@ -0,0 +1,9 @@
package com.baeldung.spring.cloud.bootstrap.svcrating.rating;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
interface RatingRepository extends JpaRepository<Rating, Long>{
List<Rating> findRatingsByBookId(Long bookId);
}

View File

@ -0,0 +1,57 @@
package com.baeldung.spring.cloud.bootstrap.svcrating.rating;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Service
@Transactional(readOnly = true)
public class RatingService {
@Autowired
private RatingRepository ratingRepository;
public Rating findRatingById(Long ratingId) {
return Optional.ofNullable(ratingRepository.findOne(ratingId))
.orElseThrow(() -> new RatingNotFoundException("Rating not found. ID: " + ratingId));
}
public List<Rating> findRatingsByBookId(Long bookId) {
return ratingRepository.findRatingsByBookId(bookId);
}
public List<Rating> findAllRatings() {
return ratingRepository.findAll();
}
@Transactional(propagation = Propagation.REQUIRED)
public Rating createRating(Rating rating) {
Rating newRating = new Rating();
newRating.setBookId(rating.getBookId());
newRating.setStars(rating.getStars());
return ratingRepository.save(newRating);
}
@Transactional(propagation = Propagation.REQUIRED)
public void deleteRating(Long ratingId) {
ratingRepository.delete(ratingId);
}
@Transactional(propagation = Propagation.REQUIRED)
public Rating updateRating(Map<String, String> updates, Long ratingId) {
Rating rating = findRatingById(ratingId);
updates.keySet().forEach(key -> {
switch (key) {
case "stars":
rating.setStars(Integer.parseInt(updates.get(key)));
break;
}
});
return ratingRepository.save(rating);
}
}