What is Spring Security
Spring Security With JWT is a robust and flexible framework that provides authentication, authorization, and other security features for Java applications. It is part of the larger Spring ecosystem and is designed to integrate seamlessly with Spring applications.
Here are some key features of Spring Security with JWT:
- Authentication: Spring Security with JWT supports a variety of authentication methods, including form-based login, HTTP Basic, OAuth2, and more. It can authenticate users against various backends, such as databases, LDAP directories, and in-memory user stores.
- Authorization: It provides fine-grained access control through configuration, annotations, and expressions. You can define security rules based on roles, user attributes, and other criteria, ensuring that only authorized users can access certain resources.
- Cross-Site Request Forgery (CSRF) Protection: It includes built-in protection against CSRF attacks, ensuring that requests are only executed when they originate from a trusted source.
- Password Management: It provides utilities for securely hashing and storing passwords, as well as password policy enforcement, ensuring user credentials are protected.
- Security for APIs: It supports securing RESTful APIs through OAuth2 and JWT (JSON Web Token), making it ideal for modern web applications and microservices architectures.
JSON Web Token (JWT)
JWT is an open standard (RFC 7519) for securely transmitting information between parties as a JSON object. The information can be verified and trusted because it is digitally signed. JWTs are commonly used for authentication and authorization purposes in web applications and APIs.
Key Features of JWT
- Self-contained: JWTs contain all the necessary information about a user or claim within the token itself, reducing the need to query a database multiple times. This self-containment also makes JWTs stateless, as they don’t rely on server-side sessions.
- Secure: JWTs are signed using a cryptographic algorithm to ensure that the claims cannot be altered after the token is issued. JWTs can be signed using either a secret (with HMAC algorithm) or a public/private key pair (using RSA or ECDSA algorithms).
Structure of a JWT
A JWT consists of three parts, separated by dots (.), which are:
- Header: The header typically consists of two parts: the type of token (which is JWT) and the signing algorithm being used, such as HMAC SHA256 or RSA.
{
"alg": "HS256",
"typ": "JWT"
}
2. Payload: The payload contains the claims. Claims are statements about an entity (typically, the user) and additional data.
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022
}
3. Signature: To create the signature part, the encoded header, the encoded payload, and a secret key (or a public/private key pair) are used with the algorithm specified in the header to create a signature. This signature is used to verify that the sender of the JWT is who it says it is and to ensure that the message wasn’t changed along the way.
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
How JWT Works
- User Authentication: When a user logs in, the server validates the credentials and issues a JWT signed with a secret key or a private key.
- Client Stores JWT: The client stores the JWT (typically in local storage or a cookie).
- Client Sends JWT with Requests: For subsequent requests to protected routes or resources, the client sends the JWT along with the HTTP request in the Authorization header.
- Server Validates JWT: The server verifies the JWT’s signature using the secret or public key. If the token is valid, the server processes the request; otherwise, it responds with an error.
- Token Expiration: JWTs usually have an expiration date (
exp
claim) to limit their validity period. After expiration, the user must log in again to obtain a new token.
How to implement Spring Security With JWT in Project
Project Story: Covid19 REST API
The Covid19 REST API with Spring Security With JWT is a project designed to provide a set of dummy APIs for managing and accessing COVID-19 data. The application supports two types of users:
- Admin: Admin users have full access to the API, allowing them to manage data, such as adding, updating, or deleting COVID-19 statistics.
- User: Regular users have limited access and can only retrieve COVID-19 data for informational purposes.
Required code components list to implement Spring Security With JWT in Project
Covid19-REST-API/
│
├── pom.xml // Maven configuration file
│
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── api
│ │ │ ├── config
│ │ │ │ ├── JwtAuthenticationFilter.java // Filter for JWT authentication
│ │ │ │ └── SecurityConfig.java // Configuration for Spring Security
│ │ │ │
│ │ │ ├── constants
│ │ │ │ └── JwtConstant.java // Constants used for JWT configuration
│ │ │ │
│ │ │ ├── controller
│ │ │ │ ├── AdminController.java // Controller for admin-specific endpoints
│ │ │ │ ├── AuthController.java // Controller for authentication endpoints
│ │ │ │ └── UserController.java // Controller for user-specific endpoints
│ │ │ │
│ │ │ ├── dao
│ │ │ │ └── UserDao.java // Data Access Object interface for user operations
│ │ │ │
│ │ │ ├── daoimpl
│ │ │ │ └── UserDaoImpl.java // Implementation of the UserDao interface
│ │ │ │
│ │ │ ├── entity
│ │ │ │ ├── Role.java // Entity representing a user's role
│ │ │ │ └── User.java // Entity representing a user
│ │ │ │
│ │ │ ├── model
│ │ │ │ └── JwtResponse.java // Model representing the JWT response
│ │ │ │
│ │ │ ├── security
│ │ │ │ ├── CustomUserDetail.java // User details implementation for Spring Security
│ │ │ │ └── CustomUserDetailService.java // Service for loading user-specific data
│ │ │ │
│ │ │ ├── service
│ │ │ │ └── UserService.java // Interface for user-related business logic
│ │ │ │
│ │ │ ├── serviceimpl
│ │ │ │ └── UserServiceImpl.java // Implementation of the UserService interface
│ │ │ │
│ │ │ └── utility
│ │ │ └── JwtUtil.java // Utility class for JWT-related operations
│ │ │
│ │ └── resources
│ │ └── application.yml // Configuration file for the application
│
└── [Other project files and directories]
Code Component Explanation
1. pom.xml:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
- Parent Version (Spring Boot Starter Parent 2.5.2): Sets up default configurations for Spring Boot applications, managing versions and compatibility for all dependencies.
- Spring Boot Starter Security: Provides essential security features like authentication and authorization.
- JJWT (JSON Web Token): Library for creating and validating JWTs, enabling token-based authentication.
- Spring Boot Starter Data JPA: Simplifies database operations and ORM with Java Persistence API (JPA).
- MySQL Connector/J: JDBC driver for MySQL, allowing Java applications to connect to MySQL databases.
- Spring Boot Starter Web: Essential for building RESTful web services and web applications.
2. application.yml or properties
server:
port: 8091
hostname: localhost
spring:
datasource:
url: jdbc:mysql://localhost:3306/covid19?createDatabaseIfNotExist=true
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update # Automatically update the database schema
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL8Dialect # MySQL 8 dialect
show_sql: true # Show SQL statements in logs
3. JwtAuthenticationFilter.java
package com.api.config;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtUtil jwtTokenUtil;
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws IOException, ServletException {
String header = req.getHeader(JwtConstatnt.HEADER_STRING);
String username = null;
String authToken = null;
if (header != null && header.startsWith(JwtConstatnt.TOKEN_PREFIX)) {
// header= Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJyYW0iLCJzY29wZXMiOiJST0xFX0FETUlOIiwiaWF0IjoxNjgxMTEyOTUyLCJleHAiOjE2ODExMzA5NTJ9.SHNB6bgI_HgWFBZyhNnG0nQO7SWvJLfaxmSDAeRkZiw
//
authToken = header.replace(JwtConstatnt.TOKEN_PREFIX, "");
try {
username = jwtTokenUtil.getUsernameFromToken(authToken);
// System.out.println("User from token "+username);
} catch (IllegalArgumentException e) {
System.err.println("an error occured during getting username from token");
} catch (ExpiredJwtException e) {
System.err.println("the token is expired and not valid anymore");
} catch (SignatureException e) {
System.err.println("Authentication Failed. Username or Password not valid.");
}
catch (Exception e) {
e.printStackTrace();
}
} else {
System.err.println("couldn't find bearer string, will ignore the header ! Logged In With Credientials");
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = jwtTokenUtil.getAuthentication(authToken,
SecurityContextHolder.getContext().getAuthentication(), userDetails);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(req));
System.err.println("authenticated user " + username + ", setting security context");
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(req, res);
}
}
Here’s a summary of the JwtAuthenticationFilter
class with key points:
- Purpose: Extends
OncePerRequestFilter
to intercept HTTP requests and validate JWT tokens for authentication. - Dependencies:
UserDetailsService
: Loads user-specific data for authentication.JwtUtil
: Utility class for JWT operations, including token parsing and validation.
- Token Extraction:
- Extracts the JWT token from the
Authorization
header if it starts with the prefixBearer
. - Handles potential exceptions such as
IllegalArgumentException
,ExpiredJwtException
, andSignatureException
during token extraction and validation.
- Extracts the JWT token from the
- Username Extraction: Retrieves the username from the token using
JwtUtil
. - Authentication Setup:
- If the username is valid and no existing authentication context is found, it loads user details and validates the token.
- Creates and sets up a
UsernamePasswordAuthenticationToken
for the authenticated user and updates theSecurityContextHolder
.
- Logging: Prints error messages and debugging information for various authentication steps and token issues.
4. SecurityConfig.java
package com.api.config;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public CustomUserDetailService customUserDetilService;
@Autowired
private JwtAuthenticationFilter jwtFilter;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetilService).passwordEncoder(bCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/auth/**").permitAll()
.and()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN") // ADMIN USER
.antMatchers("/user/**").hasRole("USER") // RECEPTIONIST USER
.anyRequest().authenticated().and().httpBasic();
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/v2/api-docs", "/configuration/ui", "/swagger-resources/**",
"/configuration/security", "/webjars/**");
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder(10);
}
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
}
Here’s a brief summary of the SecurityConfig
class:
- Purpose: Configures Spring Security with JWT for the application, managing authentication and authorization.
- Custom User Details Service: Utilizes
CustomUserDetailService
to load user-specific data for authentication. - Password Encoding: Uses
BCryptPasswordEncoder
for hashing passwords, providing a security layer for password storage. - JWT Authentication Filter: Adds a custom
JwtAuthenticationFilter
before the defaultUsernamePasswordAuthenticationFilter
to handle JWT-based authentication. - Security Rules:
- Disables CSRF protection (
csrf().disable()
) since the API is stateless. - Allows unrestricted access to endpoints under
/auth/**
. - Restricts access to
/admin/**
for users with theADMIN
role and/user/**
for users with theUSER
role. - All other requests require authentication (
anyRequest().authenticated()
).
- Disables CSRF protection (
- Web Security Configuration: Excludes specific paths from Spring Security filters, such as Swagger UI and API documentation endpoints, to allow open access for development purposes.
- Beans Provided:
BCryptPasswordEncoder
: For password encoding.AuthenticationManager
: Manages authentication processes and is necessary for custom authentication implementations.
5. JwtConstatnt.java
package com.api.constants;
public interface JwtConstatnt
{
public static final String HEADER_STRING = "Authorization";
public static final String TOKEN_PREFIX = "Bearer ";
public static final String SIGNING_KEY = "MyHealthifyApplicationSigningKEY702019272609876654321";
public static final int ACCESS_TOKEN_VALIDITY_SECONDS = 5*60*60;
public static final String AUTHORITIES_KEY = "scopes";
}
Here’s a brief summary of the JwtConstatnt
interface:
- Purpose: Provides constant values related to JWT configuration and handling, used throughout the application for security purposes.
- Constants Defined:
HEADER_STRING
: Specifies the HTTP header key"Authorization"
where the JWT token is expected.TOKEN_PREFIX
: Denotes the prefix"Bearer "
used in theAuthorization
header to indicate the type of token.SIGNING_KEY
: Represents the secret key"MyHealthifyApplicationSigningKEY702019272609876654321"
used for signing JWT tokens, ensuring their integrity and authenticity.ACCESS_TOKEN_VALIDITY_SECONDS
: Sets the validity duration of the JWT token to 5 hours (5 * 60 * 60
seconds).AUTHORITIES_KEY
: Defines the key"scopes"
used to store user roles or authorities in the JWT token payload.
6. Define All Endpoints in RestControllers
package com.api.controller;
@RestController
@RequestMapping("/admin")
public class AdminController {
@GetMapping("/get-all-center")
public ResponseEntity<String> getAllCenter(){
return new ResponseEntity<String>("Pune Center, Mumbai Center",HttpStatus.OK) ;
}
}
////////////////////////////////////////////////////////////////////////////////
package com.api.controller;
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/search-vancine/{name}")
public ResponseEntity<String> getCenter(){
return new ResponseEntity<String>("Pune Center",HttpStatus.OK) ;
}
}
package com.api.controller;
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
UserService userService;
@Autowired
CustomUserDetailService customUserDetailService;
@Autowired
JwtUtil jwtUtil;
@Autowired
private AuthenticationManager authenticationManager;
// completed
@PostMapping("/login-user")
public ResponseEntity<?> loginAdmin(@RequestBody User user,HttpServletResponse response) throws AuthenticationException {
final Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
user.getUsername(),
user.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication); //check
final String token = jwtUtil.generateToken(authentication);
response.addHeader("token", token);
return ResponseEntity.ok(new JwtResponse(token));
}
}
Here’s a brief summary of the AuthController
class:
- Purpose: Provides endpoints for authentication and login functionality in the API.
- Dependencies:
UserService
: Handles user-related operations (not directly used in the provided code).CustomUserDetailService
: Custom service for loading user details (not directly used in the provided code).JwtUtil
: Utility class for generating JWT tokens after successful authentication.AuthenticationManager
: Manages authentication processes by validating user credentials.
- Endpoint:
/login-user
(POST
): Authenticates the user using provided credentials (username
andpassword
).
- Authentication Process:
- Utilizes
AuthenticationManager
to authenticate the user withUsernamePasswordAuthenticationToken
. - If authentication is successful, sets the authentication context in
SecurityContextHolder
.
- Utilizes
- Token Generation:
- Generates a JWT token using
JwtUtil
for the authenticated user. - Adds the generated JWT token to the HTTP response header as
"token"
.
- Generates a JWT token using
- Response:
- Returns an
OK
(200 OK
) response with aJwtResponse
object containing the JWT token.
- Returns an
7. UserServiceImpl.java
package com.api.serviceimpl;
@Service
public class UserServiceImpl implements UserService {
@Autowired
public BCryptPasswordEncoder passwordEncoder;
@Autowired
private UserDao dao;
@Override
public User loginUser(User user) {
return dao.loginUser(user);
}
@Override
public CustomUserDetail loadUserByUserId(String userId) {
return dao.loadUserByUserId(userId);
}
}
Here’s a brief summary of the UserServiceImpl
class:
- Purpose: Implements the
UserService
interface, providing business logic for user-related operations. - Dependencies:
BCryptPasswordEncoder
: Used for encoding and validating user passwords securely (though not used directly in the provided methods).UserDao
: Data Access Object interface for interacting with the data source to perform user-related operations.
- Methods:
loginUser(User user)
: Delegates toUserDao
to handle user login by returning aUser
object if the login is successful.loadUserByUserId(String userId)
: Retrieves aCustomUserDetail
object based on the provideduserId
, leveraging theUserDao
.
- Implementation:
- Acts as a service layer to abstract the data access logic and provide a cleaner interface for higher-level components like controllers.
8. UserDaoImpl.java
package com.api.daoimpl;
@Repository
public class UserDaoImpl implements UserDao {
private static Logger LOG = LogManager.getLogger(UserDaoImpl.class);
@Autowired
private SessionFactory sf;
@Autowired
public PasswordEncoder passwordEncoder;
@Override
public User loginUser(User user) {
Session session = sf.getCurrentSession();
User usr = null;
try {
usr = session.get(User.class, user.getUsername());
boolean matches = passwordEncoder.matches(user.getPassword(), usr.getPassword());
if (matches) {
return usr;
} else {
usr = null;
}
} catch (Exception e) {
e.printStackTrace();
}
return usr;
}
@Override
public CustomUserDetail loadUserByUserId(String userId) {
Session session = sf.getCurrentSession();
CustomUserDetail user = new CustomUserDetail();
User usr = null;
try {
usr = session.get(User.class, userId);
if (usr != null) {
user.setUserid(usr.getUsername());
user.setPassword(usr.getPassword());
user.setRoles(usr.getRoles());
}
System.out.println("load user ..." + user);
} catch (Exception e) {
e.printStackTrace();
}
return user;
}
}
Here’s a brief summary of the UserDaoImpl
class:
- Purpose: Implements the
UserDao
interface, providing data access methods for user-related operations, including user login and loading user details by user ID. - Dependencies:
SessionFactory
: Used to obtain Hibernate sessions for interacting with the database.PasswordEncoder
: Used to verify that the provided password matches the encoded password stored in the
- Methods:
loginUser(User user)
:database.- Fetches a
User
object from the database based on the provided username. - Verifies the provided password against the stored password using
PasswordEncoder
. - Returns the
User
object if the password matches; otherwise, returnsnull
.
- Fetches a
loadUserByUserId(String userId)
:- Retrieves a
User
entity from the database based on the user ID. - Converts the
User
entity to aCustomUserDetail
object, setting the user ID, password, and roles. - Returns the
CustomUserDetail
object, or a default one if the user is not found.
- Retrieves a
- Exception Handling: Both methods include try-catch blocks to handle exceptions, ensuring that errors are logged and do not crash the application.
- Logging: Uses
LogManager
for logging activities, although it seems to only log exceptions in this context.
9. User.java
package com.api.entity;
/**
* @author RAM
*/
@Entity
public class User {
@Id
@Column(name = "UserName",unique = true,nullable = false)
@Pattern(regexp = "^[a-zA-Z0-9_]{3,20}$", message = "Invalid username format")
private String username;
@NotBlank(message = "First name is manadtory")
@Column(name = "FirstName",nullable = false)
@Pattern(regexp = "^[a-zA-Z]+(\\s[a-zA-Z]+)*$", message = "Invalid first name")
private String firstname;
@Column(name = "LastName",nullable = false)
@Pattern(regexp = "^[a-zA-Z]+(\\s[a-zA-Z]+)*$", message = "Invalid last name")
private String lastname;
@Column(name = "EmailId", unique = true,nullable = false)
@NotEmpty(message = "Email should not be empty")
@Email(message = "Invalid email format")
private String emailid;
@Column(name = "Password",nullable = false)
//@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$",message = "At least 8 characters long, one uppercase,lowercase,digit,special character")
private String password;
@Column(name = "MobileNo", unique = true,nullable = false)
@Pattern(regexp = "^[0-9]{10}$",message = "Invalid mobile number")
private String mobileno;
@Column(name = "Street")
private String street;
@Column(name = "City")
private String city;
@Column(name = "Pincode")
private String pincode;
@Column(name = "Question",nullable = false)
@NotBlank(message = "Question is manadtory")
private String Question;
@Column(name = "Answer",nullable = false)
@NotBlank(message = "Answer is manadtory")
private String answer;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd")
@Column(name = "CreatedDate",nullable = false)
private Date createdDate;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "user_role", joinColumns = { @JoinColumn(name = "USER_ID") }, inverseJoinColumns = {
@JoinColumn(name = "ROLE_ID") })
private Set<Role> roles;
public User() {
}
// setter & getter
}
Here’s a summary of the User
entity class:
- Purpose: Represents a user entity for a system, mapping user data to the database using JPA annotations.
- Annotations:
@Entity
: Specifies that this class is a JPA entity.@Id
: Marks theusername
field as the primary key.@Column
: Defines properties for database columns, including constraints such asunique
andnullable
.@Pattern
,@NotBlank
,@NotEmpty
,@Email
: Used for input validation with specific regex patterns and constraints.@DateTimeFormat
and@JsonFormat
: Formats date fields for input/output.@ManyToMany
and@JoinTable
: Defines a many-to-many relationship with theRole
entity, using a join table nameduser_role
.
10. Role.java
package com.api.entity;
@Entity
public class Role {
@Id
private int id;
private String name;
public Role() {
// TODO Auto-generated constructor stub
}
// setter & getter
}
11.CustomUserDetail.java
package com.api.security;
public class CustomUserDetail implements UserDetails {
private String userid;
private String password;
private Set<Role> roles;
public CustomUserDetail() {
// TODO Auto-generated constructor stub
}
public CustomUserDetail(String userid, String password, Set<Role> roles) {
super();
this.userid = userid;
this.password = password;
this.roles = roles;
}
public String getUserid() {
return userid;
}
public void setUserid(String userid) {
this.userid = userid;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> autorities = this.roles.stream()
.map((role) -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList());
return autorities;
}
@Override
public String getUsername() {
return this.userid;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public String getPassword() {
// TODO Auto-generated method stub
return this.password;
}
@Override
public String toString() {
return "CustomUserDetail [userid=" + userid + ", password=" + password + ", roles=" + roles + "]";
}
}
Here’s a summary of the CustomUserDetail
class:
- Purpose: Implements
UserDetails
to integrate custom user details with Spring Security for authentication and authorization. - Fields:
userid
: Stores the user’s ID or username.password
: Stores the user’s password.roles
: A set ofRole
objects representing user roles.
- Methods:
getAuthorities()
: Returns a collection ofGrantedAuthority
objects, derived from user roles, used by Spring Security to manage user roles and permissions.getUsername()
: Returns theuserid
, implementing theUserDetails
interface method.getPassword()
: Returns the user’s password.isAccountNonExpired()
: Returnstrue
, indicating the account is not expired.isAccountNonLocked()
: Returnstrue
, indicating the account is not locked.isCredentialsNonExpired()
: Returnstrue
, indicating the credentials are not expired.isEnabled()
: Returnstrue
, indicating the account is enabled.toString()
: Provides a string representation of theCustomUserDetail
object, includinguserid
,password
, androles
.
12. CustomUserDetailService.java
package com.api.security;
@Service
public class CustomUserDetailService implements UserDetailsService {
@Autowired
private UserService service;
@Override
public UserDetails loadUserByUsername(String username) throws ResourceNotFoundException {
// loading user from db
CustomUserDetail user = service.loadUserByUserId(username);
if (user != null) {
return user;
} else {
throw new ResourceNotFoundException("User not found with username: " + username);
}
}
}
Here are the key points for the CustomUserDetailService
class:
- Purpose: Implements
UserDetailsService
to provide a custom way of loading user details for authentication in Spring Security. UserService service
: Injected using@Autowired
, this dependency is used to access user-related operations, such as loading user details from the database.- Functionality: Calls
service.loadUserByUserId(username)
to fetch user details from the database.
13. JwtUtil.java
package com.api.utility;
@Component
public class JwtUtil implements Serializable {
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser()
.setSigningKey(JwtConstatnt.SIGNING_KEY)
.parseClaimsJws(token)
.getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
public String generateToken(Authentication authentication) {
final String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
return Jwts.builder()
.setSubject(authentication.getName())
.claim(JwtConstatnt.AUTHORITIES_KEY, authorities)
.signWith(SignatureAlgorithm.HS256, JwtConstatnt.SIGNING_KEY)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + JwtConstatnt.ACCESS_TOKEN_VALIDITY_SECONDS*1000))
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (
username.equals(userDetails.getUsername())
&& !isTokenExpired(token));
}
public UsernamePasswordAuthenticationToken getAuthentication(final String token, final Authentication existingAuth, final UserDetails userDetails) {
final JwtParser jwtParser = Jwts.parser().setSigningKey(JwtConstatnt.SIGNING_KEY);
final Jws<Claims> claimsJws = jwtParser.parseClaimsJws(token);
final Claims claims = claimsJws.getBody();
final Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get(JwtConstatnt.AUTHORITIES_KEY).toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
return new UsernamePasswordAuthenticationToken(userDetails, "", authorities);
}
}
Here are the key points for the JwtUtil
class:
- Purpose: Utility class for handling JWT (JSON Web Token) operations such as generating, validating, and parsing tokens.
- Key Methods:
getUsernameFromToken(String token)
:- Purpose: Extracts the username (subject) from the JWT token.
- Returns: The username embedded in the token.
getExpirationDateFromToken(String token)
:- Purpose: Retrieves the expiration date from the JWT token.
- Returns: A
Date
object representing the token’s expiration time.
getClaimFromToken(String token, Function<Claims, T> claimsResolver)
:- Purpose: General method to extract any claim from the token using a specified resolver function.
- Returns: The claim value extracted by the resolver function.
getAllClaimsFromToken(String token)
:- Purpose: Parses the JWT token to retrieve all claims.
- Returns: A
Claims
object containing all the token’s claims.
isTokenExpired(String token)
:- Purpose: Checks if the JWT token has expired.
- Returns:
true
if the token is expired;false
otherwise.
generateToken(Authentication authentication)
:- Purpose: Generates a new JWT token based on user authentication details.
- Returns: A string representing the newly created JWT token.
validateToken(String token, UserDetails userDetails)
:- Purpose: Validates the JWT token by checking the username and ensuring it hasn’t expired.
- Returns:
true
if the token is valid and matches the provided user details;false
otherwise.
getAuthentication(String token, Authentication existingAuth, UserDetails userDetails)
:- Purpose: Creates an
Authentication
object from the JWT token, using the token’s claims to determine the user’s authorities. - Returns: A
UsernamePasswordAuthenticationToken
object containing user details and authorities extracted from the token.
- Purpose: Creates an
- Token Operations:
- Uses
Jwts.builder()
andJwts.parser()
from theio.jsonwebtoken
library for creating and parsing JWT tokens. SignatureAlgorithm.HS256
: Specifies the hashing algorithm used to sign the JWT token.
- Uses
Table of Contents
Other Resources:
- install-SonarQube-server-on-windows
- how-to-do-validation-in-spring-boot-tested
- Click here to download
Very nice post. I just stumbled upon your blog and wanted to say that I’ve really enjoyed surfing around your blog posts. In any case I will be subscribing to your rss feed and I hope you write again soon!
Thank you for providing me with these article examples. May I ask you a question?
You’ve the most impressive websites.