Merge branch 'master' of https://github.com/eugenp/tutorials
This commit is contained in:
commit
4505e5c240
|
@ -0,0 +1,36 @@
|
|||
package com.baeldung.inttoenum;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public enum PizzaStatus {
|
||||
ORDERED(5),
|
||||
READY(2),
|
||||
DELIVERED(0);
|
||||
|
||||
private int timeToDelivery;
|
||||
|
||||
PizzaStatus(int timeToDelivery) {
|
||||
this.timeToDelivery = timeToDelivery;
|
||||
}
|
||||
|
||||
public int getTimeToDelivery() {
|
||||
return timeToDelivery;
|
||||
}
|
||||
|
||||
private static Map<Integer, PizzaStatus> timeToDeliveryToEnumValuesMapping = new HashMap<>();
|
||||
|
||||
static {
|
||||
PizzaStatus[] pizzaStatuses = PizzaStatus.values();
|
||||
for (int pizzaStatusIndex = 0; pizzaStatusIndex < pizzaStatuses.length; pizzaStatusIndex++) {
|
||||
timeToDeliveryToEnumValuesMapping.put(
|
||||
pizzaStatuses[pizzaStatusIndex].getTimeToDelivery(),
|
||||
pizzaStatuses[pizzaStatusIndex]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static PizzaStatus castIntToEnum(int timeToDelivery) {
|
||||
return timeToDeliveryToEnumValuesMapping.get(timeToDelivery);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package com.baeldung.inttoenum;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class IntToEnumUnitTest {
|
||||
|
||||
@Test
|
||||
public void whenIntToEnumUsingValuesMethod_thenReturnEnumObject() {
|
||||
int timeToDeliveryForOrderedPizzaStatus = 5;
|
||||
PizzaStatus[] pizzaStatuses = PizzaStatus.values();
|
||||
PizzaStatus pizzaOrderedStatus = null;
|
||||
for (int pizzaStatusIndex = 0; pizzaStatusIndex < pizzaStatuses.length; pizzaStatusIndex++) {
|
||||
if (pizzaStatuses[pizzaStatusIndex].getTimeToDelivery() == timeToDeliveryForOrderedPizzaStatus) {
|
||||
pizzaOrderedStatus = pizzaStatuses[pizzaStatusIndex];
|
||||
}
|
||||
}
|
||||
assertEquals(pizzaOrderedStatus, PizzaStatus.ORDERED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenIntToEnumUsingMap_thenReturnEnumObject() {
|
||||
int timeToDeliveryForOrderedPizzaStatus = 5;
|
||||
assertEquals(PizzaStatus.castIntToEnum(timeToDeliveryForOrderedPizzaStatus), PizzaStatus.ORDERED);
|
||||
}
|
||||
}
|
|
@ -19,9 +19,9 @@ class StringComparisonUnitTest {
|
|||
fun `compare using referential equals operator`() {
|
||||
val first = "kotlin"
|
||||
val second = "kotlin"
|
||||
val copyOfFirst = buildString { "kotlin" }
|
||||
val third = String("kotlin".toCharArray())
|
||||
assertTrue { first === second }
|
||||
assertFalse { first === copyOfFirst }
|
||||
assertFalse { first === third }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -221,6 +221,7 @@
|
|||
<systemPropertyVariables>
|
||||
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
|
||||
<jboss.home>${project.basedir}/target/wildfly-${wildfly.version}</jboss.home>
|
||||
<jboss.http.port>8756</jboss.http.port>
|
||||
<module.path>${project.basedir}/target/wildfly-${wildfly.version}/modules</module.path>
|
||||
</systemPropertyVariables>
|
||||
<redirectTestOutputToFile>false</redirectTestOutputToFile>
|
||||
|
@ -278,9 +279,10 @@
|
|||
<arquillian-drone-bom.version>2.0.1.Final</arquillian-drone-bom.version>
|
||||
<arquillian-rest-client.version>1.0.0.Alpha4</arquillian-rest-client.version>
|
||||
|
||||
<logback.version>1.1.7</logback.version>
|
||||
|
||||
<resteasy.version>3.8.0.Final</resteasy.version>
|
||||
<shrinkwrap.version>3.1.3</shrinkwrap.version>
|
||||
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
<jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
|
||||
|
||||
<class>com.enpy.entity.Student</class>
|
||||
<class>com.baeldung.jeekotlin.entity.Student</class>
|
||||
|
||||
<properties>
|
||||
<property name="hibernate.hbm2ddl.auto" value="create"/>
|
||||
|
|
|
@ -13,7 +13,7 @@ import java.util.UUID;
|
|||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest
|
||||
public class HibernateTypesIntegrationTest {
|
||||
public class HibernateTypesLiveTest {
|
||||
|
||||
@Autowired
|
||||
AlbumRepository albumRepository;
|
|
@ -3,6 +3,6 @@
|
|||
- [Using JDBI with Spring Boot](https://www.baeldung.com/spring-boot-jdbi)
|
||||
- [Configuring a Tomcat Connection Pool in Spring Boot](https://www.baeldung.com/spring-boot-tomcat-connection-pool)
|
||||
- [Integrating Spring Boot with HSQLDB](https://www.baeldung.com/spring-boot-hsqldb)
|
||||
- [List of In-Memory Databases](http://www.baeldung.com/java-in-memory-databases)
|
||||
- [List of In-Memory Databases](https://www.baeldung.com/java-in-memory-databases)
|
||||
- [Oracle Connection Pooling With Spring](https://www.baeldung.com/spring-oracle-connection-pooling)
|
||||
- More articles: [[<-- prev]](../spring-boot-persistence)
|
||||
|
|
|
@ -4,6 +4,7 @@ import javax.sql.DataSource;
|
|||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import com.baeldung.tomcatconnectionpool.application.SpringBootConsoleApplication;
|
||||
|
@ -13,6 +14,7 @@ import org.springframework.boot.test.context.SpringBootTest;
|
|||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = {SpringBootConsoleApplication.class})
|
||||
@TestPropertySource(properties = "spring.datasource.type=org.apache.tomcat.jdbc.pool.DataSource")
|
||||
public class SpringBootTomcatConnectionPoolIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
|
|
|
@ -35,23 +35,21 @@
|
|||
<version>${lombok.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-core</artifactId>
|
||||
<version>${hibernate.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.vladmihalcea</groupId>
|
||||
<artifactId>db-util</artifactId>
|
||||
<version>${db-util.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.bytebuddy</groupId>
|
||||
<artifactId>byte-buddy</artifactId>
|
||||
<version>${byte-buddy.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<!-- The main class to start by executing java -jar -->
|
||||
<start-class>com.baeldung.h2db.demo.server.SpringBootApp</start-class>
|
||||
<spring-boot.version>2.0.4.RELEASE</spring-boot.version>
|
||||
<hibernate.version>5.3.11.Final</hibernate.version>
|
||||
<db-util.version>1.0.4</db-util.version>
|
||||
</properties>
|
||||
|
||||
|
|
|
@ -8,5 +8,4 @@ spring.jpa.properties.hibernate.format_sql=true
|
|||
spring.jpa.properties.hibernate.validator.apply_to_ddl=false
|
||||
#spring.jpa.properties.hibernate.check_nullability=true
|
||||
spring.h2.console.enabled=true
|
||||
spring.h2.console.path=/h2-console
|
||||
debug=true
|
||||
spring.h2.console.path=/h2-console
|
|
@ -1,8 +1,8 @@
|
|||
### Relevant Articles:
|
||||
|
||||
- [Spring Boot with Multiple SQL Import Files](http://www.baeldung.com/spring-boot-sql-import-files)
|
||||
- [Configuring Separate Spring DataSource for Tests](http://www.baeldung.com/spring-testing-separate-data-source)
|
||||
- [Quick Guide on Loading Initial Data with Spring Boot](http://www.baeldung.com/spring-boot-data-sql-and-schema-sql)
|
||||
- [Spring Boot with Multiple SQL Import Files](https://www.baeldung.com/spring-boot-sql-import-files)
|
||||
- [Configuring Separate Spring DataSource for Tests](https://www.baeldung.com/spring-testing-separate-data-source)
|
||||
- [Quick Guide on Loading Initial Data with Spring Boot](https://www.baeldung.com/spring-boot-data-sql-and-schema-sql)
|
||||
- [Configuring a DataSource Programmatically in Spring Boot](https://www.baeldung.com/spring-boot-configure-data-source-programmatic)
|
||||
- [Resolving “Failed to Configure a DataSource” Error](https://www.baeldung.com/spring-boot-failed-to-configure-data-source)
|
||||
- [Hibernate Field Naming with Spring Boot](https://www.baeldung.com/hibernate-field-naming-spring-boot)
|
||||
|
|
|
@ -87,7 +87,35 @@
|
|||
<artifactId>javase</artifactId>
|
||||
<version>${zxing.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Bucket4j -->
|
||||
<dependency>
|
||||
<groupId>com.github.vladimir-bukhtoyarov</groupId>
|
||||
<artifactId>bucket4j-core</artifactId>
|
||||
<version>${bucket4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.giffing.bucket4j.spring.boot.starter</groupId>
|
||||
<artifactId>bucket4j-spring-boot-starter</artifactId>
|
||||
<version>${bucket4j-spring-boot-starter.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-cache</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.cache</groupId>
|
||||
<artifactId>cache-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
<version>${caffeine.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>jcache</artifactId>
|
||||
<version>${caffeine.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<repositories>
|
||||
|
@ -200,6 +228,9 @@
|
|||
<barcode4j.version>2.1</barcode4j.version>
|
||||
<qrgen.version>2.6.0</qrgen.version>
|
||||
<zxing.version>3.3.0</zxing.version>
|
||||
<bucket4j.version>4.10.0</bucket4j.version>
|
||||
<bucket4j-spring-boot-starter.version>0.2.0</bucket4j-spring-boot-starter.version>
|
||||
<caffeine.version>2.8.2</caffeine.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package com.baeldung.ratelimiting.bootstarterapp;
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = "com.baeldung.ratelimiting", exclude = {
|
||||
DataSourceAutoConfiguration.class,
|
||||
SecurityAutoConfiguration.class,
|
||||
})
|
||||
@EnableCaching
|
||||
public class Bucket4jRateLimitApp {
|
||||
|
||||
public static void main(String[] args) {
|
||||
new SpringApplicationBuilder(Bucket4jRateLimitApp.class)
|
||||
.properties("spring.config.location=classpath:ratelimiting/application-bucket4j-starter.yml")
|
||||
.run(args);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package com.baeldung.ratelimiting.bucket4japp;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import com.baeldung.ratelimiting.bucket4japp.interceptor.RateLimitInterceptor;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = "com.baeldung.ratelimiting", exclude = {
|
||||
DataSourceAutoConfiguration.class,
|
||||
SecurityAutoConfiguration.class
|
||||
})
|
||||
public class Bucket4jRateLimitApp implements WebMvcConfigurer {
|
||||
|
||||
@Autowired
|
||||
@Lazy
|
||||
private RateLimitInterceptor interceptor;
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(interceptor)
|
||||
.addPathPatterns("/api/v1/area/**");
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
new SpringApplicationBuilder(Bucket4jRateLimitApp.class)
|
||||
.properties("spring.config.location=classpath:ratelimiting/application-bucket4j.yml")
|
||||
.run(args);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package com.baeldung.ratelimiting.bucket4japp.interceptor;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import com.baeldung.ratelimiting.bucket4japp.service.PricingPlanService;
|
||||
|
||||
import io.github.bucket4j.Bucket;
|
||||
import io.github.bucket4j.ConsumptionProbe;
|
||||
|
||||
@Component
|
||||
public class RateLimitInterceptor implements HandlerInterceptor {
|
||||
|
||||
private static final String HEADER_API_KEY = "X-api-key";
|
||||
private static final String HEADER_LIMIT_REMAINING = "X-Rate-Limit-Remaining";
|
||||
private static final String HEADER_RETRY_AFTER = "X-Rate-Limit-Retry-After-Seconds";
|
||||
|
||||
@Autowired
|
||||
private PricingPlanService pricingPlanService;
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
|
||||
String apiKey = request.getHeader(HEADER_API_KEY);
|
||||
|
||||
if (apiKey == null || apiKey.isEmpty()) {
|
||||
response.sendError(HttpStatus.BAD_REQUEST.value(), "Missing Header: " + HEADER_API_KEY);
|
||||
return false;
|
||||
}
|
||||
|
||||
Bucket tokenBucket = pricingPlanService.resolveBucket(apiKey);
|
||||
|
||||
ConsumptionProbe probe = tokenBucket.tryConsumeAndReturnRemaining(1);
|
||||
|
||||
if (probe.isConsumed()) {
|
||||
|
||||
response.addHeader(HEADER_LIMIT_REMAINING, String.valueOf(probe.getRemainingTokens()));
|
||||
return true;
|
||||
|
||||
} else {
|
||||
|
||||
long waitForRefill = probe.getNanosToWaitForRefill() / 1_000_000_000;
|
||||
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
response.addHeader(HEADER_RETRY_AFTER, String.valueOf(waitForRefill));
|
||||
response.sendError(HttpStatus.TOO_MANY_REQUESTS.value(), "You have exhausted your API Request Quota"); // 429
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package com.baeldung.ratelimiting.bucket4japp.service;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import io.github.bucket4j.Bandwidth;
|
||||
import io.github.bucket4j.Refill;
|
||||
|
||||
public enum PricingPlan {
|
||||
|
||||
FREE(20),
|
||||
|
||||
BASIC(40),
|
||||
|
||||
PROFESSIONAL(100);
|
||||
|
||||
private int bucketCapacity;
|
||||
|
||||
private PricingPlan(int bucketCapacity) {
|
||||
this.bucketCapacity = bucketCapacity;
|
||||
}
|
||||
|
||||
Bandwidth getLimit() {
|
||||
return Bandwidth.classic(bucketCapacity, Refill.intervally(bucketCapacity, Duration.ofHours(1)));
|
||||
}
|
||||
|
||||
public int bucketCapacity() {
|
||||
return bucketCapacity;
|
||||
}
|
||||
|
||||
static PricingPlan resolvePlanFromApiKey(String apiKey) {
|
||||
if (apiKey == null || apiKey.isEmpty()) {
|
||||
return FREE;
|
||||
|
||||
} else if (apiKey.startsWith("PX001-")) {
|
||||
return PROFESSIONAL;
|
||||
|
||||
} else if (apiKey.startsWith("BX001-")) {
|
||||
return BASIC;
|
||||
}
|
||||
return FREE;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package com.baeldung.ratelimiting.bucket4japp.service;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import io.github.bucket4j.Bandwidth;
|
||||
import io.github.bucket4j.Bucket;
|
||||
import io.github.bucket4j.Bucket4j;
|
||||
|
||||
@Service
|
||||
public class PricingPlanService {
|
||||
|
||||
private final Map<String, Bucket> cache = new ConcurrentHashMap<>();
|
||||
|
||||
public Bucket resolveBucket(String apiKey) {
|
||||
return cache.computeIfAbsent(apiKey, this::newBucket);
|
||||
}
|
||||
|
||||
private Bucket newBucket(String apiKey) {
|
||||
PricingPlan pricingPlan = PricingPlan.resolvePlanFromApiKey(apiKey);
|
||||
return bucket(pricingPlan.getLimit());
|
||||
}
|
||||
|
||||
private Bucket bucket(Bandwidth limit) {
|
||||
return Bucket4j.builder()
|
||||
.addLimit(limit)
|
||||
.build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package com.baeldung.ratelimiting.controller;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.baeldung.ratelimiting.dto.AreaV1;
|
||||
import com.baeldung.ratelimiting.dto.RectangleDimensionsV1;
|
||||
import com.baeldung.ratelimiting.dto.TriangleDimensionsV1;
|
||||
|
||||
@RestController
|
||||
@RequestMapping(value = "/api/v1/area", consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
class AreaCalculationController {
|
||||
|
||||
@PostMapping(value = "/rectangle", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<AreaV1> rectangle(@RequestBody RectangleDimensionsV1 dimensions) {
|
||||
|
||||
return ResponseEntity.ok(new AreaV1("rectangle", dimensions.getLength() * dimensions.getWidth()));
|
||||
}
|
||||
|
||||
@PostMapping(value = "/triangle", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<AreaV1> triangle(@RequestBody TriangleDimensionsV1 dimensions) {
|
||||
|
||||
return ResponseEntity.ok(new AreaV1("triangle", 0.5d * dimensions.getHeight() * dimensions.getBase()));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package com.baeldung.ratelimiting.dto;
|
||||
|
||||
public class AreaV1 {
|
||||
|
||||
private String shape;
|
||||
private Double area;
|
||||
|
||||
public AreaV1(String shape, Double area) {
|
||||
this.area = area;
|
||||
this.shape = shape;
|
||||
}
|
||||
|
||||
public Double getArea() {
|
||||
return area;
|
||||
}
|
||||
|
||||
public String getShape() {
|
||||
return shape;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package com.baeldung.ratelimiting.dto;
|
||||
|
||||
public class RectangleDimensionsV1 {
|
||||
|
||||
private double length;
|
||||
private double width;
|
||||
|
||||
public double getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public double getWidth() {
|
||||
return width;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package com.baeldung.ratelimiting.dto;
|
||||
|
||||
public class TriangleDimensionsV1 {
|
||||
|
||||
private double base;
|
||||
private double height;
|
||||
|
||||
public double getBase() {
|
||||
return base;
|
||||
}
|
||||
|
||||
public double getHeight() {
|
||||
return height;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
server:
|
||||
port: 9001
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: bucket4j-starter-api-rate-limit-app
|
||||
mvc:
|
||||
throw-exception-if-no-handler-found: true
|
||||
resources:
|
||||
add-mappings: false
|
||||
cache:
|
||||
cache-names:
|
||||
- rate-limit-buckets
|
||||
caffeine:
|
||||
spec: maximumSize=100000,expireAfterAccess=3600s
|
||||
|
||||
bucket4j:
|
||||
enabled: true
|
||||
filters:
|
||||
- cache-name: rate-limit-buckets
|
||||
url: /api/v1/area.*
|
||||
http-response-body: "{ \"status\": 429, \"error\": \"Too Many Requests\", \"message\": \"You have exhausted your API Request Quota\" }"
|
||||
rate-limits:
|
||||
- expression: "getHeader('X-api-key')"
|
||||
execute-condition: "getHeader('X-api-key').startsWith('PX001-')"
|
||||
bandwidths:
|
||||
- capacity: 100
|
||||
time: 1
|
||||
unit: hours
|
||||
- expression: "getHeader('X-api-key')"
|
||||
execute-condition: "getHeader('X-api-key').startsWith('BX001-')"
|
||||
bandwidths:
|
||||
- capacity: 40
|
||||
time: 1
|
||||
unit: hours
|
||||
- expression: "getHeader('X-api-key')"
|
||||
bandwidths:
|
||||
- capacity: 20
|
||||
time: 1
|
||||
unit: hours
|
|
@ -0,0 +1,10 @@
|
|||
server:
|
||||
port: 9000
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: bucket4j-api-rate-limit-app
|
||||
mvc:
|
||||
throw-exception-if-no-handler-found: true
|
||||
resources:
|
||||
add-mappings: false
|
|
@ -0,0 +1,63 @@
|
|||
package com.baeldung.ratelimiting.bootstarterapp;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.RequestBuilder;
|
||||
|
||||
import com.baeldung.ratelimiting.bucket4japp.service.PricingPlan;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = Bucket4jRateLimitApp.class)
|
||||
@TestPropertySource(properties = "spring.config.location=classpath:ratelimiting/application-bucket4j-starter.yml")
|
||||
@AutoConfigureMockMvc
|
||||
public class Bucket4jBootStarterRateLimitIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Test
|
||||
public void givenTriangleAreaCalculator_whenRequestsWithinRateLimit_thenAccepted() throws Exception {
|
||||
|
||||
RequestBuilder request = post("/api/v1/area/triangle").contentType(MediaType.APPLICATION_JSON_VALUE)
|
||||
.content("{ \"height\": 8, \"base\": 10 }")
|
||||
.header("X-api-key", "FX001-UBSZ5YRYQ");
|
||||
|
||||
for (int i = 1; i <= PricingPlan.FREE.bucketCapacity(); i++) {
|
||||
mockMvc.perform(request)
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(header().exists("X-Rate-Limit-Remaining"))
|
||||
.andExpect(jsonPath("$.shape", equalTo("triangle")))
|
||||
.andExpect(jsonPath("$.area", equalTo(40d)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenTriangleAreaCalculator_whenRequestRateLimitTriggered_thenRejected() throws Exception {
|
||||
|
||||
RequestBuilder request = post("/api/v1/area/triangle").contentType(MediaType.APPLICATION_JSON_VALUE)
|
||||
.content("{ \"height\": 8, \"base\": 10 }")
|
||||
.header("X-api-key", "FX001-ZBSY6YSLP");
|
||||
|
||||
for (int i = 1; i <= PricingPlan.FREE.bucketCapacity(); i++) {
|
||||
mockMvc.perform(request); // exhaust limit
|
||||
}
|
||||
|
||||
mockMvc.perform(request)
|
||||
.andExpect(status().isTooManyRequests())
|
||||
.andExpect(jsonPath("$.message", equalTo("You have exhausted your API Request Quota")))
|
||||
.andExpect(header().exists("X-Rate-Limit-Retry-After-Seconds"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package com.baeldung.ratelimiting.bucket4japp;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.RequestBuilder;
|
||||
|
||||
import com.baeldung.ratelimiting.bucket4japp.service.PricingPlan;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = Bucket4jRateLimitApp.class)
|
||||
@AutoConfigureMockMvc
|
||||
public class Bucket4jRateLimitIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Test
|
||||
public void givenRectangleAreaCalculator_whenRequestsWithinRateLimit_thenAccepted() throws Exception {
|
||||
|
||||
RequestBuilder request = post("/api/v1/area/rectangle").contentType(MediaType.APPLICATION_JSON_VALUE)
|
||||
.content("{ \"length\": 12, \"width\": 10 }")
|
||||
.header("X-api-key", "FX001-UBSZ5YRYQ");
|
||||
|
||||
for (int i = 1; i <= PricingPlan.FREE.bucketCapacity(); i++) {
|
||||
mockMvc.perform(request)
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(header().exists("X-Rate-Limit-Remaining"))
|
||||
.andExpect(jsonPath("$.shape", equalTo("rectangle")))
|
||||
.andExpect(jsonPath("$.area", equalTo(120d)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenReactangleAreaCalculator_whenRequestRateLimitTriggered_thenRejected() throws Exception {
|
||||
|
||||
RequestBuilder request = post("/api/v1/area/rectangle").contentType(MediaType.APPLICATION_JSON_VALUE)
|
||||
.content("{ \"length\": 12, \"width\": 10 }")
|
||||
.header("X-api-key", "FX001-ZBSY6YSLP");
|
||||
|
||||
for (int i = 1; i <= PricingPlan.FREE.bucketCapacity(); i++) {
|
||||
mockMvc.perform(request); // exhaust limit
|
||||
}
|
||||
|
||||
mockMvc.perform(request)
|
||||
.andExpect(status().isTooManyRequests())
|
||||
.andExpect(status().reason("You have exhausted your API Request Quota"))
|
||||
.andExpect(header().exists("X-Rate-Limit-Retry-After-Seconds"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package com.baeldung.ratelimiting.bucket4japp;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import io.github.bucket4j.Bandwidth;
|
||||
import io.github.bucket4j.Bucket;
|
||||
import io.github.bucket4j.Bucket4j;
|
||||
import io.github.bucket4j.Refill;
|
||||
|
||||
public class Bucket4jUsageUnitTest {
|
||||
|
||||
@Test
|
||||
public void givenBucketLimit_whenExceedLimit_thenConsumeReturnsFalse() {
|
||||
Refill refill = Refill.intervally(10, Duration.ofMinutes(1));
|
||||
Bandwidth limit = Bandwidth.classic(10, refill);
|
||||
Bucket bucket = Bucket4j.builder()
|
||||
.addLimit(limit)
|
||||
.build();
|
||||
|
||||
for (int i = 1; i <= 10; i++) {
|
||||
assertTrue(bucket.tryConsume(1));
|
||||
}
|
||||
assertFalse(bucket.tryConsume(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenMultipletLimits_whenExceedSmallerLimit_thenConsumeReturnsFalse() {
|
||||
Bucket bucket = Bucket4j.builder()
|
||||
.addLimit(Bandwidth.classic(10, Refill.intervally(10, Duration.ofMinutes(1))))
|
||||
.addLimit(Bandwidth.classic(5, Refill.intervally(5, Duration.ofSeconds(20))))
|
||||
.build();
|
||||
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
assertTrue(bucket.tryConsume(1));
|
||||
}
|
||||
assertFalse(bucket.tryConsume(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenBucketLimit_whenThrottleRequests_thenConsumeReturnsTrue() throws InterruptedException {
|
||||
Refill refill = Refill.intervally(1, Duration.ofSeconds(2));
|
||||
Bandwidth limit = Bandwidth.classic(1, refill);
|
||||
Bucket bucket = Bucket4j.builder()
|
||||
.addLimit(limit)
|
||||
.build();
|
||||
|
||||
assertTrue(bucket.tryConsume(1));
|
||||
|
||||
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
executor.schedule(new AssertTryConsume(bucket, latch), 2, TimeUnit.SECONDS);
|
||||
|
||||
latch.await();
|
||||
}
|
||||
|
||||
static class AssertTryConsume implements Runnable {
|
||||
|
||||
private Bucket bucket;
|
||||
private CountDownLatch latch;
|
||||
|
||||
AssertTryConsume(Bucket bucket, CountDownLatch latch) {
|
||||
this.bucket = bucket;
|
||||
this.latch = latch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
assertTrue(bucket.tryConsume(1));
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package com.baeldung.ratelimiting.bucket4japp;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.baeldung.ratelimiting.bucket4japp.service.PricingPlan;
|
||||
import com.baeldung.ratelimiting.bucket4japp.service.PricingPlanService;
|
||||
|
||||
import io.github.bucket4j.Bucket;
|
||||
|
||||
public class PricingPlanServiceUnitTest {
|
||||
|
||||
private PricingPlanService service = new PricingPlanService();
|
||||
|
||||
@Test
|
||||
public void givenAPIKey_whenFreePlan_thenReturnFreePlanBucket() {
|
||||
Bucket bucket = service.resolveBucket("FX001-UBSZ5YRYQ");
|
||||
|
||||
assertEquals(PricingPlan.FREE.bucketCapacity(), bucket.getAvailableTokens());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenAPIKey_whenBasiclan_thenReturnBasicPlanBucket() {
|
||||
Bucket bucket = service.resolveBucket("BX001-MBSZ5YRYP");
|
||||
|
||||
assertEquals(PricingPlan.BASIC.bucketCapacity(), bucket.getAvailableTokens());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenAPIKey_whenProfessionalPlan_thenReturnProfessionalPlanBucket() {
|
||||
Bucket bucket = service.resolveBucket("PX001-NBSZ5YRYY");
|
||||
|
||||
assertEquals(PricingPlan.PROFESSIONAL.bucketCapacity(), bucket.getAvailableTokens());
|
||||
}
|
||||
}
|
|
@ -75,7 +75,6 @@
|
|||
</build>
|
||||
|
||||
<properties>
|
||||
<spring-boot.version>2.1.1.RELEASE</spring-boot.version>
|
||||
<start-class>com.baeldung.birt.engine.ReportEngineApplication</start-class>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Git
|
||||
.git
|
||||
.cache
|
||||
|
||||
# Classes
|
||||
**/*.class
|
||||
|
||||
# Ignore md files
|
||||
*.md
|
|
@ -0,0 +1,10 @@
|
|||
FROM maven:3.6.0-jdk-11
|
||||
WORKDIR /code/spring-boot-modules/spring-boot-properties/
|
||||
COPY ./spring-boot-modules/spring-boot-properties/pom.xml .
|
||||
COPY ./spring-boot-modules/spring-boot-properties/src ./src
|
||||
COPY ./parent-boot-2/pom.xml /code/parent-boot-2/pom.xml
|
||||
COPY ./pom.xml /code/pom.xml
|
||||
COPY ./custom-pmd-0.0.1.jar /code/custom-pmd-0.0.1.jar
|
||||
COPY ./baeldung-pmd-rules.xml /code/baeldung-pmd-rules.xml
|
||||
RUN mvn dependency:resolve
|
||||
CMD ["mvn", "spring-boot:run"]
|
|
@ -128,7 +128,8 @@
|
|||
<httpcore.version>4.4.11</httpcore.version>
|
||||
<resource.delimiter>@</resource.delimiter>
|
||||
<configuration-processor.version>2.2.4.RELEASE</configuration-processor.version>
|
||||
<start-class>com.baeldung.buildproperties.Application</start-class>
|
||||
<!-- <start-class>com.baeldung.buildproperties.Application</start-class> -->
|
||||
<start-class>com.baeldung.yaml.MyApplication</start-class>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
spring:
|
||||
profiles:
|
||||
active:
|
||||
- test
|
||||
|
||||
---
|
||||
|
||||
spring:
|
||||
profiles: test
|
||||
name: test-YAML
|
||||
environment: test
|
||||
environment: testing
|
||||
servers:
|
||||
- www.abc.test.com
|
||||
- www.xyz.test.com
|
||||
|
@ -15,3 +22,13 @@ environment: production
|
|||
servers:
|
||||
- www.abc.com
|
||||
- www.xyz.com
|
||||
|
||||
---
|
||||
|
||||
spring:
|
||||
profiles: dev
|
||||
name: ${DEV_NAME:dev-YAML}
|
||||
environment: development
|
||||
servers:
|
||||
- www.abc.dev.com
|
||||
- www.xyz.dev.com
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package com.baeldung.yaml;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = MyApplication.class)
|
||||
@TestPropertySource(properties = {"spring.profiles.active = dev"})
|
||||
class YAMLDevIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private YAMLConfig config;
|
||||
|
||||
@Test
|
||||
void whenProfileTest_thenNameTesting() {
|
||||
assertTrue("development".equalsIgnoreCase(config.getEnvironment()));
|
||||
assertTrue("dev-YAML".equalsIgnoreCase(config.getName()));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package com.baeldung.yaml;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = MyApplication.class)
|
||||
class YAMLIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private YAMLConfig config;
|
||||
|
||||
@Test
|
||||
void whenProfileTest_thenNameTesting() {
|
||||
assertTrue("testing".equalsIgnoreCase(config.getEnvironment()));
|
||||
assertTrue("test-YAML".equalsIgnoreCase(config.getName()));
|
||||
}
|
||||
}
|
|
@ -24,6 +24,16 @@
|
|||
<artifactId>spring-core</artifactId>
|
||||
<version>${spring.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-expression</artifactId>
|
||||
<version>${spring.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>28.2-jre</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
|
@ -42,6 +52,18 @@
|
|||
<version>${junit-jupiter.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.awaitility</groupId>
|
||||
<artifactId>awaitility</artifactId>
|
||||
<version>4.0.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>2.9.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@ -60,4 +82,4 @@
|
|||
<spring.boot.version>2.2.2.RELEASE</spring.boot.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
</project>
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
package com.baeldung.postprocessor;
|
||||
|
||||
import com.google.common.eventbus.AsyncEventBus;
|
||||
import com.google.common.eventbus.EventBus;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@SuppressWarnings("ALL")
|
||||
public final class GlobalEventBus {
|
||||
|
||||
public static final String GLOBAL_EVENT_BUS_EXPRESSION = "T(com.baeldung.postprocessor.GlobalEventBus).getEventBus()";
|
||||
|
||||
private static final String IDENTIFIER = "global-event-bus";
|
||||
|
||||
private static final GlobalEventBus GLOBAL_EVENT_BUS = new GlobalEventBus();
|
||||
|
||||
private final EventBus eventBus = new AsyncEventBus(IDENTIFIER, Executors.newCachedThreadPool());
|
||||
|
||||
private GlobalEventBus() {
|
||||
}
|
||||
|
||||
public static GlobalEventBus getInstance() {
|
||||
return GlobalEventBus.GLOBAL_EVENT_BUS;
|
||||
}
|
||||
|
||||
public static EventBus getEventBus() {
|
||||
return GlobalEventBus.GLOBAL_EVENT_BUS.eventBus;
|
||||
}
|
||||
|
||||
public static void subscribe(Object obj) {
|
||||
getEventBus().register(obj);
|
||||
}
|
||||
public static void unsubscribe(Object obj) {
|
||||
getEventBus().unregister(obj);
|
||||
}
|
||||
public static void post(Object event) {
|
||||
getEventBus().post(event);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package com.baeldung.postprocessor;
|
||||
|
||||
import com.google.common.eventbus.EventBus;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.FatalBeanException;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionException;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
|
||||
@SuppressWarnings("ALL")
|
||||
public class GuavaEventBusBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
private final SpelExpressionParser expressionParser = new SpelExpressionParser();
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
for (Iterator<String> names = beanFactory.getBeanNamesIterator(); names.hasNext(); ) {
|
||||
Object proxy = this.getTargetObject(beanFactory.getBean(names.next()));
|
||||
final Subscriber annotation = AnnotationUtils.getAnnotation(proxy.getClass(), Subscriber.class);
|
||||
if (annotation == null)
|
||||
continue;
|
||||
this.logger.info("{}: processing bean of type {} during initialization", this.getClass().getSimpleName(),
|
||||
proxy.getClass().getName());
|
||||
final String annotationValue = annotation.value();
|
||||
try {
|
||||
final Expression expression = this.expressionParser.parseExpression(annotationValue);
|
||||
final Object value = expression.getValue();
|
||||
if (!(value instanceof EventBus)) {
|
||||
this.logger.error("{}: expression {} did not evaluate to an instance of EventBus for bean of type {}",
|
||||
this.getClass().getSimpleName(), annotationValue, proxy.getClass().getSimpleName());
|
||||
return;
|
||||
}
|
||||
final EventBus eventBus = (EventBus)value;
|
||||
eventBus.register(proxy);
|
||||
} catch (ExpressionException ex) {
|
||||
this.logger.error("{}: unable to parse/evaluate expression {} for bean of type {}", this.getClass().getSimpleName(),
|
||||
annotationValue, proxy.getClass().getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Object getTargetObject(Object proxy) throws BeansException {
|
||||
if (AopUtils.isJdkDynamicProxy(proxy)) {
|
||||
try {
|
||||
return ((Advised)proxy).getTargetSource().getTarget();
|
||||
} catch (Exception e) {
|
||||
throw new FatalBeanException("Error getting target of JDK proxy", e);
|
||||
}
|
||||
}
|
||||
return proxy;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package com.baeldung.postprocessor;
|
||||
|
||||
import com.google.common.eventbus.EventBus;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.FatalBeanException;
|
||||
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionException;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
|
||||
/**
|
||||
* A {@link DestructionAwareBeanPostProcessor} which registers/un-registers subscribers to a Guava {@link EventBus}. The class must
|
||||
* be annotated with {@link Subscriber} and each subscribing method must be annotated with
|
||||
* {@link com.google.common.eventbus.Subscribe}.
|
||||
*/
|
||||
@SuppressWarnings("ALL")
|
||||
public class GuavaEventBusBeanPostProcessor implements DestructionAwareBeanPostProcessor {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
private final SpelExpressionParser expressionParser = new SpelExpressionParser();
|
||||
|
||||
@Override
|
||||
public void postProcessBeforeDestruction(final Object bean, final String beanName) throws BeansException {
|
||||
this.process(bean, EventBus::unregister, "destruction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresDestruction(Object bean) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException {
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
|
||||
this.process(bean, EventBus::register, "initialization");
|
||||
return bean;
|
||||
}
|
||||
|
||||
private void process(final Object bean, final BiConsumer<EventBus, Object> consumer, final String action) {
|
||||
Object proxy = this.getTargetObject(bean);
|
||||
final Subscriber annotation = AnnotationUtils.getAnnotation(proxy.getClass(), Subscriber.class);
|
||||
if (annotation == null)
|
||||
return;
|
||||
this.logger.info("{}: processing bean of type {} during {}", this.getClass().getSimpleName(), proxy.getClass().getName(),
|
||||
action);
|
||||
final String annotationValue = annotation.value();
|
||||
try {
|
||||
final Expression expression = this.expressionParser.parseExpression(annotationValue);
|
||||
final Object value = expression.getValue();
|
||||
if (!(value instanceof EventBus)) {
|
||||
this.logger.error("{}: expression {} did not evaluate to an instance of EventBus for bean of type {}",
|
||||
this.getClass().getSimpleName(), annotationValue, proxy.getClass().getSimpleName());
|
||||
return;
|
||||
}
|
||||
final EventBus eventBus = (EventBus)value;
|
||||
consumer.accept(eventBus, proxy);
|
||||
} catch (ExpressionException ex) {
|
||||
this.logger.error("{}: unable to parse/evaluate expression {} for bean of type {}", this.getClass().getSimpleName(),
|
||||
annotationValue, proxy.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
private Object getTargetObject(Object proxy) throws BeansException {
|
||||
if (AopUtils.isJdkDynamicProxy(proxy)) {
|
||||
try {
|
||||
return ((Advised)proxy).getTargetSource().getTarget();
|
||||
} catch (Exception e) {
|
||||
throw new FatalBeanException("Error getting target of JDK proxy", e);
|
||||
}
|
||||
}
|
||||
return proxy;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.baeldung.postprocessor;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class StockTrade {
|
||||
|
||||
private final String symbol;
|
||||
private final int quantity;
|
||||
private final double price;
|
||||
private final Date tradeDate;
|
||||
|
||||
public StockTrade(String symbol, int quantity, double price, Date tradeDate) {
|
||||
this.symbol = symbol;
|
||||
this.quantity = quantity;
|
||||
this.price = price;
|
||||
this.tradeDate = tradeDate;
|
||||
}
|
||||
|
||||
public String getSymbol() {
|
||||
return this.symbol;
|
||||
}
|
||||
|
||||
public int getQuantity() {
|
||||
return this.quantity;
|
||||
}
|
||||
|
||||
public double getPrice() {
|
||||
return this.price;
|
||||
}
|
||||
|
||||
public Date getTradeDate() {
|
||||
return this.tradeDate;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package com.baeldung.postprocessor;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface StockTradeListener {
|
||||
|
||||
void stockTradePublished(StockTrade trade);
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package com.baeldung.postprocessor;
|
||||
|
||||
import com.google.common.eventbus.AllowConcurrentEvents;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@Subscriber
|
||||
public class StockTradePublisher {
|
||||
|
||||
private final Set<StockTradeListener> stockTradeListeners = new HashSet<>();
|
||||
|
||||
public void addStockTradeListener(StockTradeListener listener) {
|
||||
synchronized (this.stockTradeListeners) {
|
||||
this.stockTradeListeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeStockTradeListener(StockTradeListener listener) {
|
||||
synchronized (this.stockTradeListeners) {
|
||||
this.stockTradeListeners.remove(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
@AllowConcurrentEvents
|
||||
private void handleNewStockTradeEvent(StockTrade trade) {
|
||||
// publish to DB, send to PubNub, whatever you want here
|
||||
final Set<StockTradeListener> listeners;
|
||||
synchronized (this.stockTradeListeners) {
|
||||
listeners = new HashSet<>(this.stockTradeListeners);
|
||||
}
|
||||
listeners.forEach(li -> li.stockTradePublished(trade));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package com.baeldung.postprocessor;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* An annotation which indicates which Guava {@link com.google.common.eventbus.EventBus} a Spring bean wishes to subscribe to.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Inherited
|
||||
public @interface Subscriber {
|
||||
|
||||
/**
|
||||
* A SpEL expression which selects the {@link com.google.common.eventbus.EventBus}.
|
||||
*/
|
||||
String value() default GlobalEventBus.GLOBAL_EVENT_BUS_EXPRESSION;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package com.baeldung.postprocessor;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class PostProcessorConfiguration {
|
||||
|
||||
@Bean
|
||||
public GlobalEventBus eventBus() {
|
||||
return GlobalEventBus.getInstance();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GuavaEventBusBeanPostProcessor eventBusBeanPostProcessor() {
|
||||
return new GuavaEventBusBeanPostProcessor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public StockTradePublisher stockTradePublisher() {
|
||||
return new StockTradePublisher();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package com.baeldung.postprocessor;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = {PostProcessorConfiguration.class})
|
||||
public class StockTradeIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private StockTradePublisher stockTradePublisher;
|
||||
|
||||
@Test
|
||||
public void givenValidConfig_whenTradePublished_thenTradeReceived() {
|
||||
Date tradeDate = new Date();
|
||||
StockTrade stockTrade = new StockTrade("AMZN", 100, 2483.52d, tradeDate);
|
||||
AtomicBoolean assertionsPassed = new AtomicBoolean(false);
|
||||
StockTradeListener listener = trade -> assertionsPassed.set(this.verifyExact(stockTrade, trade));
|
||||
this.stockTradePublisher.addStockTradeListener(listener);
|
||||
try {
|
||||
GlobalEventBus.post(stockTrade);
|
||||
await().atMost(Duration.ofSeconds(2L))
|
||||
.untilAsserted(() -> assertThat(assertionsPassed.get()).isTrue());
|
||||
} finally {
|
||||
this.stockTradePublisher.removeStockTradeListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean verifyExact(StockTrade stockTrade, StockTrade trade) {
|
||||
return Objects.equals(stockTrade.getSymbol(), trade.getSymbol())
|
||||
&& Objects.equals(stockTrade.getTradeDate(), trade.getTradeDate())
|
||||
&& stockTrade.getQuantity() == trade.getQuantity()
|
||||
&& stockTrade.getPrice() == trade.getPrice();
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
<modules>
|
||||
<module>spring-security-acl</module>
|
||||
<module>spring-security-auth0</module>
|
||||
<module>spring-security-angular/server</module>
|
||||
<module>spring-security-cache-control</module>
|
||||
<module>spring-security-core</module>
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>spring-security-auth0</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<name>spring-security-auth0</name>
|
||||
<packaging>war</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>com.baeldung</groupId>
|
||||
<artifactId>parent-boot-2</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<relativePath>../../parent-boot-2</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-oauth2-resource-server</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.auth0</groupId>
|
||||
<artifactId>mvc-auth-commons</artifactId>
|
||||
<version>${mvc-auth-commons.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>${json.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>spring-security-auth0</finalName>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<addResources>true</addResources>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<properties>
|
||||
<json.version>20190722</json.version>
|
||||
<mvc-auth-commons.version>1.2.0</mvc-auth-commons.version>
|
||||
</properties>
|
||||
</project>
|
|
@ -0,0 +1,13 @@
|
|||
package com.baeldung.auth0;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
package com.baeldung.auth0;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
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.web.authentication.logout.LogoutSuccessHandler;
|
||||
|
||||
import com.auth0.AuthenticationController;
|
||||
import com.baeldung.auth0.controller.LogoutController;
|
||||
import com.auth0.jwk.JwkProvider;
|
||||
import com.auth0.jwk.JwkProviderBuilder;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class AuthConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Value(value = "${com.auth0.domain}")
|
||||
private String domain;
|
||||
|
||||
@Value(value = "${com.auth0.clientId}")
|
||||
private String clientId;
|
||||
|
||||
@Value(value = "${com.auth0.clientSecret}")
|
||||
private String clientSecret;
|
||||
|
||||
@Value(value = "${com.auth0.managementApi.clientId}")
|
||||
private String managementApiClientId;
|
||||
|
||||
@Value(value = "${com.auth0.managementApi.clientSecret}")
|
||||
private String managementApiClientSecret;
|
||||
|
||||
@Value(value = "${com.auth0.managementApi.grantType}")
|
||||
private String grantType;
|
||||
|
||||
@Bean
|
||||
public LogoutSuccessHandler logoutSuccessHandler() {
|
||||
return new LogoutController();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationController authenticationController() throws UnsupportedEncodingException {
|
||||
JwkProvider jwkProvider = new JwkProviderBuilder(domain).build();
|
||||
return AuthenticationController.newBuilder(domain, clientId, clientSecret)
|
||||
.withJwkProvider(jwkProvider)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.csrf().disable();
|
||||
http
|
||||
.authorizeRequests()
|
||||
.antMatchers("/callback", "/login", "/").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.formLogin()
|
||||
.loginPage("/login")
|
||||
.and()
|
||||
.logout().logoutSuccessHandler(logoutSuccessHandler()).permitAll();
|
||||
}
|
||||
|
||||
public String getDomain() {
|
||||
return domain;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public String getClientSecret() {
|
||||
return clientSecret;
|
||||
}
|
||||
|
||||
public String getManagementApiClientId() {
|
||||
return managementApiClientId;
|
||||
}
|
||||
|
||||
public String getManagementApiClientSecret() {
|
||||
return managementApiClientSecret;
|
||||
}
|
||||
|
||||
public String getGrantType() {
|
||||
return grantType;
|
||||
}
|
||||
|
||||
public String getUserInfoUrl() {
|
||||
return "https://" + getDomain() + "/userinfo";
|
||||
}
|
||||
|
||||
public String getUsersUrl() {
|
||||
return "https://" + getDomain() + "/api/v2/users";
|
||||
}
|
||||
|
||||
public String getUsersByEmailUrl() {
|
||||
return "https://" + getDomain() + "/api/v2/users-by-email?email=";
|
||||
}
|
||||
|
||||
public String getLogoutUrl() {
|
||||
return "https://" + getDomain() +"/v2/logout";
|
||||
}
|
||||
|
||||
public String getContextPath(HttpServletRequest request) {
|
||||
String path = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort();
|
||||
return path;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package com.baeldung.auth0.controller;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import com.auth0.AuthenticationController;
|
||||
import com.auth0.IdentityVerificationException;
|
||||
import com.auth0.Tokens;
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||
import com.baeldung.auth0.AuthConfig;
|
||||
|
||||
@Controller
|
||||
public class AuthController {
|
||||
|
||||
@Autowired
|
||||
private AuthenticationController authenticationController;
|
||||
|
||||
@Autowired
|
||||
private AuthConfig config;
|
||||
|
||||
private static final String AUTH0_TOKEN_URL = "https://dev-example.auth0.com/oauth/token";
|
||||
|
||||
@GetMapping(value = "/login")
|
||||
protected void login(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
String redirectUri = config.getContextPath(request) + "/callback";
|
||||
String authorizeUrl = authenticationController.buildAuthorizeUrl(request, response, redirectUri)
|
||||
.withScope("openid email")
|
||||
.build();
|
||||
response.sendRedirect(authorizeUrl);
|
||||
}
|
||||
|
||||
@GetMapping(value="/callback")
|
||||
public void callback(HttpServletRequest request, HttpServletResponse response) throws IOException, IdentityVerificationException {
|
||||
Tokens tokens = authenticationController.handle(request, response);
|
||||
|
||||
DecodedJWT jwt = JWT.decode(tokens.getIdToken());
|
||||
TestingAuthenticationToken authToken2 = new TestingAuthenticationToken(jwt.getSubject(), jwt.getToken());
|
||||
authToken2.setAuthenticated(true);
|
||||
|
||||
SecurityContextHolder.getContext().setAuthentication(authToken2);
|
||||
response.sendRedirect(config.getContextPath(request) + "/");
|
||||
}
|
||||
|
||||
public String getManagementApiToken() {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
JSONObject requestBody = new JSONObject();
|
||||
requestBody.put("client_id", config.getManagementApiClientId());
|
||||
requestBody.put("client_secret", config.getManagementApiClientSecret());
|
||||
requestBody.put("audience", "https://dev-example.auth0.com/api/v2/");
|
||||
requestBody.put("grant_type", config.getGrantType());
|
||||
|
||||
HttpEntity<String> request = new HttpEntity<String>(requestBody.toString(), headers);
|
||||
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
HashMap<String, String> result = restTemplate.postForObject(AUTH0_TOKEN_URL, request, HashMap.class);
|
||||
|
||||
return result.get("access_token");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package com.baeldung.auth0.controller;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||
|
||||
@Controller
|
||||
public class HomeController {
|
||||
|
||||
@GetMapping(value = "/")
|
||||
@ResponseBody
|
||||
public String home(HttpServletRequest request, HttpServletResponse response, final Authentication authentication) throws IOException {
|
||||
|
||||
if (authentication!= null && authentication instanceof TestingAuthenticationToken) {
|
||||
TestingAuthenticationToken token = (TestingAuthenticationToken) authentication;
|
||||
|
||||
DecodedJWT jwt = JWT.decode(token.getCredentials().toString());
|
||||
String email = jwt.getClaims().get("email").asString();
|
||||
|
||||
return "Welcome, " + email + "!";
|
||||
} else {
|
||||
response.sendRedirect("http://localhost:8080/login");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package com.baeldung.auth0.controller;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
import com.baeldung.auth0.AuthConfig;
|
||||
|
||||
@Controller
|
||||
public class LogoutController implements LogoutSuccessHandler {
|
||||
|
||||
@Autowired
|
||||
private AuthConfig config;
|
||||
|
||||
@Override
|
||||
public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse res, Authentication authentication) {
|
||||
if (req.getSession() != null) {
|
||||
req.getSession().invalidate();
|
||||
}
|
||||
String returnTo = config.getContextPath(req);
|
||||
String logoutUrl = config.getLogoutUrl() + "?client_id=" + config.getClientId() + "&returnTo=" +returnTo;
|
||||
try {
|
||||
res.sendRedirect(logoutUrl);
|
||||
} catch(IOException e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package com.baeldung.auth0.controller;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import com.auth0.IdentityVerificationException;
|
||||
import com.baeldung.auth0.AuthConfig;
|
||||
import com.baeldung.auth0.service.ApiService;
|
||||
|
||||
@Controller
|
||||
public class UserController {
|
||||
|
||||
@Autowired
|
||||
private ApiService apiService;
|
||||
|
||||
@Autowired
|
||||
private AuthConfig config;
|
||||
|
||||
@GetMapping(value="/users")
|
||||
@ResponseBody
|
||||
public ResponseEntity<String> users(HttpServletRequest request, HttpServletResponse response) throws IOException, IdentityVerificationException {
|
||||
ResponseEntity<String> result = apiService.getCall(config.getUsersUrl());
|
||||
return result;
|
||||
}
|
||||
|
||||
@GetMapping(value = "/userByEmail")
|
||||
@ResponseBody
|
||||
public ResponseEntity<String> userByEmail(HttpServletResponse response, @RequestParam String email) {
|
||||
ResponseEntity<String> result = apiService.getCall(config.getUsersByEmailUrl()+email);
|
||||
return result;
|
||||
}
|
||||
|
||||
@GetMapping(value = "/createUser")
|
||||
@ResponseBody
|
||||
public ResponseEntity<String> createUser(HttpServletResponse response) {
|
||||
JSONObject request = new JSONObject();
|
||||
request.put("email", "norman.lewis@email.com");
|
||||
request.put("given_name", "Norman");
|
||||
request.put("family_name", "Lewis");
|
||||
request.put("connection", "Username-Password-Authentication");
|
||||
request.put("password", "Pa33w0rd");
|
||||
|
||||
ResponseEntity<String> result = apiService.postCall(config.getUsersUrl(), request.toString());
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package com.baeldung.auth0.service;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import com.baeldung.auth0.controller.AuthController;
|
||||
|
||||
@Service
|
||||
public class ApiService {
|
||||
|
||||
@Autowired
|
||||
private AuthController controller;
|
||||
|
||||
public ResponseEntity<String> getCall(String url) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.set("Authorization", "Bearer "+controller.getManagementApiToken());
|
||||
|
||||
HttpEntity<String> entity = new HttpEntity<String>(headers);
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
ResponseEntity<String> result = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public ResponseEntity<String> postCall(String url, String requestBody) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.set("Authorization", "Bearer "+controller.getManagementApiToken());
|
||||
|
||||
HttpEntity<String> request = new HttpEntity<String>(requestBody, headers);
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
ResponseEntity<String> result = restTemplate.postForEntity(url, request, String.class);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
com.auth0.domain: dev-example.auth0.com
|
||||
com.auth0.clientId: exampleClientId
|
||||
com.auth0.clientSecret: exampleClientSecret
|
||||
|
||||
com.auth0.managementApi.clientId: exampleManagementApiClientId
|
||||
com.auth0.managementApi.clientSecret: exampleManagementApiClientSecret
|
||||
com.auth0.managementApi.grantType: client_credentials
|
|
@ -27,7 +27,7 @@ public class CurrenciesControllerIntegrationTest {
|
|||
.header("Accept-Language", "es-ES")
|
||||
.param("amount", "10032.5"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(containsString("10.032,50 €")));
|
||||
.andExpect(content().string(containsString("10.032,50")));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -42,10 +42,10 @@ public class CurrenciesControllerIntegrationTest {
|
|||
@Test
|
||||
public void whenCallCurrencyWithRomanianLocaleWithArrays_ThenReturnLocaleCurrencies() throws Exception {
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/currency")
|
||||
.header("Accept-Language", "ro-RO")
|
||||
.header("Accept-Language", "en-GB")
|
||||
.param("amountList", "10", "20", "30"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(containsString("10,00 RON, 20,00 RON, 30,00 RON")));
|
||||
.andExpect(content().string(containsString("£10.00, £20.00, £30.00")));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Reference in New Issue