Wired up Spring security, modified test to use accounts.

This commit is contained in:
Artur Kmieckowiak 2020-02-13 13:39:32 +01:00
parent 1acd2fa781
commit bc27408b83
16 changed files with 194 additions and 85 deletions

View File

@ -25,7 +25,6 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'com.auth0:java-jwt:3.4.0'
implementation 'org.openpnp:opencv:3.2.0-0'
// developmentOnly 'org.springframework.boot:spring-boot-devtools'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation('org.junit.jupiter:junit-jupiter-api')
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine')

View File

@ -3,6 +3,7 @@ package pl.edu.amu.wmi.bookapi.api;
import org.apache.coyote.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import pl.edu.amu.wmi.bookapi.api.dto.BookDto;
@ -53,7 +54,6 @@ public class BookController {
@PostMapping
public ResponseEntity addBook(@RequestBody BookDto bookDto) {
System.out.println("Save book");
bookService.saveBook(getUserName(), bookDto);
return ResponseEntity.ok().build();
}
@ -68,6 +68,8 @@ public class BookController {
}
private String getUserName() {
return "admin";
String username = SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString();
System.out.println("Username - " + username);
return username;
}
}

View File

@ -2,6 +2,7 @@ package pl.edu.amu.wmi.bookapi.api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import pl.edu.amu.wmi.bookapi.api.dto.MessageDto;
import pl.edu.amu.wmi.bookapi.service.MessageService;
@ -21,7 +22,7 @@ public class MessageController {
public ResponseEntity listThreads() {
return ResponseEntity.ok(
messageService.getThreads(
getUserId()
getUsername()
)
);
}
@ -30,7 +31,7 @@ public class MessageController {
public ResponseEntity createMessage(@RequestBody MessageDto messageDto) {
messageService.createMessage(
messageDto.getContent(),
messageDto.getAuthor(),
getUsername(),
messageDto.getRecipient()
);
@ -39,12 +40,12 @@ public class MessageController {
@GetMapping("/{threadId}")
public ResponseEntity getMessagesInThread(@PathVariable String threadId) {
return ResponseEntity.ok(messageService.getMessagesInThread(threadId, getUserId()));
return ResponseEntity.ok(messageService.getMessagesInThread(threadId, getUsername()));
}
private String getUserId() {
return "admin";
private String getUsername() {
String username = SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString();
System.out.println("Username - " + username);
return username;
}
}

View File

@ -1,13 +1,16 @@
package pl.edu.amu.wmi.bookapi.api;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.*;
import org.springframework.web.bind.annotation.*;
import pl.edu.amu.wmi.bookapi.exceptions.RegisterException;
import pl.edu.amu.wmi.bookapi.models.*;
import pl.edu.amu.wmi.bookapi.repositories.*;
import javax.security.auth.login.LoginException;
@RestController
@RequestMapping("/users")
public class UserController {
@ -26,8 +29,14 @@ public class UserController {
user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
try {
userRepository.save(user);
System.out.println("hello");
} catch (DuplicateKeyException e) {
throw new RegisterException("Login already in use");
}
}
@PostMapping("/sign-in")
public void signIn(@RequestBody UserDocument user) throws LoginException {
Object princ = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
}

View File

@ -2,12 +2,10 @@ package pl.edu.amu.wmi.bookapi.api.dto;
public class MessageDto {
private String content;
private String author;
private String recipient;
public MessageDto(String content, String author, String recipient) {
public MessageDto(String content, String recipient) {
this.content = content;
this.author = author;
this.recipient = recipient;
}
@ -19,14 +17,6 @@ public class MessageDto {
this.content = content;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getRecipient() {
return recipient;
}

View File

@ -8,5 +8,5 @@ import java.util.List;
public interface BookRepository extends MongoRepository<BookDocument, String>, BookRepositoryCustom {
List<BookDocument> findAllByOwnerUsername(String ownerUsername);
void deleteByIdAndOwnerUsername(String id, String ownerUsername);
}

View File

@ -27,6 +27,7 @@ public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilte
public static final String TOKEN_PREFIX = "Bearer ";
public static final String HEADER_STRING = "Authorization";
public static final String SIGN_UP_URL = "/users/sign-up";
public static final String LOG_IN_URL = "/users/login";
@Override
public Authentication attemptAuthentication(HttpServletRequest req,

View File

@ -43,8 +43,6 @@ public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
.build()
.verify(token.replace(TOKEN_PREFIX, ""))
.getSubject();
System.out.println();
if (user != null) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
}

View File

@ -25,22 +25,28 @@ public class WebSecurity extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/api/books", "/api/messages/", "/api/messages/*", "/api/books/public").permitAll()
.antMatchers(HttpMethod.DELETE, "/api/books/*").permitAll()
.antMatchers(HttpMethod.PATCH, "/api/books/*").permitAll()
// .antMatchers(HttpMethod.GET, "/api/books", "/api/messages/", "/api/messages/*", "/api/books/public").permitAll()
// .antMatchers(HttpMethod.DELETE, "/api/books/*").permitAll()
// .antMatchers(HttpMethod.PATCH, "/api/books/*").permitAll()
.antMatchers(HttpMethod.POST,
SIGN_UP_URL,
"/api/books",
"/api/books/image",
"/api/messages").permitAll()
SIGN_UP_URL).permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
// this disables session creation on Spring Security
.addFilter(
this.getConfiguredJwtAuthenticationFilter()
)
.addFilter(
new JWTAuthorizationFilter(authenticationManager())
)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
private JWTAuthenticationFilter getConfiguredJwtAuthenticationFilter() throws Exception{
JWTAuthenticationFilter jwtFilter = new JWTAuthenticationFilter(authenticationManager());
jwtFilter.setFilterProcessesUrl("/users/login");
return jwtFilter;
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);

View File

@ -22,7 +22,7 @@ public class BookService {
}
public void deleteBook(String userName, String bookId) {
bookRepository.deleteById(bookId);
bookRepository.deleteByIdAndOwnerUsername(bookId, userName);
}
public List<BookDocument> findAllForUser(String userName) {

View File

@ -13,6 +13,7 @@ import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.test.web.servlet.MockMvc;
import pl.edu.amu.wmi.bookapi.fixtures.IntegrationTestUtil;
import pl.edu.amu.wmi.bookapi.fixtures.api.BookControllerRequest;
import pl.edu.amu.wmi.bookapi.fixtures.api.UserControllerRequests;
import pl.edu.amu.wmi.bookapi.models.BookDocument;
import java.util.List;
@ -37,65 +38,113 @@ public class BookControllerInt {
IntegrationTestUtil testUtil;
private BookControllerRequest bookRequests;
private UserControllerRequests userRequests;
@BeforeEach
void cleanCollections() {
this.bookRequests = new BookControllerRequest(mvc, new ObjectMapper());
this.userRequests = new UserControllerRequests(mvc, new ObjectMapper());
testUtil.cleanCollections();
}
@Test
void should_add_a_book() throws Exception {
this.bookRequests.addBook("admin", "12345", "auth", "title")
userRequests.registerUser("a-1", "p-1");
String bearer = userRequests.loginAsUserAndReturnAuthorizationHeader("a-1", "p-1");
this.bookRequests.addBook(bearer, "12345", "auth", "title")
.andExpect(status().isOk());
assertEquals(mongoTemplate.findAll(BookDocument.class).get(0).getEan(), "12345");
assertEquals(mongoTemplate.findAll(BookDocument.class).get(0).getOwnerUsername(), "a-1");
}
@Test
void should_list_books_for_user() throws Exception {
this.bookRequests.addBook("admin", "12345", "auth", "title")
userRequests.registerUser("a","b");
String user1 = userRequests.loginAsUserAndReturnAuthorizationHeader("a", "b");
userRequests.registerUser("b","b");
String user2 = userRequests.loginAsUserAndReturnAuthorizationHeader("b", "b");
this.bookRequests.addBook(user1, "12345", "auth", "title")
.andExpect(status().isOk());
this.bookRequests.addBook("admin", "12345", "auth", "title")
this.bookRequests.addBook(user1, "12345", "auth", "title")
.andExpect(status().isOk());
bookRequests.getBooksForUser("admin")
this.bookRequests.addBook(user2, "12345", "auth", "title")
.andExpect(status().isOk());
bookRequests.getBooksForUser(user1)
.andExpect(status().isOk())
.andExpect(jsonPath("$.*.ownerUsername", equalTo(List.of("admin", "admin"))));
.andExpect(jsonPath("$.*.ownerUsername", equalTo(List.of("a", "a"))));
}
@Test
void should_delete_a_book() throws Exception {
this.bookRequests.addBook("admin", "12345", "auth", "title")
userRequests.registerUser("a","b");
String user1 = userRequests.loginAsUserAndReturnAuthorizationHeader("a", "b");
userRequests.registerUser("b","b");
String user2 = userRequests.loginAsUserAndReturnAuthorizationHeader("b", "b");
this.bookRequests.addBook(user1, "12345", "auth", "title")
.andExpect(status().isOk());
BookDocument foundBook = mongoTemplate.findAll(BookDocument.class).get(0);
assertEquals(foundBook.getEan(), "12345");
this.bookRequests.deleteBook("admin", foundBook.getId());
// Should not allow other users to delete books
this.bookRequests.deleteBook(user2, foundBook.getId());
assertEquals(1, mongoTemplate.findAll(BookDocument.class).size());
this.bookRequests.deleteBook(user1, foundBook.getId());
assertEquals(0, mongoTemplate.findAll(BookDocument.class).size());
}
@Test
void should_list_all_books() throws Exception {
this.bookRequests.addBook("admin1", "123451", "auth1", "title1")
userRequests.registerUser("a-1", "p-1");
String bearer1 = userRequests.loginAsUserAndReturnAuthorizationHeader("a-1", "p-1");
userRequests.registerUser("a-2", "p-1");
String bearer2 = userRequests.loginAsUserAndReturnAuthorizationHeader("a-1", "p-1");
userRequests.registerUser("a-3", "p-1");
String bearer3 = userRequests.loginAsUserAndReturnAuthorizationHeader("a-1", "p-1");
userRequests.registerUser("a-4", "p-1");
String bearer4 = userRequests.loginAsUserAndReturnAuthorizationHeader("a-1", "p-1");
this.bookRequests.addBook(bearer1, "123451", "auth1", "title1")
.andExpect(status().isOk());
this.bookRequests.addBook("admin2", "123452", "auth2", "title2")
this.bookRequests.addBook(bearer2, "123452", "auth2", "title2")
.andExpect(status().isOk());
this.bookRequests.addBook("admin3", "123453", "auth3", "title3")
this.bookRequests.addBook(bearer3, "123453", "auth3", "title3")
.andExpect(status().isOk());
this.bookRequests.getAllBooks()
this.bookRequests.getAllBooks(bearer4)
.andExpect(status().isOk())
.andExpect(jsonPath("$").isArray());
}
@Test
void should_update_a_book() throws Exception {
this.bookRequests.addBook("admin", "12345", "1", "2")
.andDo(print());
userRequests.registerUser("a-1", "p-1");
String bearer = userRequests.loginAsUserAndReturnAuthorizationHeader("a-1", "p-1");
userRequests.registerUser("a-2", "p-1");
String otherUser = userRequests.loginAsUserAndReturnAuthorizationHeader("a-2", "p-1");
this.bookRequests.addBook(bearer, "12345", "1", "2")
.andExpect(status().isOk());
BookDocument foundBook = mongoTemplate.findAll(BookDocument.class).get(0);
String bookId = foundBook.getId();
ObjectMapper objectMapper = new ObjectMapper();
this.bookRequests.updateBook(bookId, "admin",
this.bookRequests.updateBook(bookId, bearer,
"{\n" +
" \"ean\": " + objectMapper.writeValueAsString("ean") + ",\n" +
" \"author\": " + objectMapper.writeValueAsString("author") + ",\n" +
@ -109,15 +158,25 @@ public class BookControllerInt {
assertEquals("title", foundBookAfterUpdate.getTitle());
// And should allow for partial update
this.bookRequests.updateBook(bookId, "admin",
"{\n" +
" \"ean\": " + objectMapper.writeValueAsString("ean-1-modified") + "\n" +
"}").andExpect(status().isOk());
this.bookRequests.updateBook(bookId, bearer,
"{\n" +
"\"ean\": " + objectMapper.writeValueAsString("ean-1-modified") + "\n" +
"}").andExpect(status().isOk());
BookDocument foundBookAfterPartialUpdate = mongoTemplate.findAll(BookDocument.class).get(0);
assertEquals("ean-1-modified", foundBookAfterPartialUpdate.getEan());
assertEquals("author", foundBookAfterPartialUpdate.getAuthor());
assertEquals("title", foundBookAfterPartialUpdate.getTitle());
// And should not allow other users to modify book
this.bookRequests.updateBook(bookId, otherUser, "{\n" +
"\"ean\": " + objectMapper.writeValueAsString("abc") + "\n" +
"}");
BookDocument foundBookAfterOtherUserUpdate = mongoTemplate.findAll(BookDocument.class).get(0);
assertEquals("ean-1-modified", foundBookAfterOtherUserUpdate.getEan());
}
}

View File

@ -14,6 +14,7 @@ import org.springframework.test.web.servlet.MockMvc;
import pl.edu.amu.wmi.bookapi.fixtures.IntegrationTestUtil;
import pl.edu.amu.wmi.bookapi.fixtures.api.BookControllerRequest;
import pl.edu.amu.wmi.bookapi.fixtures.api.MessageControllerRequests;
import pl.edu.amu.wmi.bookapi.fixtures.api.UserControllerRequests;
import pl.edu.amu.wmi.bookapi.models.MessageDocument;
import pl.edu.amu.wmi.bookapi.models.ThreadDocument;
@ -36,19 +37,24 @@ public class MessageControllerInt {
IntegrationTestUtil testUtil;
private MessageControllerRequests messageControllerRequests;
private UserControllerRequests userControllerRequests;
@BeforeEach
void cleanCollections() {
this.messageControllerRequests = new MessageControllerRequests(mvc, new ObjectMapper());
this.userControllerRequests = new UserControllerRequests(mvc, new ObjectMapper());
testUtil.cleanCollections();
}
@Test
void should_start_new_thread_if_does_not_exist() throws Exception {
userControllerRequests.registerUser("a","b");
String user1 = userControllerRequests.loginAsUserAndReturnAuthorizationHeader("a", "b");
messageControllerRequests.postMessage(
"content",
"id-1",
"id-2"
user1
).andExpect(status().isOk());;
assertEquals(1, mongoTemplate.findAll(ThreadDocument.class).size());
assertEquals(1, mongoTemplate.findAll(MessageDocument.class).size());
@ -56,18 +62,27 @@ public class MessageControllerInt {
@Test
void if_thread_exists_it_should_not_create_new() throws Exception {
userControllerRequests.registerUser("a","b");
String user1 = userControllerRequests.loginAsUserAndReturnAuthorizationHeader("a", "b");
userControllerRequests.registerUser("b","b");
String user2 = userControllerRequests.loginAsUserAndReturnAuthorizationHeader("b", "b");
userControllerRequests.registerUser("c","b");
String user3 = userControllerRequests.loginAsUserAndReturnAuthorizationHeader("c", "b");
messageControllerRequests.postMessage(
"content",
"id-1",
"id-2"
"b",
user1
).andExpect(status().isOk());
assertEquals(1, mongoTemplate.findAll(ThreadDocument.class).size());
assertEquals(1, mongoTemplate.findAll(MessageDocument.class).size());
messageControllerRequests.postMessage(
"content",
"id-1",
"id-2"
"a",
user2
).andExpect(status().isOk());;
assertEquals(1, mongoTemplate.findAll(ThreadDocument.class).size());
@ -75,8 +90,8 @@ public class MessageControllerInt {
messageControllerRequests.postMessage(
"content",
"id-1",
"id-5"
"b",
user3
).andExpect(status().isOk());
assertEquals(2, mongoTemplate.findAll(ThreadDocument.class).size());
@ -85,10 +100,14 @@ public class MessageControllerInt {
@Test
void it_should_allow_to_send_a_message() throws Exception {
userControllerRequests.registerUser("a","b");
String user1 = userControllerRequests.loginAsUserAndReturnAuthorizationHeader("a", "b");
messageControllerRequests.postMessage(
"content",
"id-1",
"id-5"
"id-5",
user1
).andExpect(status().isOk());
assertEquals(1, mongoTemplate.findAll(ThreadDocument.class).size());

View File

@ -14,6 +14,7 @@ import pl.edu.amu.wmi.bookapi.fixtures.api.*;
import pl.edu.amu.wmi.bookapi.models.UserDocument;
import static org.junit.Assert.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest
@ -54,4 +55,12 @@ public class UserControllerInt{
.andExpect(status().is4xxClientError());
assertEquals(mongoTemplate.findAll(UserDocument.class).size(), 1);
}
@Test
void should_return_jwt_as_header_when_loggin_in() throws Exception {
userRequests.registerUser("a","b")
.andExpect(status().isOk());
String authHeader = userRequests.loginAsUserAndReturnAuthorizationHeader("a","b");
}
}

View File

@ -4,7 +4,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import pl.edu.amu.wmi.bookapi.models.BookDocument;
import java.net.URI;
@ -19,31 +18,35 @@ public class BookControllerRequest {
this.objectMapper = objectMapper;
}
public ResultActions getBooksForUser(String userName) throws Exception {
return mvc.perform(get("/api/books"));
public ResultActions getBooksForUser(String bearer) throws Exception {
return mvc.perform(get("/api/books").header("Authorization", bearer));
}
public ResultActions getAllBooks() throws Exception {
return mvc.perform(get("/api/books/public"));
public ResultActions getAllBooks(String bearer) throws Exception {
return mvc.perform(get("/api/books/public")
.header("Authorization", bearer));
}
public ResultActions updateBook(String bookId, String userId, String jsonBody) throws Exception {
public ResultActions updateBook(String bookId, String bearer, String jsonBody) throws Exception {
return mvc.perform(patch("/api/books/" + bookId)
.header("Authorization", bearer)
.contentType(MediaType.APPLICATION_JSON)
.content(jsonBody));
}
public ResultActions deleteBook(String userName, String bookId) throws Exception {
return mvc.perform(delete("/api/books/" + bookId));
public ResultActions deleteBook(String bearer, String bookId) throws Exception {
return mvc.perform(delete("/api/books/" + bookId)
.header("Authorization", bearer));
}
public ResultActions addBook(
String userName,
String bearer,
String ean,
String author,
String title
) throws Exception {
return mvc.perform(post(URI.create("/api/books"))
.header("Authorization", bearer)
.contentType(MediaType.APPLICATION_JSON)
.content("{\n" +
" \"ean\": " + objectMapper.writeValueAsString(ean) + ",\n" +

View File

@ -17,27 +17,22 @@ public class MessageControllerRequests {
this.objectMapper = objectMapper;
}
public ResultActions getThreads(String userId) throws Exception {
return mvc.perform(get("/api/messages"));
public ResultActions getThreads(String bearer) throws Exception {
return mvc.perform(get("/api/messages")
.header("Authorization", bearer));
}
public ResultActions getMessages(String userId, String threadId) throws Exception {
return mvc.perform(get("/api/messages" + threadId));
public ResultActions getMessages(String bearer, String threadId) throws Exception {
return mvc.perform(get("/api/messages" + threadId)
.header("Authorization", bearer));
}
public ResultActions postMessage(String content, String author, String recipient) throws Exception {
System.out.println("Content");
System.out.println("{\n" +
"\"content\": " + objectMapper.writeValueAsString(content) + ",\n" +
"\"author\": " + objectMapper.writeValueAsString(author) + ",\n" +
"\"recipient\": " + objectMapper.writeValueAsString(recipient) + "\n" +
"}");
public ResultActions postMessage(String content, String recipient, String bearer) throws Exception {
return mvc.perform(post("/api/messages")
.header("Authorization", bearer)
.contentType(MediaType.APPLICATION_JSON)
.content("{\n" +
"\"content\": " + objectMapper.writeValueAsString(content) + ",\n" +
"\"author\": " + objectMapper.writeValueAsString(author) + ",\n" +
"\"recipient\": " + objectMapper.writeValueAsString(recipient) + "\n" +
"}")
);

View File

@ -8,6 +8,8 @@ import org.springframework.test.web.servlet.ResultActions;
import java.net.URI;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public class UserControllerRequests {
@ -28,4 +30,20 @@ public class UserControllerRequests {
"}"));
}
public ResultActions loginUser(String userName, String password) throws Exception {
return mvc.perform(post(URI.create("/users/login"))
.contentType(MediaType.APPLICATION_JSON)
.content("{\n" +
" \"username\": " + objectMapper.writeValueAsString(userName) + ",\n" +
" \"password\": " + objectMapper.writeValueAsString(password) + "\n" +
"}"))
.andDo(print());
}
public String loginAsUserAndReturnAuthorizationHeader(String username, String password) throws Exception {
return loginUser(username, password)
.andExpect(status().isOk())
.andReturn().getResponse().getHeader("Authorization");
}
}