A lightweight web-based application that provide high performance URL shortening service. It allows users to convert long, cumbersome URLs into short, shareable links with built-in redirection logic. The service is designed with scalability, security, and analytics in mind, making it suitable for both personal and enterprise use.
- Provide a fast and reliable way to shorten long URLs.
- Ensure unique and collision-free short links through proper encoding or ID generation techniques.
- Handle redirection from short URLs to original destinations seamlessly.
- Log essential click metrics for future analytics (e.g., hit count, timestamp, referrer).
- Offer a RESTful API interface to integrate with web, mobile, or browser-based clients.
- Provide a simple web UI for users to manage or track their shortened links.
users
→ Stores user credentials and roles.url_mapping
→ Contains mappings between original and shortened URLs.- Many-to-One with
users
(A user can generate many shortened URLs)
- Many-to-One with
click_event
→ Logs each time a shortened URL is accessed.- Many-to-One with
url_mapping
(Each URL can have multiple click events)
- Many-to-One with
- Tokens are sent using HTTP Authorized header.
Format →
Authorization: Bearer <token>
In its compact form, JSON Web Tokens consist of three parts separated by dots (.), which are:
- Header
- Payload
- Signature
Therefore, a JWT typically looks like the: xxxxx.yyyyy.zzzzz
-
JwtUtils
→ Contains utility methods for generating, parsing, and validating JWTs. Inclues:- Generating a token from username
- Validating a JWT
- Extracting the username from a token
-
JwtAutheticationFilter
→ Filters incoming requests to check for a valid JWT in the header, setting the authentication context if the token is valid.- Intercepts HTTP requests.
- Extracts JWT from request header, validates it.
- Configures the Spring Security context with user details if the token is valid.
-
JwtAutheticationResponse
→ DTO for JWT authentication respose. -
SecurityConfig
→ Configures Spring Security files & rules for the application. Sets up the secuirty filter chain, permitting or denying the access based on paths and roles.
Spring Security has its own inbuilt implemenation of User which would work, but in most of the cases custom implementation is needed.
- This is a custom implementation of Spring Security's
UserDetails
interface, which represents the authenticated user's information. - Provides a Spring Security-compatible representation of the user for authentication and authorization.
- This is a custom implementation of
UserDetailsService
, which is a service interface provided by Spring Security. - Spring calls this class to fetch user data from your database based on the username.
- Bridges the gap between the database (User entity) and Spring Security (User Details interface).
Refer to the Official GitHub for Maven dependencies for JWT. Add them to pom.xml
file.
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.6</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
Defines the security rules for handling HTTP requests.
- Spring Security recongnizes it as a filter that will only be executed once per request.
- By default, Spring Security doesn't automatically include your custom filter (
JwtAuthenticationFilter
) in the filter chain unless you explicitly add it.
- It's a pre-built class in Spring Security.
- Sets up how authentication is handled by Spring Security.
DaoAuthenticationProvider
is an implementation ofAuthenticationProvider
that validates user credentials during login. It uses:- A
UserDetailsService
to load user details (typically from a database) - A
PasswordEncoder
to check credentials (e.g., hashed password verification)
- A
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
- Creates a singleton bean of
PasswordEncoder
usingBCrypt
. BCryptPasswordEncoder
hashes user passwords securely.- Used during:
- Registration: To hash and store password.
- Login: To compare raw password with the hashed one stored in DB.
Passwords must never be stored or compared as plain text.
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
- This integrates cleanly with your existing
DaoAuthenticationProvider
. - Spring uses the
AuthenticationManager
built from our custom security config (which includes our customDaoAuthenticationProvider
).
public JwtAuthenticationResponse authenticate(LoginRequest loginRequest)
This method starts by accepting a LoginRequest
object containing the user's username and password.
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(), loginRequest.getPassword()
)
);
-
Creates a
UsernamePasswordAuthenticationToken
using the credentials provided by the user. -
Passes it to the authenticationManager to authenticate the user:
- Behind the scenes, Spring uses your configured
DaoAuthenticationProvider
. - It will call
UserDetailsServiceImpl.loadUserByUsername()
. - Compares the provided password (raw) with the hashed one from DB using
PasswordEncoder
.
- Behind the scenes, Spring uses your configured
-
If authentication is successful, it returns an
Authentication
object.
If authentication fails, Spring throws an exception (e.g.,
BadCredentialsException
).
SecurityContextHolder.getContext().setAuthentication(authentication);
-
After successful authentication, you store the
Authentication
object in Spring Security’s context. -
This sets the current user as "authenticated" for the rest of the request lifecycle.
-
Future authorization checks (like
@PreAuthorize
) will use this context.
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
-
Retrieves the authenticated user’s details from the
Authentication
object. -
getPrincipal()
returns the object returned by yourUserDetailsService
— in this case, your customUserDetailsImpl
.
String jwtToken = jwtUtils.generateJwtToken(userDetails);
return new JwtAuthenticationResponse(jwtToken);
- Calls a utility method (
JwtUtils
) to generate a JWT token. - Returns the token wrapped in a response DTO.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Annotate DTO fields with the following annotations:
@NotBlank
→ Ensures field is not null, empty, or whitespace (applicable ONLY for String)@Email
→ Validates structure of an email address@Size(min=8)
→ Ensures the field is at least 8 characters long@NotEmpty
→ Checks for not null and not empty (size() > 0
orlength > 0
) (applicable for Strings, Collections, Maps, Arrays). It doesn't check for whitespace
- Spring only triggers validation on DTO fields when the request body is annotated with
@Valid
. - Use
@Valid
in controller method parameters.
Without
@Valid
, validation annotations are ignored, and invalid data may reach the business logic.
- When validation fails, Spring throws a
MethodArgumentNotValidException
. To return clean, structured error responses, you must handle this exception globally. - Implement
@RestControllerAdvice
to handle exceptions globally.
Benefits:
- Returns field-specific error messages in JSON format
- Keeps controller code clean and focused
- Provides centralized error handling