diff --git a/apache-poi/README.md b/apache-poi/README.md index 599b21e063..d500787536 100644 --- a/apache-poi/README.md +++ b/apache-poi/README.md @@ -11,3 +11,4 @@ This module contains articles about Apache POI - [Get String Value of Excel Cell with Apache POI](https://www.baeldung.com/java-apache-poi-cell-string-value) - [Read Excel Cell Value Rather Than Formula With Apache POI](https://www.baeldung.com/apache-poi-read-cell-value-formula) - [Setting Formulas in Excel with Apache POI](https://www.baeldung.com/java-apache-poi-set-formulas) +- [Insert a Row in Excel Using Apache POI](https://www.baeldung.com/apache-poi-insert-excel-row) diff --git a/core-java-modules/core-java-collections-4/README.md b/core-java-modules/core-java-collections-4/README.md index 6e117c98b1..4fd77473d4 100644 --- a/core-java-modules/core-java-collections-4/README.md +++ b/core-java-modules/core-java-collections-4/README.md @@ -5,3 +5,4 @@ ### Relevant Articles: - [ArrayList vs. LinkedList vs. HashMap in Java](https://www.baeldung.com/java-arraylist-vs-linkedlist-vs-hashmap) +- [Java Deque vs. Stack](https://www.baeldung.com/java-deque-vs-stack) diff --git a/core-java-modules/core-java-collections-maps-3/README.md b/core-java-modules/core-java-collections-maps-3/README.md index 8b84ecbf81..530a9310c2 100644 --- a/core-java-modules/core-java-collections-maps-3/README.md +++ b/core-java-modules/core-java-collections-maps-3/README.md @@ -8,4 +8,5 @@ This module contains articles about Map data structures in Java. - [The Map.computeIfAbsent() Method](https://www.baeldung.com/java-map-computeifabsent) - [Collections.synchronizedMap vs. ConcurrentHashMap](https://www.baeldung.com/java-synchronizedmap-vs-concurrenthashmap) - [Java HashMap Load Factor](https://www.baeldung.com/java-hashmap-load-factor) +- [Converting java.util.Properties to HashMap](https://www.baeldung.com/java-convert-properties-to-hashmap) - More articles: [[<-- prev]](/core-java-modules/core-java-collections-maps-2) diff --git a/core-java-modules/core-java-io-conversions/src/test/java/com/baeldung/filetoinputstream/JavaXToInputStreamUnitTest.java b/core-java-modules/core-java-io-conversions/src/test/java/com/baeldung/filetoinputstream/JavaXToInputStreamUnitTest.java index caedd1747b..c20752639f 100644 --- a/core-java-modules/core-java-io-conversions/src/test/java/com/baeldung/filetoinputstream/JavaXToInputStreamUnitTest.java +++ b/core-java-modules/core-java-io-conversions/src/test/java/com/baeldung/filetoinputstream/JavaXToInputStreamUnitTest.java @@ -5,12 +5,12 @@ import com.google.common.io.CharSource; import com.google.common.io.Files; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; -import org.apache.commons.io.input.ReaderInputStream; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; +import java.nio.charset.StandardCharsets; public class JavaXToInputStreamUnitTest { protected final Logger logger = LoggerFactory.getLogger(getClass()); @@ -28,7 +28,7 @@ public class JavaXToInputStreamUnitTest { @Test public final void givenUsingGuava_whenConvertingStringToInputStream_thenCorrect() throws IOException { final String initialString = "text"; - final InputStream targetStream = new ReaderInputStream(CharSource.wrap(initialString).openStream()); + final InputStream targetStream = CharSource.wrap(initialString).asByteSource(StandardCharsets.UTF_8).openStream(); IOUtils.closeQuietly(targetStream); } diff --git a/core-java-modules/core-java-reflection-2/pom.xml b/core-java-modules/core-java-reflection-2/pom.xml index ea76ee8c98..02a806f87c 100644 --- a/core-java-modules/core-java-reflection-2/pom.xml +++ b/core-java-modules/core-java-reflection-2/pom.xml @@ -14,6 +14,15 @@ ../ + + + org.springframework + spring-test + ${spring.version} + test + + + core-java-reflection-2 @@ -40,5 +49,6 @@ 3.8.0 1.8 1.8 + 5.3.4 \ No newline at end of file diff --git a/core-java-modules/core-java-reflection-2/src/main/java/com/baeldung/reflection/access/privatemethods/LongArrayUtil.java b/core-java-modules/core-java-reflection-2/src/main/java/com/baeldung/reflection/access/privatemethods/LongArrayUtil.java new file mode 100644 index 0000000000..03d8daabbf --- /dev/null +++ b/core-java-modules/core-java-reflection-2/src/main/java/com/baeldung/reflection/access/privatemethods/LongArrayUtil.java @@ -0,0 +1,18 @@ +package com.baeldung.reflection.access.privatemethods; + +public class LongArrayUtil { + + public static int indexOf(long[] array, long target) { + return indexOf(array, target, 0, array.length); + } + + private static int indexOf(long[] array, long target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + +} diff --git a/core-java-modules/core-java-reflection-2/src/test/java/com/baeldung/reflection/access/privatemethods/InvokePrivateMethodsUnitTest.java b/core-java-modules/core-java-reflection-2/src/test/java/com/baeldung/reflection/access/privatemethods/InvokePrivateMethodsUnitTest.java new file mode 100644 index 0000000000..e98eb9ec7f --- /dev/null +++ b/core-java-modules/core-java-reflection-2/src/test/java/com/baeldung/reflection/access/privatemethods/InvokePrivateMethodsUnitTest.java @@ -0,0 +1,28 @@ +package com.baeldung.reflection.access.privatemethods; + +import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import java.lang.reflect.Method; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class InvokePrivateMethodsUnitTest { + + private final long[] someLongArray = new long[] { 1L, 2L, 1L, 4L, 2L }; + + @Test + void whenSearchingForLongValueInSubsequenceUsingReflection_thenTheCorrectIndexOfTheValueIsReturned() throws Exception { + Method indexOfMethod = LongArrayUtil.class.getDeclaredMethod("indexOf", long[].class, long.class, int.class, int.class); + indexOfMethod.setAccessible(true); + + assertEquals(2, indexOfMethod.invoke(LongArrayUtil.class, someLongArray, 1L, 1, someLongArray.length), "The index should be 2."); + } + + @Test + void whenSearchingForLongValueInSubsequenceUsingSpring_thenTheCorrectIndexOfTheValueIsReturned() throws Exception { + int indexOfSearchTarget = ReflectionTestUtils.invokeMethod(LongArrayUtil.class, "indexOf", someLongArray, 1L, 1, someLongArray.length); + assertEquals(2, indexOfSearchTarget, "The index should be 2."); + } + +} diff --git a/core-java-modules/core-java-security-2/README.md b/core-java-modules/core-java-security-2/README.md index 9b99d624c9..3f9520b888 100644 --- a/core-java-modules/core-java-security-2/README.md +++ b/core-java-modules/core-java-security-2/README.md @@ -15,4 +15,5 @@ This module contains articles about core Java Security - [Security Context Basics: User, Subject and Principal](https://www.baeldung.com/security-context-basics) - [Java AES Encryption and Decryption](https://www.baeldung.com/java-aes-encryption-decryption) - [InvalidAlgorithmParameterException: Wrong IV Length](https://www.baeldung.com/java-invalidalgorithmparameter-exception) +- [The java.security.egd JVM Option](https://www.baeldung.com/java-security-egd) - More articles: [[<-- prev]](/core-java-modules/core-java-security) diff --git a/data-structures/src/main/java/com/baeldung/circularlinkedlist/CircularLinkedList.java b/data-structures/src/main/java/com/baeldung/circularlinkedlist/CircularLinkedList.java index 47368d7f15..cf18f0783e 100644 --- a/data-structures/src/main/java/com/baeldung/circularlinkedlist/CircularLinkedList.java +++ b/data-structures/src/main/java/com/baeldung/circularlinkedlist/CircularLinkedList.java @@ -5,7 +5,7 @@ import org.slf4j.LoggerFactory; public class CircularLinkedList { - final Logger LOGGER = LoggerFactory.getLogger(CircularLinkedList.class); + final Logger logger = LoggerFactory.getLogger(CircularLinkedList.class); private Node head = null; private Node tail = null; @@ -42,24 +42,29 @@ public class CircularLinkedList { } public void deleteNode(int valueToDelete) { - Node currentNode = head; - - if (head != null) { - if (currentNode.value == valueToDelete) { - head = head.nextNode; - tail.nextNode = head; - } else { - do { - Node nextNode = currentNode.nextNode; - if (nextNode.value == valueToDelete) { - currentNode.nextNode = nextNode.nextNode; - break; - } - currentNode = currentNode.nextNode; - } while (currentNode != head); - } + if (head == null) { + return; } + do { + Node nextNode = currentNode.nextNode; + if (nextNode.value == valueToDelete) { + if (tail == head) { + head = null; + tail = null; + } else { + currentNode.nextNode = nextNode.nextNode; + if (head == nextNode) { + head = head.nextNode; + } + if (tail == nextNode) { + tail = currentNode; + } + } + break; + } + currentNode = nextNode; + } while (currentNode != head); } public void traverseList() { @@ -68,7 +73,7 @@ public class CircularLinkedList { if (head != null) { do { - LOGGER.info(currentNode.value + " "); + logger.info(currentNode.value + " "); currentNode = currentNode.nextNode; } while (currentNode != head); } diff --git a/data-structures/src/test/java/com/baeldung/circularlinkedlist/CircularLinkedListUnitTest.java b/data-structures/src/test/java/com/baeldung/circularlinkedlist/CircularLinkedListUnitTest.java index 5b0573a1ce..23829df7e9 100644 --- a/data-structures/src/test/java/com/baeldung/circularlinkedlist/CircularLinkedListUnitTest.java +++ b/data-structures/src/test/java/com/baeldung/circularlinkedlist/CircularLinkedListUnitTest.java @@ -1,10 +1,10 @@ package com.baeldung.circularlinkedlist; +import org.junit.Test; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import org.junit.Test; - public class CircularLinkedListUnitTest { @Test @@ -23,7 +23,7 @@ public class CircularLinkedListUnitTest { } @Test - public void givenACircularLinkedList_WhenDeletingElements_ThenListDoesNotContainThoseElements() { + public void givenACircularLinkedList_WhenDeletingInOrderHeadMiddleTail_ThenListDoesNotContainThoseElements() { CircularLinkedList cll = createCircularLinkedList(); assertTrue(cll.containsNode(13)); @@ -39,6 +39,32 @@ public class CircularLinkedListUnitTest { assertFalse(cll.containsNode(46)); } + @Test + public void givenACircularLinkedList_WhenDeletingInOrderTailMiddleHead_ThenListDoesNotContainThoseElements() { + CircularLinkedList cll = createCircularLinkedList(); + + assertTrue(cll.containsNode(46)); + cll.deleteNode(46); + assertFalse(cll.containsNode(46)); + + assertTrue(cll.containsNode(1)); + cll.deleteNode(1); + assertFalse(cll.containsNode(1)); + + assertTrue(cll.containsNode(13)); + cll.deleteNode(13); + assertFalse(cll.containsNode(13)); + } + + @Test + public void givenACircularLinkedListWithOneNode_WhenDeletingElement_ThenListDoesNotContainTheElement() { + CircularLinkedList cll = new CircularLinkedList(); + cll.addNode(1); + cll.deleteNode(1); + assertFalse(cll.containsNode(1)); + } + + private CircularLinkedList createCircularLinkedList() { CircularLinkedList cll = new CircularLinkedList(); diff --git a/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/bidirection/ItemWithRef.java b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/bidirection/ItemWithRef.java index 0ca8d721e8..1bd2451c6c 100644 --- a/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/bidirection/ItemWithRef.java +++ b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/bidirection/ItemWithRef.java @@ -1,12 +1,12 @@ package com.baeldung.jackson.bidirection; -import com.fasterxml.jackson.annotation.JsonManagedReference; +import com.fasterxml.jackson.annotation.JsonBackReference; public class ItemWithRef { public int id; public String itemName; - @JsonManagedReference + @JsonBackReference public UserWithRef owner; public ItemWithRef() { diff --git a/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/bidirection/UserWithRef.java b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/bidirection/UserWithRef.java index 3de03fc651..cc7b2b52ca 100644 --- a/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/bidirection/UserWithRef.java +++ b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/bidirection/UserWithRef.java @@ -3,13 +3,13 @@ package com.baeldung.jackson.bidirection; import java.util.ArrayList; import java.util.List; -import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonManagedReference; public class UserWithRef { public int id; public String name; - @JsonBackReference + @JsonManagedReference public List userItems; public UserWithRef() { @@ -19,7 +19,7 @@ public class UserWithRef { public UserWithRef(final int id, final String name) { this.id = id; this.name = name; - userItems = new ArrayList(); + userItems = new ArrayList<>(); } public void addItem(final ItemWithRef item) { diff --git a/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/bidirection/JacksonBidirectionRelationUnitTest.java b/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/bidirection/JacksonBidirectionRelationUnitTest.java index d0c5209891..b3ce27ad94 100644 --- a/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/bidirection/JacksonBidirectionRelationUnitTest.java +++ b/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/bidirection/JacksonBidirectionRelationUnitTest.java @@ -1,7 +1,9 @@ package com.baeldung.jackson.bidirection; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; @@ -16,7 +18,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; public class JacksonBidirectionRelationUnitTest { - @Test(expected = JsonMappingException.class) + @Test (expected = JsonMappingException.class) public void givenBidirectionRelation_whenSerializing_thenException() throws JsonProcessingException { final User user = new User(1, "John"); final Item item = new Item(2, "book", user); @@ -26,16 +28,39 @@ public class JacksonBidirectionRelationUnitTest { } @Test - public void givenBidirectionRelation_whenUsingJacksonReferenceAnnotation_thenCorrect() throws JsonProcessingException { + public void givenBidirectionRelation_whenUsingJacksonReferenceAnnotationWithSerialization_thenCorrect() throws JsonProcessingException { final UserWithRef user = new UserWithRef(1, "John"); final ItemWithRef item = new ItemWithRef(2, "book", user); user.addItem(item); - final String result = new ObjectMapper().writeValueAsString(item); + final String itemJson = new ObjectMapper().writeValueAsString(item); + final String userJson = new ObjectMapper().writeValueAsString(user); - assertThat(result, containsString("book")); - assertThat(result, containsString("John")); - assertThat(result, not(containsString("userItems"))); + assertThat(itemJson, containsString("book")); + assertThat(itemJson, not(containsString("John"))); + + assertThat(userJson, containsString("John")); + assertThat(userJson, containsString("userItems")); + assertThat(userJson, containsString("book")); + } + + @Test + public void givenBidirectionRelation_whenUsingJacksonReferenceAnnotationWithDeserialization_thenCorrect() throws JsonProcessingException { + final UserWithRef user = new UserWithRef(1, "John"); + final ItemWithRef item = new ItemWithRef(2, "book", user); + user.addItem(item); + + final String itemJson = new ObjectMapper().writeValueAsString(item); + final String userJson = new ObjectMapper().writeValueAsString(user); + + final ItemWithRef itemRead = new ObjectMapper().readValue(itemJson, ItemWithRef.class); + final UserWithRef userRead = new ObjectMapper().readValue(userJson, UserWithRef.class); + + assertThat(itemRead.itemName, is("book")); + assertThat(itemRead.owner, nullValue()); + + assertThat(userRead.name, is("John")); + assertThat(userRead.userItems.get(0).itemName, is("book")); } @Test diff --git a/java-collections-maps-3/README.md b/java-collections-maps-3/README.md index bd1029c9cf..9b1d8510c2 100644 --- a/java-collections-maps-3/README.md +++ b/java-collections-maps-3/README.md @@ -3,3 +3,4 @@ - [Java Map With Case-Insensitive Keys](https://www.baeldung.com/java-map-with-case-insensitive-keys) - [Using a Byte Array as Map Key in Java](https://www.baeldung.com/java-map-key-byte-array) - [Using the Map.Entry Java Class](https://www.baeldung.com/java-map-entry) +- [Optimizing HashMap’s Performance](https://www.baeldung.com/java-hashmap-optimize-performance) diff --git a/jmeter/README.md b/jmeter/README.md index 11351ffdda..53aca8d34b 100644 --- a/jmeter/README.md +++ b/jmeter/README.md @@ -52,3 +52,4 @@ Enjoy it :) - [Intro to Performance Testing using JMeter](https://www.baeldung.com/jmeter) - [Configure Jenkins to Run and Show JMeter Tests](https://www.baeldung.com/jenkins-and-jmeter) +- [Write Extracted Data to a File Using JMeter](https://www.baeldung.com/jmeter-write-to-file) diff --git a/libraries-http-2/pom.xml b/libraries-http-2/pom.xml index d0bdb26bd4..855008521f 100644 --- a/libraries-http-2/pom.xml +++ b/libraries-http-2/pom.xml @@ -1,7 +1,7 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 libraries-http-2 libraries-http-2 @@ -13,12 +13,17 @@ - + com.squareup.okhttp3 okhttp ${okhttp.version} + + com.squareup.okhttp3 + logging-interceptor + ${okhttp.version} + com.fasterxml.jackson.core jackson-databind @@ -81,9 +86,9 @@ - 3.14.2 + 4.9.1 2.8.5 - 3.14.2 + 4.9.1 1.0.3 9.4.19.v20190610 2.2.11 diff --git a/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/CacheControlResponeInterceptor.java b/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/CacheControlResponeInterceptor.java new file mode 100644 index 0000000000..e9f8db19be --- /dev/null +++ b/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/CacheControlResponeInterceptor.java @@ -0,0 +1,18 @@ +package com.baeldung.okhttp.interceptors; + +import java.io.IOException; + +import okhttp3.Interceptor; +import okhttp3.Response; + +public class CacheControlResponeInterceptor implements Interceptor { + + @Override + public Response intercept(Chain chain) throws IOException { + Response response = chain.proceed(chain.request()); + return response.newBuilder() + .header("Cache-Control", "no-store") + .build(); + } + +} diff --git a/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/ErrorMessage.java b/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/ErrorMessage.java new file mode 100644 index 0000000000..73411d947f --- /dev/null +++ b/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/ErrorMessage.java @@ -0,0 +1,21 @@ +package com.baeldung.okhttp.interceptors; + +public class ErrorMessage { + + private final int status; + private final String detail; + + public ErrorMessage(int status, String detail) { + this.status = status; + this.detail = detail; + } + + public int getStatus() { + return status; + } + + public String getDetail() { + return detail; + } + +} diff --git a/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/ErrorResponseInterceptor.java b/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/ErrorResponseInterceptor.java new file mode 100644 index 0000000000..f6c6673705 --- /dev/null +++ b/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/ErrorResponseInterceptor.java @@ -0,0 +1,32 @@ +package com.baeldung.okhttp.interceptors; + +import java.io.IOException; + +import com.google.gson.Gson; + +import okhttp3.Interceptor; +import okhttp3.MediaType; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public class ErrorResponseInterceptor implements Interceptor { + + public static final MediaType APPLICATION_JSON = MediaType.get("application/json; charset=utf-8"); + + @Override + public Response intercept(Chain chain) throws IOException { + Response response = chain.proceed(chain.request()); + + if(!response.isSuccessful()) { + Gson gson = new Gson(); + String body = gson.toJson(new ErrorMessage(response.code(), "The response from the server was not OK")); + ResponseBody responseBody = ResponseBody.create(body, APPLICATION_JSON); + + return response.newBuilder() + .body(responseBody) + .build(); + } + return response; + } + +} diff --git a/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/SimpleLoggingInterceptor.java b/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/SimpleLoggingInterceptor.java new file mode 100644 index 0000000000..6d08546eea --- /dev/null +++ b/libraries-http-2/src/main/java/com/baeldung/okhttp/interceptors/SimpleLoggingInterceptor.java @@ -0,0 +1,25 @@ +package com.baeldung.okhttp.interceptors; + +import java.io.IOException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +public class SimpleLoggingInterceptor implements Interceptor { + + private static final Logger LOGGER = LoggerFactory.getLogger(SimpleLoggingInterceptor.class); + + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + + LOGGER.info("Intercepted headers: {} from URL: {}", request.headers(), request.url()); + + return chain.proceed(request); + } + +} diff --git a/libraries-http-2/src/test/java/com/baeldung/okhttp/interceptors/InterceptorIntegrationTest.java b/libraries-http-2/src/test/java/com/baeldung/okhttp/interceptors/InterceptorIntegrationTest.java new file mode 100644 index 0000000000..d15b67ad3b --- /dev/null +++ b/libraries-http-2/src/test/java/com/baeldung/okhttp/interceptors/InterceptorIntegrationTest.java @@ -0,0 +1,90 @@ +package com.baeldung.okhttp.interceptors; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; + +import org.junit.Rule; +import org.junit.Test; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.logging.HttpLoggingInterceptor; +import okhttp3.logging.HttpLoggingInterceptor.Level; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; + +public class InterceptorIntegrationTest { + + @Rule + public MockWebServer server = new MockWebServer(); + + @Test + public void givenSimpleLogginInterceptor_whenRequestSent_thenHeadersLogged() throws IOException { + server.enqueue(new MockResponse().setBody("Hello Baeldung Readers!")); + + OkHttpClient client = new OkHttpClient.Builder() + .addNetworkInterceptor(new SimpleLoggingInterceptor()) + .build(); + + Request request = new Request.Builder() + .url(server.url("/greeting")) + .header("User-Agent", "A Baeldung Reader") + .build(); + + try (Response response = client.newCall(request).execute()) { + assertEquals("Response code should be: ", 200, response.code()); + assertEquals("Body should be: ", "Hello Baeldung Readers!", response.body().string()); + } + } + + @Test + public void givenResponseInterceptor_whenRequestSent_thenCacheControlSetToNoStore() throws IOException { + server.enqueue(new MockResponse().setBody("Hello Baeldung Readers!")); + + OkHttpClient client = new OkHttpClient.Builder() + .addInterceptor(getHttpLogger()) + .addInterceptor(new CacheControlResponeInterceptor()) + .build(); + + Request request = new Request.Builder() + .url(server.url("/greeting")) + .header("User-Agent", "A Baeldung Reader") + .build(); + + try (Response response = client.newCall(request).execute()) { + assertEquals("Response code should be: ", 200, response.code()); + assertEquals("Body should be: ", "Hello Baeldung Readers!", response.body().string()); + assertEquals("Response cache-control should be", "no-store", response.header("Cache-Control")); + } + } + + @Test + public void givenErrorResponseInterceptor_whenResponseIs500_thenBodyIsJsonWithStatus() throws IOException { + server.enqueue(new MockResponse().setResponseCode(500).setBody("Hello Baeldung Readers!")); + + OkHttpClient client = new OkHttpClient.Builder() + .addInterceptor(getHttpLogger()) + .addInterceptor(new ErrorResponseInterceptor()) + .build(); + + Request request = new Request.Builder() + .url(server.url("/greeting")) + .header("User-Agent", "A Baeldung Reader") + .build(); + + try (Response response = client.newCall(request).execute()) { + assertEquals("Response code should be: ", 500, response.code()); + assertEquals("Body should be: ", "{\"status\":500,\"detail\":\"The response from the server was not OK\"}", + response.body().string()); + } + } + + private HttpLoggingInterceptor getHttpLogger() { + HttpLoggingInterceptor logger = new HttpLoggingInterceptor(); + logger.setLevel(Level.HEADERS); + return logger; + } + +} diff --git a/persistence-modules/spring-data-jpa-annotations/README.md b/persistence-modules/spring-data-jpa-annotations/README.md index 1ee579cf6c..3892e75733 100644 --- a/persistence-modules/spring-data-jpa-annotations/README.md +++ b/persistence-modules/spring-data-jpa-annotations/README.md @@ -6,7 +6,6 @@ This module contains articles about annotations used in Spring Data JPA - [DDD Aggregates and @DomainEvents](https://www.baeldung.com/spring-data-ddd) - [JPA @Embedded And @Embeddable](https://www.baeldung.com/jpa-embedded-embeddable) -- [Spring Data JPA @Modifying Annotation](https://www.baeldung.com/spring-data-jpa-modifying-annotation) - [Spring JPA @Embedded and @EmbeddedId](https://www.baeldung.com/spring-jpa-embedded-method-parameters) - [Programmatic Transaction Management in Spring](https://www.baeldung.com/spring-programmatic-transaction-management) - [JPA Entity Lifecycle Events](https://www.baeldung.com/jpa-entity-lifecycle-events) diff --git a/persistence-modules/spring-data-jpa-enterprise/README.md b/persistence-modules/spring-data-jpa-enterprise/README.md index 81398b1f00..42fbecc880 100644 --- a/persistence-modules/spring-data-jpa-enterprise/README.md +++ b/persistence-modules/spring-data-jpa-enterprise/README.md @@ -12,6 +12,7 @@ This module contains articles about Spring Data JPA used in enterprise applicati - [Working with Lazy Element Collections in JPA](https://www.baeldung.com/java-jpa-lazy-collections) - [Custom Naming Convention with Spring Data JPA](https://www.baeldung.com/spring-data-jpa-custom-naming) - [Partial Data Update with Spring Data](https://www.baeldung.com/spring-data-partial-update) +- [Spring Data JPA @Modifying Annotation](https://www.baeldung.com/spring-data-jpa-modifying-annotation) ### Eclipse Config After importing the project into Eclipse, you may see the following error: diff --git a/persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/boot/daos/user/UserRepository.java b/persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/boot/daos/user/UserRepository.java index 53f692ff28..cfa99414c6 100644 --- a/persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/boot/daos/user/UserRepository.java +++ b/persistence-modules/spring-data-jpa-enterprise/src/main/java/com/baeldung/boot/daos/user/UserRepository.java @@ -93,6 +93,9 @@ public interface UserRepository extends JpaRepository , UserRepos @Query("delete User u where u.active = false") int deleteDeactivatedUsers(); + @Query("delete User u where u.active = false") + int deleteDeactivatedUsersWithNoModifyingAnnotation(); + @Modifying(clearAutomatically = true, flushAutomatically = true) @Query(value = "alter table USERS add column deleted int(1) not null default 0", nativeQuery = true) void addDeletedColumn(); diff --git a/persistence-modules/spring-data-jpa-enterprise/src/test/java/com/baeldung/boot/daos/UserRepositoryCommon.java b/persistence-modules/spring-data-jpa-enterprise/src/test/java/com/baeldung/boot/daos/UserRepositoryCommon.java index b2581b8034..e8841d921c 100644 --- a/persistence-modules/spring-data-jpa-enterprise/src/test/java/com/baeldung/boot/daos/UserRepositoryCommon.java +++ b/persistence-modules/spring-data-jpa-enterprise/src/test/java/com/baeldung/boot/daos/UserRepositoryCommon.java @@ -3,6 +3,7 @@ package com.baeldung.boot.daos; import org.junit.After; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; @@ -21,6 +22,7 @@ import java.util.function.Predicate; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.*; public class UserRepositoryCommon { @@ -520,6 +522,22 @@ public class UserRepositoryCommon { assertEquals(1, deletedUsersCount); } + @Test + @Transactional + public void givenTwoUsers_whenDeleteDeactivatedUsersWithNoModifyingAnnotation_ThenException() { + User usr01 = new User("usr01", LocalDate.of(2018, 1, 1), "usr01@baeldung.com", 1); + usr01.setLastLoginDate(LocalDate.now()); + User usr02 = new User("usr02", LocalDate.of(2018, 6, 1), "usr02@baeldung.com", 0); + usr02.setLastLoginDate(LocalDate.of(2018, 7, 20)); + usr02.setActive(false); + + userRepository.save(usr01); + userRepository.save(usr02); + + assertThatThrownBy(() -> userRepository.deleteDeactivatedUsersWithNoModifyingAnnotation()) + .isInstanceOf(InvalidDataAccessApiUsageException.class); + } + @Test @Transactional public void givenTwoUsers_whenAddDeletedColumn_ThenUsersHaveDeletedColumn() { diff --git a/pom.xml b/pom.xml index ef1e33cfe8..52ebabb727 100644 --- a/pom.xml +++ b/pom.xml @@ -531,7 +531,7 @@ protobuffer quarkus - quarkus-extension + rabbitmq @@ -992,7 +992,7 @@ protobuffer quarkus - quarkus-extension + rabbitmq diff --git a/spring-boot-modules/spring-boot-mvc-jersey/README.md b/spring-boot-modules/spring-boot-mvc-jersey/README.md index 07f9e78ea6..192658c4a5 100644 --- a/spring-boot-modules/spring-boot-mvc-jersey/README.md +++ b/spring-boot-modules/spring-boot-mvc-jersey/README.md @@ -5,4 +5,4 @@ This module contains articles about Spring Boot: JAX-RS vs Spring ### Relevant Articles: -- [REST API: JAX-RS vs Spring](https://www.baeldung.com/TBD) +- [REST API: JAX-RS vs Spring](https://www.baeldung.com/rest-api-jax-rs-vs-spring) diff --git a/spring-cloud/spring-cloud-docker/docker-message-server/src/main/resources/application.properties b/spring-cloud/spring-cloud-docker/docker-message-server/src/main/resources/application.properties index 7e41f5877f..c4e658f9e2 100644 --- a/spring-cloud/spring-cloud-docker/docker-message-server/src/main/resources/application.properties +++ b/spring-cloud/spring-cloud-docker/docker-message-server/src/main/resources/application.properties @@ -1,3 +1 @@ server.port=8888 -spring.security.user.name=root -spring.security.user.password=docker! diff --git a/spring-cloud/spring-cloud-docker/docker-product-server/src/main/resources/application.properties b/spring-cloud/spring-cloud-docker/docker-product-server/src/main/resources/application.properties index f62c8d0bab..2c5fdc3545 100644 --- a/spring-cloud/spring-cloud-docker/docker-product-server/src/main/resources/application.properties +++ b/spring-cloud/spring-cloud-docker/docker-product-server/src/main/resources/application.properties @@ -1,3 +1 @@ server.port=9999 -spring.security.user.name=root -spring.security.user.password=docker! diff --git a/spring-security-modules/pom.xml b/spring-security-modules/pom.xml index 99dea4bc67..096ffb9c3f 100644 --- a/spring-security-modules/pom.xml +++ b/spring-security-modules/pom.xml @@ -35,6 +35,7 @@ spring-security-legacy-oidc spring-security-oidc spring-security-okta + spring-security-saml spring-security-web-react spring-security-web-rest spring-security-web-rest-basic-auth diff --git a/spring-security-modules/spring-security-saml/pom.xml b/spring-security-modules/spring-security-saml/pom.xml new file mode 100644 index 0000000000..561582045a --- /dev/null +++ b/spring-security-modules/spring-security-saml/pom.xml @@ -0,0 +1,73 @@ + + + 4.0.0 + spring-security-saml + 1.0-SNAPSHOT + spring-security-saml + war + + + com.baeldung + parent-boot-2 + 0.0.1-SNAPSHOT + ../../parent-boot-2 + + + + Shibboleth + Shibboleth + https://build.shibboleth.net/nexus/content/repositories/releases/ + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.security.extensions + spring-security-saml2-core + ${saml2-core.spring.version} + + + + + spring-security-saml + + + src/main/resources + + + + + org.springframework.boot + spring-boot-maven-plugin + + true + + + + + repackage + + + + + + + + + 1.0.10.RELEASE + + diff --git a/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/Application.java b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/Application.java new file mode 100644 index 0000000000..39eaa46424 --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/Application.java @@ -0,0 +1,11 @@ +package com.baeldung.saml; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + public static void main(String... args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/authentication/CustomSAMLAuthenticationProvider.java b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/authentication/CustomSAMLAuthenticationProvider.java new file mode 100644 index 0000000000..b35a72763d --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/authentication/CustomSAMLAuthenticationProvider.java @@ -0,0 +1,28 @@ +package com.baeldung.saml.authentication; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.providers.ExpiringUsernameAuthenticationToken; +import org.springframework.security.saml.SAMLAuthenticationProvider; +import org.springframework.security.saml.SAMLCredential; + +public class CustomSAMLAuthenticationProvider extends SAMLAuthenticationProvider { + + @Override + public Collection getEntitlements(SAMLCredential credential, Object userDetail) { + + if(userDetail instanceof ExpiringUsernameAuthenticationToken) { + List authorities = new ArrayList(); + authorities.addAll(((ExpiringUsernameAuthenticationToken) userDetail).getAuthorities()); + return authorities; + + } else { + return Collections.emptyList(); + } + } + +} diff --git a/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/config/SamlSecurityConfig.java b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/config/SamlSecurityConfig.java new file mode 100644 index 0000000000..378db478cf --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/config/SamlSecurityConfig.java @@ -0,0 +1,226 @@ +package com.baeldung.saml.config; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.opensaml.saml2.metadata.provider.FilesystemMetadataProvider; +import org.opensaml.saml2.metadata.provider.MetadataProvider; +import org.opensaml.saml2.metadata.provider.MetadataProviderException; +import org.opensaml.util.resource.ResourceException; +import org.opensaml.xml.parse.StaticBasicParserPool; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.security.saml.*; +import org.springframework.security.saml.context.SAMLContextProviderImpl; +import org.springframework.security.saml.key.JKSKeyManager; +import org.springframework.security.saml.key.KeyManager; +import org.springframework.security.saml.log.SAMLDefaultLogger; +import org.springframework.security.saml.metadata.CachingMetadataManager; +import org.springframework.security.saml.metadata.ExtendedMetadata; +import org.springframework.security.saml.metadata.ExtendedMetadataDelegate; +import org.springframework.security.saml.processor.*; +import org.springframework.security.saml.util.VelocityFactory; +import org.springframework.security.saml.websso.*; +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; +import org.springframework.security.web.authentication.logout.LogoutHandler; +import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; +import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; + +import com.baeldung.saml.authentication.CustomSAMLAuthenticationProvider; + +@Configuration +public class SamlSecurityConfig { + + @Value("${saml.keystore.location}") + private String samlKeystoreLocation; + + @Value("${saml.keystore.password}") + private String samlKeystorePassword; + + @Value("${saml.keystore.alias}") + private String samlKeystoreAlias; + + @Value("${saml.idp}") + private String defaultIdp; + + @Bean(initMethod = "initialize") + public StaticBasicParserPool parserPool() { + return new StaticBasicParserPool(); + } + + @Bean + public SAMLAuthenticationProvider samlAuthenticationProvider() { + return new CustomSAMLAuthenticationProvider(); + } + + @Bean + public SAMLContextProviderImpl contextProvider() { + return new SAMLContextProviderImpl(); + } + + @Bean + public static SAMLBootstrap samlBootstrap() { + return new SAMLBootstrap(); + } + + @Bean + public SAMLDefaultLogger samlLogger() { + return new SAMLDefaultLogger(); + } + + @Bean + public WebSSOProfileConsumer webSSOprofileConsumer() { + return new WebSSOProfileConsumerImpl(); + } + + @Bean + @Qualifier("hokWebSSOprofileConsumer") + public WebSSOProfileConsumerHoKImpl hokWebSSOProfileConsumer() { + return new WebSSOProfileConsumerHoKImpl(); + } + + @Bean + public WebSSOProfile webSSOprofile() { + return new WebSSOProfileImpl(); + } + + @Bean + public WebSSOProfileConsumerHoKImpl hokWebSSOProfile() { + return new WebSSOProfileConsumerHoKImpl(); + } + + @Bean + public WebSSOProfileECPImpl ecpProfile() { + return new WebSSOProfileECPImpl(); + } + + @Bean + public SingleLogoutProfile logoutProfile() { + return new SingleLogoutProfileImpl(); + } + + @Bean + public KeyManager keyManager() { + DefaultResourceLoader loader = new DefaultResourceLoader(); + Resource storeFile = loader.getResource(samlKeystoreLocation); + Map passwords = new HashMap<>(); + passwords.put(samlKeystoreAlias, samlKeystorePassword); + return new JKSKeyManager(storeFile, samlKeystorePassword, passwords, samlKeystoreAlias); + } + + @Bean + public WebSSOProfileOptions defaultWebSSOProfileOptions() { + WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions(); + webSSOProfileOptions.setIncludeScoping(false); + return webSSOProfileOptions; + } + + @Bean + public SAMLEntryPoint samlEntryPoint() { + SAMLEntryPoint samlEntryPoint = new SAMLEntryPoint(); + samlEntryPoint.setDefaultProfileOptions(defaultWebSSOProfileOptions()); + return samlEntryPoint; + } + + @Bean + public ExtendedMetadata extendedMetadata() { + ExtendedMetadata extendedMetadata = new ExtendedMetadata(); + extendedMetadata.setIdpDiscoveryEnabled(false); + extendedMetadata.setSignMetadata(false); + return extendedMetadata; + } + + @Bean + @Qualifier("okta") + public ExtendedMetadataDelegate oktaExtendedMetadataProvider() throws MetadataProviderException { + File metadata = null; + try { + metadata = new File("./src/main/resources/saml/metadata/sso.xml"); + } catch (Exception e) { + e.printStackTrace(); + } + FilesystemMetadataProvider provider = new FilesystemMetadataProvider(metadata); + provider.setParserPool(parserPool()); + return new ExtendedMetadataDelegate(provider, extendedMetadata()); + } + + @Bean + @Qualifier("metadata") + public CachingMetadataManager metadata() throws MetadataProviderException, ResourceException { + List providers = new ArrayList<>(); + providers.add(oktaExtendedMetadataProvider()); + CachingMetadataManager metadataManager = new CachingMetadataManager(providers); + metadataManager.setDefaultIDP(defaultIdp); + return metadataManager; + } + + @Bean + @Qualifier("saml") + public SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() { + SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler = new SavedRequestAwareAuthenticationSuccessHandler(); + successRedirectHandler.setDefaultTargetUrl("/home"); + return successRedirectHandler; + } + + @Bean + @Qualifier("saml") + public SimpleUrlAuthenticationFailureHandler authenticationFailureHandler() { + SimpleUrlAuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler(); + failureHandler.setUseForward(true); + failureHandler.setDefaultFailureUrl("/error"); + return failureHandler; + } + + @Bean + public SimpleUrlLogoutSuccessHandler successLogoutHandler() { + SimpleUrlLogoutSuccessHandler successLogoutHandler = new SimpleUrlLogoutSuccessHandler(); + successLogoutHandler.setDefaultTargetUrl("/"); + return successLogoutHandler; + } + + @Bean + public SecurityContextLogoutHandler logoutHandler() { + SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler(); + logoutHandler.setInvalidateHttpSession(true); + logoutHandler.setClearAuthentication(true); + return logoutHandler; + } + + @Bean + public SAMLLogoutProcessingFilter samlLogoutProcessingFilter() { + return new SAMLLogoutProcessingFilter(successLogoutHandler(), logoutHandler()); + } + + @Bean + public SAMLLogoutFilter samlLogoutFilter() { + return new SAMLLogoutFilter(successLogoutHandler(), + new LogoutHandler[] { logoutHandler() }, + new LogoutHandler[] { logoutHandler() }); + } + + @Bean + public HTTPPostBinding httpPostBinding() { + return new HTTPPostBinding(parserPool(), VelocityFactory.getEngine()); + } + + @Bean + public HTTPRedirectDeflateBinding httpRedirectDeflateBinding() { + return new HTTPRedirectDeflateBinding(parserPool()); + } + + @Bean + public SAMLProcessorImpl processor() { + ArrayList bindings = new ArrayList<>(); + bindings.add(httpRedirectDeflateBinding()); + bindings.add(httpPostBinding()); + return new SAMLProcessorImpl(bindings); + } +} diff --git a/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/config/WebSecurityConfig.java b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/config/WebSecurityConfig.java new file mode 100644 index 0000000000..297c391823 --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/config/WebSecurityConfig.java @@ -0,0 +1,152 @@ +package com.baeldung.saml.config; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.saml.*; +import org.springframework.security.saml.key.KeyManager; +import org.springframework.security.saml.metadata.*; +import org.springframework.security.web.DefaultSecurityFilterChain; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.access.channel.ChannelProcessingFilter; +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; +import org.springframework.security.web.csrf.CsrfFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(securedEnabled = true) +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + @Value("${saml.sp}") + private String samlAudience; + + @Autowired + @Qualifier("saml") + private SavedRequestAwareAuthenticationSuccessHandler samlAuthSuccessHandler; + + @Autowired + @Qualifier("saml") + private SimpleUrlAuthenticationFailureHandler samlAuthFailureHandler; + + @Autowired + private SAMLEntryPoint samlEntryPoint; + + @Autowired + private SAMLLogoutFilter samlLogoutFilter; + + @Autowired + private SAMLLogoutProcessingFilter samlLogoutProcessingFilter; + + @Bean + public SAMLDiscovery samlDiscovery() { + SAMLDiscovery idpDiscovery = new SAMLDiscovery(); + return idpDiscovery; + } + + @Autowired + private SAMLAuthenticationProvider samlAuthenticationProvider; + + @Autowired + private ExtendedMetadata extendedMetadata; + + @Autowired + private KeyManager keyManager; + + public MetadataGenerator metadataGenerator() { + MetadataGenerator metadataGenerator = new MetadataGenerator(); + metadataGenerator.setEntityId(samlAudience); + metadataGenerator.setExtendedMetadata(extendedMetadata); + metadataGenerator.setIncludeDiscoveryExtension(false); + metadataGenerator.setKeyManager(keyManager); + return metadataGenerator; + } + + @Bean + public SAMLProcessingFilter samlWebSSOProcessingFilter() throws Exception { + SAMLProcessingFilter samlWebSSOProcessingFilter = new SAMLProcessingFilter(); + samlWebSSOProcessingFilter.setAuthenticationManager(authenticationManager()); + samlWebSSOProcessingFilter.setAuthenticationSuccessHandler(samlAuthSuccessHandler); + samlWebSSOProcessingFilter.setAuthenticationFailureHandler(samlAuthFailureHandler); + return samlWebSSOProcessingFilter; + } + + @Bean + public FilterChainProxy samlFilter() throws Exception { + List chains = new ArrayList<>(); + chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSO/**"), + samlWebSSOProcessingFilter())); + chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/discovery/**"), + samlDiscovery())); + chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/login/**"), + samlEntryPoint)); + chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/logout/**"), + samlLogoutFilter)); + chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SingleLogout/**"), + samlLogoutProcessingFilter)); + return new FilterChainProxy(chains); + } + + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + @Bean + public MetadataGeneratorFilter metadataGeneratorFilter() { + return new MetadataGeneratorFilter(metadataGenerator()); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .csrf() + .disable(); + + http + .httpBasic() + .authenticationEntryPoint(samlEntryPoint); + + http + .addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class) + .addFilterAfter(samlFilter(), BasicAuthenticationFilter.class) + .addFilterBefore(samlFilter(), CsrfFilter.class); + + http + .authorizeRequests() + .antMatchers("/").permitAll() + .anyRequest().authenticated(); + + http + .logout() + .addLogoutHandler((request, response, authentication) -> { + try { + response.sendRedirect("/saml/logout"); + } catch (IOException e) { + e.printStackTrace(); + } + }); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.authenticationProvider(samlAuthenticationProvider); + } + +} diff --git a/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/controller/HomeController.java b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/controller/HomeController.java new file mode 100644 index 0000000000..e77933b8f3 --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/java/com/baeldung/saml/controller/HomeController.java @@ -0,0 +1,35 @@ +package com.baeldung.saml.controller; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +public class HomeController { + + @RequestMapping("/") + public String index() { + return "index"; + } + + @GetMapping(value = "/auth") + public String handleSamlAuth() { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth != null) { + return "redirect:/home"; + } else { + return "/"; + } + } + + @RequestMapping("/home") + public String home(Model model) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + model.addAttribute("username", authentication.getPrincipal()); + return "home"; + } + +} diff --git a/spring-security-modules/spring-security-saml/src/main/resources/application.properties b/spring-security-modules/spring-security-saml/src/main/resources/application.properties new file mode 100644 index 0000000000..f9d6a5df3c --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/resources/application.properties @@ -0,0 +1,6 @@ +saml.keystore.location=classpath:/saml/samlKeystore.jks +saml.keystore.password= +saml.keystore.alias= + +saml.idp= +saml.sp=http://localhost:8080/saml/metadata \ No newline at end of file diff --git a/spring-security-modules/spring-security-saml/src/main/resources/saml/metadata/sso.xml b/spring-security-modules/spring-security-saml/src/main/resources/saml/metadata/sso.xml new file mode 100644 index 0000000000..2d3258de12 --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/resources/saml/metadata/sso.xml @@ -0,0 +1,44 @@ + + + + + + + MIIDpDCCAoygAwIBAgIGAXGiSQ7ZMA0GCSqGSIb3DQEBCwUAMIGSMQswCQYDVQQGEwJVUzETMBEG + A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU + MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi05MjY2NjYxHDAaBgkqhkiG9w0BCQEW + DWluZm9Ab2t0YS5jb20wHhcNMjAwNDIyMTQyNjA5WhcNMzAwNDIyMTQyNzA5WjCBkjELMAkGA1UE + BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV + BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtOTI2NjY2MRwwGgYJ + KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA + g1rQYYqeVx2gl/UUnLJzp5hrm06VOILJB9hIUmNqXgWV3UjzDq/zX0KW8MENjsO7+S8a+LLnYRkb + N5egH9FSt8AHtB1pmfXDtpUQmWe9yJbNxbCISoc6XzCmaRw3HRv9pK5SciIutciz9lvFaHMWAWtP + MmQSKdhMet52tuf6sTy4ODeXjyMnD9q5QOKww1SJ678wjHbGRRhNvCxvTSAH33sa4oNCf2RvP9hp + NiJRcYW9yLZXmZArPQOuAx5PIXfHhK2e4ac39YO4fgO7gwU5TZ+vL7o6iEmd9tk44PrND0ZV5yzZ + +Y33Hiun3fIiZu/nZZGUjm4k4exl8JJpwrVTHQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBcfHcL + 2DjTjZGoANF4dPpGXTYdVnL/XzGiLS+3LR/HDrEz/EqsHouF40RnzdZ7Ax7RReKBYCUUqHpSE+LU + ductz2ANguzyseGEn72I4Ym4ytQWnFyTXeW+xI9CoCLGfOUhT1hlKjsu/qNM8qwKFPWkzQp7mDN8 + S9MGhsnbiyeD/lceAEKw16Os73/sX2j7F+43WVCYRDCRB8pRIPfcqYLXUIUSstQlwEvCF7HyeO4+ + jxKHA1tp9Cpmj7/VD9TE3fyvrbVmfjTbKjF7/0wYQNfbHDDko0ratDMAizG5/d3i9wk9KbGCHSxT + ph5nl1pdjKgAYPK0iNDnGCZbGKzXOrqV + + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + + + + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-saml/src/main/resources/saml/samlKeystore.jks b/spring-security-modules/spring-security-saml/src/main/resources/saml/samlKeystore.jks new file mode 100644 index 0000000000..7f3a5850d9 Binary files /dev/null and b/spring-security-modules/spring-security-saml/src/main/resources/saml/samlKeystore.jks differ diff --git a/spring-security-modules/spring-security-saml/src/main/resources/templates/error.html b/spring-security-modules/spring-security-saml/src/main/resources/templates/error.html new file mode 100644 index 0000000000..7223ee43fd --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/resources/templates/error.html @@ -0,0 +1,13 @@ + + + +
Something went wrong
+
+ +

+ An error occurred +

+
+ + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-saml/src/main/resources/templates/home.html b/spring-security-modules/spring-security-saml/src/main/resources/templates/home.html new file mode 100644 index 0000000000..c66e92c1f0 --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/resources/templates/home.html @@ -0,0 +1,13 @@ + + + +Baeldung Spring Security SAML: Home + + +

Welcome!
You are successfully logged in!

+

You are logged as null.

+ + Logout + + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-saml/src/main/resources/templates/index.html b/spring-security-modules/spring-security-saml/src/main/resources/templates/index.html new file mode 100644 index 0000000000..7999c2fded --- /dev/null +++ b/spring-security-modules/spring-security-saml/src/main/resources/templates/index.html @@ -0,0 +1,10 @@ + + + +Baeldung Spring Security SAML + + +

Welcome to Baeldung Spring Security SAML

+ Login + + \ No newline at end of file diff --git a/testing-modules/zerocode/README.md b/testing-modules/zerocode/README.md new file mode 100644 index 0000000000..a0a844c63d --- /dev/null +++ b/testing-modules/zerocode/README.md @@ -0,0 +1,3 @@ +### Relevant Articles: + +- [Introduction to ZeroCode](https://www.baeldung.com/zerocode-intro)