The following are the dependencies defined in the pom.xml
file for the Order Service API:
spring-boot-starter-actuator
: Provides production-ready features to help you monitor and manage your application.spring-boot-starter-data-jpa
: Provides Spring Data JPA and Hibernate support.spring-boot-starter-web
: Provides Spring MVC and embedded Tomcat server.
micrometer-tracing-bridge-brave
: Provides a Micrometer tracing bridge for the Brave tracing system.zipkin-reporter-brave
: Provides a Zipkin reporter for Brave tracing system.
spring-cloud-starter-netflix-eureka-client
: Provides integration with Netflix Eureka service discovery.
mysql-connector-j
: Provides the MySQL JDBC driver to communicate with MySQL databases.spring-boot-starter-test
: Provides testing support for Spring Boot applications.
lombok
: Provides annotation-based shortcuts for common Java code patterns.spring-cloud-starter-config
: Provides Spring Cloud Config support to externalize configuration properties.spring-boot-devtools
: Provides additional development-time features to improve developer productivity.spring-cloud-starter-openfeign
: Provides integration with the OpenFeign client to simplify RESTful service calls.spring-cloud-starter-circuitbreaker-resilience4j
: Provides integration with Resilience4j, a lightweight fault tolerance library.
Lombok is a Java library that helps to reduce boilerplate code by generating getters, setters, constructors, and other utility methods using annotations. Here is a list of some common Lombok annotations along with their brief descriptions:
@Getter
: Generates a getter method for the annotated field.@Setter
: Generates a setter method for the annotated field.@ToString
: Generates atoString()
method for the class based on the fields.@EqualsAndHashCode
: Generatesequals()
andhashCode()
methods for the class based on the fields.@NoArgsConstructor
: Generates a no-argument constructor for the class.@AllArgsConstructor
: Generates an all-arguments constructor for the class, where each argument corresponds to a field in the class.@RequiredArgsConstructor
: Generates a constructor for the class with required arguments only. Fields marked withfinal
or annotated with@NonNull
are considered required.@Data
: A shortcut annotation that combines@Getter
,@Setter
,@ToString
,@EqualsAndHashCode
, and@RequiredArgsConstructor
.@Builder
: Generates a builder pattern implementation for the class, allowing you to create instances using a fluent API.@SneakyThrows
: Allows you to throw checked exceptions without explicitly declaring them in the method'sthrows
clause.@Value
: A shortcut annotation that combines@Getter
,@ToString
,@EqualsAndHashCode
,@AllArgsConstructor
, and makes all fieldsfinal
. Useful for creating immutable value classes.@NoArgsConstructor(force = true)
: Generates a no-argument constructor for the class, setting all fields to their default values (even forfinal
fields).@NonNull
: An annotation used on a field or method parameter to indicate that null values are not allowed. When used with@RequiredArgsConstructor
, a null-check is added to the constructor.@Cleanup
: Ensures that the resource (likeInputStream
,OutputStream
,Reader
, orWriter
) is closed after it is used, even if an exception is thrown.@Delegate
: Automatically delegates method calls from the annotated field to the field's type.@With
: Generates "with" methods for the annotated field. A "with" method is like a setter, but it returns a new instance of the class with the new field value, rather than modifying the current instance.
You can find more information and examples of these annotations in the Lombok documentation: https://projectlombok.org/features/all
-
Separation of concerns: It allows you to separate the incoming request data representation from the actual entity representation in your application. This way, you can make changes to the request model without affecting the underlying entity directly.
-
Validation: By creating a separate request model, you can apply validation rules specific to incoming requests. For example, you may want to ensure that certain fields are provided in the request or meet specific criteria. Using a separate request model makes it easier to apply and maintain these validation rules.
-
Data mapping: Sometimes, the incoming request data may not match the structure of the entity you're working with. In these cases, using a separate request model makes it easier to map the incoming data to the entity properties.
-
Flexibility: Having a separate request model allows you to evolve your API without impacting the underlying entity. For example, you might want to add additional fields to your request model that are not part of the entity, such as metadata or user-specific information.
The ProductController
class includes the following API endpoints, which cover all the business logic implemented in the ProductServiceImpl
class:
-
Create a new product (HTTP POST)
Endpoint:/product
Method:createProduct
-
Get a single product by its ID (HTTP GET)
Endpoint:/product/{id}
Method:getProductById
-
Get all products (HTTP GET)
Endpoint:/product
Method:getAllProducts
-
Update a product by its ID (HTTP PUT)
Endpoint:/product/{id}
Method:updateProduct
-
Delete a product by its ID (HTTP DELETE)
Endpoint:/product/{id}
Method:deleteProduct
-
Get products within a specified price range (HTTP GET)
Endpoint:/product/price-range?min={min}&max={max}
Method:getProductsByPriceRange
POST /product/create-product
Creates a new product with the given details.
{
"productName": "Sample Product",
"price": 10,
"quantity": 100
}
{
"productId": 1,
"productName": "Sample Product",
"price": 10,
"quantity": 100
}
GET /product
Returns a list of all available products.
[
{
"productId": 1,
"productName": "Product 1",
"price": 10,
"quantity": 100
},
{
"productId": 2,
"productName": "Product 2",
"price": 20,
"quantity": 50
},
{
"productId": 3,
"productName": "Product 3",
"price": 30,
"quantity": 25
},
...
]
GET /product/{id}
Returns the product with the given ID.
{
"productId": 1,
"productName": "Product 1",
"price": 10,
"quantity": 100
}
PUT /product/{id}
Updates the product with the given ID.
{
"productName": "Updated Product",
"price": 15,
"quantity": 75
}
{
"productId": 1,
"productName": "Updated Product",
"price": 15,
"quantity": 75
}
DELETE /product/{id}
Deletes the product with the given ID.
{
"productId": 1,
"productName": "Product 1",
"price": 10,
"quantity": 100
}
Request Body:
The request body should be a JSON object that includes the productId
and quantity
fields. For example:
{
"productId": 123,
"quantity": 10
}
Response Body:
If the order is placed successfully, the response body will be a JSON object representing the created order, which includes the id
, productId
, quantity
, orderDate
, orderStatus
, and amount
fields. For example:
{
"id": 1,
"productId": 123,
"quantity": 10,
"orderDate": "2022-05-01T10:10:10Z",
"orderStatus": "PROCESSING",
"amount": 100
}
Response Body:
If the order is found, the response body will be a JSON object representing the order, which includes the id
, productId
, quantity
, orderDate
, orderStatus
, and amount
fields. For example:
{
"id": 1,
"productId": 123,
"quantity": 10,
"orderDate": "2022-05-01T10:10:10Z",
"orderStatus": "PROCESSING",
"amount": 100
}
If the order is not found, the response body will be empty.
Response Body:
If there are orders available, the response body will be a JSON array of objects, each representing an order. Each object includes the id
, productId
, quantity
, orderDate
, orderStatus
, and amount
fields. For example:
[
{
"id": 1,
"productId": 123,
"quantity": 10,
"orderDate": "2022-05-01T10:10:10Z",
"orderStatus": "PROCESSING",
"amount": 100
},
{
"id": 2,
"productId": 456,
"quantity": 5,
"orderDate": "2022-05-02T10:10:10Z",
"orderStatus": "COMPLETED",
"amount": 50
}
]
If there are no orders available, the response body will be an empty array.
Okta OpenID Connect Integration with Spring Boot
This project demonstrates the integration of Okta's OpenID Connect for authentication in a Spring Boot application. It utilizes Spring Security to secure the application and enables users to authenticate using OAuth 2.0 and JWT (JSON Web Tokens) with Okta as the identity provider.
- Java
- Spring Boot
- Spring Security
- Okta
- JWT
The main configuration code in the project is found in the OktaOAuth2WebSecurity
class. Here's an explanation of the configuration:
-
@Bean
annotation: Indicates that the method should be treated as a bean and its return value should be registered in the Spring application context. -
SecurityWebFilterChain
: Represents the security filter chain for the application. It defines the security rules and filters to be applied to incoming HTTP requests. -
authorizeExchange()
: Configures the authorization rules for different exchange patterns. In this case,anyExchange().authenticated()
is used to require authentication for any incoming request. -
oauth2Login()
: Configures the OAuth 2.0 login flow, enabling the login process using OAuth 2.0. It sets up the necessary authentication filters and handlers. -
oauth2ResourceServer()
: Configures the application as an OAuth 2.0 resource server, allowing it to validate and process OAuth 2.0 access tokens for authentication and authorization. -
jwt()
: Configures the resource server to validate and process JWT (JSON Web Token) access tokens. -
return http.build()
: Returns the built ServerHttpSecurity object representing the configured security filter chain.
The main configuration for the authentication controller is in the AuthenticationController class. It handles the /authenticate/login endpoint and retrieves the user's authentication details from the OidcUser and OAuth2AuthorizedClient objects.
Please make sure to configure the Okta integration properties in the application's properties file before running the application.
To use the application:
- Launch the application.
- Access the application in your browser.
- You will be redirected to the Okta login page to authenticate.
- After successful authentication, you will be redirected back to the application.
- You can now access the protected resources and endpoints in the application.
The provided code is a Spring Boot REST controller that handles the authentication-related endpoints. Let's go through the code and explain its functionality:
package com.example.apigateway.controller;
import com.example.apigateway.model.AuthenticationResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/authenticate")
public class AuthenticationController {
- The
AuthenticationController
class is a Spring Boot REST controller annotated with@RestController
. It handles authentication-related endpoints.
@GetMapping("/login")
public ResponseEntity<AuthenticationResponse> login(
@AuthenticationPrincipal OidcUser oidcUser,
Model model,
@RegisteredOAuth2AuthorizedClient("okta")
OAuth2AuthorizedClient client
) {
- The
login
method is annotated with@GetMapping("/login")
and handles GET requests to the "/authenticate/login" endpoint. - The method parameters include:
@AuthenticationPrincipal OidcUser oidcUser
: Annotating this parameter with@AuthenticationPrincipal
allows the injection of the currently authenticated user object,OidcUser
. It provides access to the user's details, such as the email.Model model
: This parameter allows adding attributes to the model for view rendering. However, it is not used in this code snippet.@RegisteredOAuth2AuthorizedClient("okta") OAuth2AuthorizedClient client
: Annotating this parameter with@RegisteredOAuth2AuthorizedClient("okta")
allows the injection of the authorized OAuth2 client details for the "okta" client registration. It represents the client's access and refresh tokens.
AuthenticationResponse authenticationResponse
= AuthenticationResponse.builder()
.userId(oidcUser.getEmail())
.accessToken(client.getAccessToken().getTokenValue())
.refreshToken(client.getRefreshToken().getTokenValue())
.expiresAt(client.getAccessToken().getExpiresAt().getEpochSecond())
.authorityList(oidcUser.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()))
.build();
- An
AuthenticationResponse
object is created using a builder pattern. - The
userId
field is set to the user's email obtained from theoidcUser
object. - The
accessToken
field is set to the value of the access token obtained from theclient
object. - The
refreshToken
field is set to the value of the refresh token obtained from theclient
object. - The
expiresAt
field is set to the epoch second value of the access token's expiration time obtained from theclient
object. - The
authorityList
field is set to a list of the user's authorities (roles) obtained from theoidcUser
object.
return new ResponseEntity<>(authenticationResponse, HttpStatus.OK);
}
- The
AuthenticationResponse
object is returned as the response body usingResponseEntity
, with an HTTP status of OK (200).
To use this controller:
- Make a GET request to
/authenticate/login
. - The controller will retrieve the authenticated user's information, including the email, access token, refresh token, expiration time, and authorities.
Contributions to this project are welcome. If you find any issues or want to suggest improvements, feel free to submit a pull request.
Jay Desai