Compare commits

...

3 Commits

Author SHA1 Message Date
30aff2259e SpringSecurity: testing basic authentication
- add autentication chain rule
- create test user
- add auth data to test requests
2023-07-23 15:21:36 +02:00
7963542c9b SpringSecurity: minimal do-nothing implementtaion
- add SpringSecurity dependancie
- Minimal do-nothing SecurityConfig.java
2023-07-23 14:50:20 +02:00
6cd4088522 SpringSecurity: add owner to the CashCard data contract
updated:
- tests
- sql scripts
2023-07-23 14:08:37 +02:00
9 changed files with 107 additions and 28 deletions

View File

@ -21,6 +21,8 @@ dependencies {
implementation 'org.springframework.data:spring-data-jdbc' implementation 'org.springframework.data:spring-data-jdbc'
testImplementation 'com.h2database:h2' testImplementation 'com.h2database:h2'
implementation 'org.springframework.boot:spring-boot-starter-security'
} }
tasks.named('test') { tasks.named('test') {

View File

@ -2,5 +2,5 @@ package djmil.cashcard;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
public record CashCard(@Id Long id, Double amount) { public record CashCard(@Id Long id, Double amount, String owner) {
} }

View File

@ -0,0 +1,44 @@
package djmil.cashcard;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests()
.requestMatchers("/cashcards/**")
.authenticated()
.and()
.csrf().disable()
.httpBasic();
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService testOnlyUsers(PasswordEncoder passwordEncoder) {
User.UserBuilder users = User.builder();
UserDetails sarah = users
.username("sarah1")
.password(passwordEncoder.encode("abc123"))
.roles() // No roles for now
.build();
return new InMemoryUserDetailsManager(sarah);
}
}

View File

@ -25,9 +25,9 @@ public class CashCardJsonTest {
@BeforeEach @BeforeEach
void setUp() { void setUp() {
cashCards = Arrays.array( cashCards = Arrays.array(
new CashCard(99L, 123.45), new CashCard(99L, 123.45, "sarah1"),
new CashCard(100L, 1.00), new CashCard(100L, 1.00, "sarah1"),
new CashCard(101L, 150.00)); new CashCard(101L, 150.00, "sarah1"));
} }
@Test @Test
@ -37,7 +37,7 @@ public class CashCardJsonTest {
@Test @Test
public void cashCardSerializationTest() throws IOException { public void cashCardSerializationTest() throws IOException {
CashCard cashCard = new CashCard(99L, 123.45); CashCard cashCard = new CashCard(99L, 123.45, "sarah1");
assertThat(json.write(cashCard)).isStrictlyEqualToJson("expected.json"); assertThat(json.write(cashCard)).isStrictlyEqualToJson("expected.json");
@ -55,11 +55,12 @@ public class CashCardJsonTest {
String expected = """ String expected = """
{ {
"id":1000, "id":1000,
"amount":67.89 "amount":67.89,
"owner": "sarah1"
} }
"""; """;
assertThat(json.parse(expected)).isEqualTo(new CashCard(1000L, 67.89)); assertThat(json.parse(expected)).isEqualTo(new CashCard(1000L, 67.89, "sarah1"));
assertThat(json.parseObject(expected).id()).isEqualTo(1000); assertThat(json.parseObject(expected).id()).isEqualTo(1000);
assertThat(json.parseObject(expected).amount()).isEqualTo(67.89); assertThat(json.parseObject(expected).amount()).isEqualTo(67.89);
@ -74,9 +75,9 @@ public class CashCardJsonTest {
void cashCardListDeserializationTest() throws IOException { void cashCardListDeserializationTest() throws IOException {
String expected=""" String expected="""
[ [
{ "id": 99, "amount": 123.45 }, { "id": 99, "amount": 123.45, "owner": "sarah1" },
{ "id": 100, "amount": 1.00 }, { "id": 100, "amount": 1.00, "owner": "sarah1" },
{ "id": 101, "amount": 150.00 } { "id": 101, "amount": 150.00, "owner": "sarah1" }
] ]
"""; """;
assertThat(jsonList.parse(expected)).isEqualTo(cashCards); assertThat(jsonList.parse(expected)).isEqualTo(cashCards);

View File

@ -31,7 +31,9 @@ class CashcardApplicationTests {
@Test @Test
void shouldReturnACashCardWhenDataIsSaved() { void shouldReturnACashCardWhenDataIsSaved() {
ResponseEntity<String> response = restTemplate.getForEntity("/cashcards/99", String.class); ResponseEntity<String> response = restTemplate
.withBasicAuth("sarah1", "abc123")
.getForEntity("/cashcards/99", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
@ -46,7 +48,9 @@ class CashcardApplicationTests {
@Test @Test
void shouldNotReturnACashCardWithAnUnknownId() { void shouldNotReturnACashCardWithAnUnknownId() {
ResponseEntity<String> response = restTemplate.getForEntity("/cashcards/1000", String.class); ResponseEntity<String> response = restTemplate
.withBasicAuth("sarah1", "abc123")
.getForEntity("/cashcards/1000", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
assertThat(response.getBody()).isBlank(); assertThat(response.getBody()).isBlank();
@ -55,16 +59,21 @@ class CashcardApplicationTests {
@Test @Test
@DirtiesContext @DirtiesContext
void shouldCreateANewCashCard() { void shouldCreateANewCashCard() {
CashCard newCashCard = new CashCard(null, 250.00); CashCard newCashCard = new CashCard(null, 250.00, "sarah1");
ResponseEntity<Void> createResponse = restTemplate.postForEntity("/cashcards", newCashCard, Void.class ); ResponseEntity<Void> createResponse = restTemplate
.withBasicAuth("sarah1", "abc123")
.postForEntity("/cashcards", newCashCard, Void.class );
assertThat(createResponse.getStatusCode()).isEqualTo(HttpStatus.CREATED); assertThat(createResponse.getStatusCode()).isEqualTo(HttpStatus.CREATED);
// Validate created CashCard
URI locationOfNewCashCard = createResponse.getHeaders().getLocation(); URI locationOfNewCashCard = createResponse.getHeaders().getLocation();
ResponseEntity<String> getResponse = restTemplate.getForEntity(locationOfNewCashCard, String.class); ResponseEntity<String> getResponse = restTemplate
.withBasicAuth("sarah1", "abc123")
.getForEntity(locationOfNewCashCard, String.class);
assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
// Validate created CashCard JSON
DocumentContext documentContext = JsonPath.parse(getResponse.getBody()); DocumentContext documentContext = JsonPath.parse(getResponse.getBody());
Number id = documentContext.read("$.id"); Number id = documentContext.read("$.id");
Double amount = documentContext.read("$.amount"); Double amount = documentContext.read("$.amount");
@ -75,7 +84,9 @@ class CashcardApplicationTests {
@Test @Test
void shouldReturnAllCashCardsWhenListIsRequested() { void shouldReturnAllCashCardsWhenListIsRequested() {
ResponseEntity<String> response = restTemplate.getForEntity("/cashcards", String.class); ResponseEntity<String> response = restTemplate
.withBasicAuth("sarah1", "abc123")
.getForEntity("/cashcards", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
DocumentContext documentContext = JsonPath.parse(response.getBody()); DocumentContext documentContext = JsonPath.parse(response.getBody());
@ -91,7 +102,9 @@ class CashcardApplicationTests {
@Test @Test
void shouldReturnAPageOfCashCards() { void shouldReturnAPageOfCashCards() {
ResponseEntity<String> response = restTemplate.getForEntity("/cashcards?page=0&size=1", String.class); ResponseEntity<String> response = restTemplate
.withBasicAuth("sarah1", "abc123")
.getForEntity("/cashcards?page=0&size=1", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
DocumentContext documentContext = JsonPath.parse(response.getBody()); DocumentContext documentContext = JsonPath.parse(response.getBody());
@ -101,7 +114,9 @@ class CashcardApplicationTests {
@Test @Test
void shouldReturnASortedPageOfCashCards() { void shouldReturnASortedPageOfCashCards() {
ResponseEntity<String> response = restTemplate.getForEntity("/cashcards?page=0&size=1&sort=amount,desc", String.class); ResponseEntity<String> response = restTemplate
.withBasicAuth("sarah1", "abc123")
.getForEntity("/cashcards?page=0&size=1&sort=amount,desc", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
DocumentContext documentContext = JsonPath.parse(response.getBody()); DocumentContext documentContext = JsonPath.parse(response.getBody());
@ -114,7 +129,9 @@ class CashcardApplicationTests {
@Test @Test
void shouldReturnASortedPageOfCashCardsWithNoParametersAndUseDefaultValues() { void shouldReturnASortedPageOfCashCardsWithNoParametersAndUseDefaultValues() {
ResponseEntity<String> response = restTemplate.getForEntity("/cashcards", String.class); ResponseEntity<String> response = restTemplate
.withBasicAuth("sarah1", "abc123")
.getForEntity("/cashcards", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
DocumentContext documentContext = JsonPath.parse(response.getBody()); DocumentContext documentContext = JsonPath.parse(response.getBody());
@ -124,4 +141,17 @@ class CashcardApplicationTests {
JSONArray amounts = documentContext.read("$..amount"); JSONArray amounts = documentContext.read("$..amount");
assertThat(amounts).containsExactly(1.00, 123.45, 150.00); assertThat(amounts).containsExactly(1.00, 123.45, 150.00);
} }
@Test
void shouldNotReturnACashCardWhenUsingBadCredentials() {
ResponseEntity<String> response = restTemplate
.withBasicAuth("BAD-USER", "abc123")
.getForEntity("/cashcards/99", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
response = restTemplate
.withBasicAuth("sarah1", "BAD-PASSWORD")
.getForEntity("/cashcards/99", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
}
} }

View File

@ -1,3 +1,3 @@
INSERT INTO CASH_CARD(ID, AMOUNT) VALUES (99, 123.45); INSERT INTO CASH_CARD(ID, AMOUNT, OWNER) VALUES (99, 123.45, 'sarah1');
INSERT INTO CASH_CARD(ID, AMOUNT) VALUES (100, 1.00); INSERT INTO CASH_CARD(ID, AMOUNT, OWNER) VALUES (100, 1.00, 'sarah1');
INSERT INTO CASH_CARD(ID, AMOUNT) VALUES (101, 150.00); INSERT INTO CASH_CARD(ID, AMOUNT, OWNER) VALUES (101, 150.00, 'sarah1');

View File

@ -1,4 +1,5 @@
{ {
"id": 99, "id": 99,
"amount": 123.45 "amount": 123.45,
"owner": "sarah1"
} }

View File

@ -1,5 +1,5 @@
[ [
{ "id": 99, "amount": 123.45 }, { "id": 99, "amount": 123.45, "owner": "sarah1" },
{ "id": 100, "amount": 1.0 }, { "id": 100, "amount": 1.0, "owner": "sarah1" },
{ "id": 101, "amount": 150.0 } { "id": 101, "amount": 150.0, "owner": "sarah1" }
] ]

View File

@ -1,5 +1,6 @@
CREATE TABLE cash_card CREATE TABLE cash_card
( (
ID BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, ID BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
AMOUNT NUMBER NOT NULL DEFAULT 0 AMOUNT NUMBER NOT NULL DEFAULT 0,
OWNER VARCHAR(256) NOT NULL
); );