Dear folks,
Today I will cover how to create simple REST APIs with JWT authorization using Spring Boot.
You might want to check out my previous blog post for How to create API using Spring and Tomcat
I've already covered how to setup MySQL, create new project in IntelliJ in the above blog post so in this blog I will skip that.
0.Create MySQL database and table using SQL query:
- Create new database
CREATE DATABASE restapi;
USE restapi;
- Create new table for blog
CREATE TABLE blog (
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(500) NOT NULL,
content VARCHAR(5000) NOT NULL
);
- Create new table for userinfo:
CREATE TABLE user_info(
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
password VARCHAR(500) NOT NULL,
fullname VARCHAR(50) NOT NULL
);
1.Dependencies we will need for this tutorial:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
</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-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-core -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-web -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
We would need spring-boot-starter for create REST API.
Mysql-connector-java for connect to MySQL database.
Spring-security for setting up Authorization
jsonwebtoken for using JWT with Authorization
2.Project structure
- resources: We will define the properties for our project in application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/restapi
spring.datasource.username=xxxx
spring.datasource.password=xxxx
spring.datasource.platform=mysql
jwt.secret={bcrypt}$donald
spring.datasource used to provide info about your database. You would need to provide your username and password for it to work.
jwt.secret is the secret key of jwt. (I will talk about this in details later)
- project packages:
+) config:
Used to store config files for our project.
+) controller:
Used to define controller class for Authentication, CRUD for Blog content, Create new user
+) exceptions:
Define base error handles and exception for validate data
+) model:
Create model for Blog Entity, UserInfo Entity, JwtRequest and JwtResponse
+) repository:
Create Blog and UserInfo repository to interact with MySQL database using JPA
+) service:
Create JwtUserDetailsService to check whether the username is existed in database or not
- MainApplicationClass to run SpringBootApplication:
@SpringBootApplication
public class MainApplicationClass {
public static void main(String[] args) {
SpringApplication.run(MainApplicationClass.class, args);
}
}
3.What we will create:
- API to create new user in the application
- API to authen whether the user credentials is valid, if it is return token so that he or she can do other stuff
- API to create new blog post, view blog post, or update them.
So the API for create and authenticate credentials, will not have that authorization part --> to make sure anyone can access and perform these APIs.
The API for interact with blogs will require authentication with jwt token.
To be able to do this, we would need to create configure method in our WebSecurityConfig class in config package:
httpSecurity.csrf().disable()
.authorizeRequests().antMatchers("/authenticate","/user").permitAll().
anyRequest().authenticated().and().
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
4.Configuration for jwt token
JwtAuthenticationEntryPoint to throw unauthorized message if the user credential is not correct
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = -7858869558953243875L;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
JwtRequestFilter to filter value of Authorization header:
import donald.apiwithspringboot.service.JwtUserDetailsService;
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private JwtUserDetailsService jwtUserDetailsService;
private final JwtToken jwtTokenUtil;
public JwtRequestFilter(JwtToken jwtTokenUtil) {
this.jwtTokenUtil = jwtTokenUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
}
} else {
logger.warn("JWT Token does not begin with Bearer String");
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
JwtToken class to generate jwt token:
@Component
public class JwtToken implements Serializable {
private static final long serialVersionUID = -2550185165626007488L;
public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60;
@Value("${jwt.secret}")
private String secret;
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(secret).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, userDetails.getUsername());
}
private String doGenerateToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000))
.signWith(SignatureAlgorithm.HS512, secret).compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
WebSecurityConfig to define the beans we would need and config path with authentication:
import donald.apiwithspringboot.service.JwtUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
private JwtUserDetailsService jwtUserDetailsService;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public JwtAuthenticationEntryPoint jwtAuthenticationEntryPointBean() throws Exception{
return new JwtAuthenticationEntryPoint();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
.authorizeRequests().antMatchers("/authenticate","/user").permitAll().
anyRequest().authenticated().and().
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
5.Controller:
AuthController to define the API to authenticate user credentials and response jwt token if correct:
import donald.apiwithspringboot.model.JwtRequest;
import donald.apiwithspringboot.model.JwtResponse;
import donald.apiwithspringboot.service.JwtUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;
import donald.apiwithspringboot.config.JwtToken;
import org.springframework.security.authentication.AuthenticationManager;
@RestController
@CrossOrigin
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtToken jwtToken;
@Autowired
private JwtUserDetailsService jwtUserDetailsService;
@RequestMapping(value = "/authenticate", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtRequest authenticationRequest) throws Exception {
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
final UserDetails userDetails = jwtUserDetailsService
.loadUserByUsername(authenticationRequest.getUsername());
final String token = jwtToken.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(token));
}
private void authenticate(String username, String password) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new Exception("INVALID_CREDENTIALS", e);
}
}
}
BlogController class to create API for create new blog, modify blog content, view blog or update blog
import donald.apiwithspringboot.model.Blog;
import donald.apiwithspringboot.repository.BlogRepository;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
public class BlogController {
final
private BlogRepository blogRepository;
public BlogController(BlogRepository blogRepository) {
this.blogRepository = blogRepository;
}
@GetMapping("/blog")
public List<Blog> index(){
return blogRepository.findAll();
}
@GetMapping("/blog/{id}")
public Blog show(@PathVariable String id){
int blogId = Integer.parseInt(id);
return blogRepository.findById(blogId).orElse(new Blog());
}
@PostMapping("/blog/search")
public List<Blog> search(@RequestBody Map<String, String> body){
String searchTerm = body.get("text");
return blogRepository.findByTitleContainingOrContentContaining(searchTerm, searchTerm);
}
@PostMapping("/blog")
public Blog create(@RequestBody Map<String, String> body){
String title = body.get("title");
String content = body.get("content");
return blogRepository.save(new Blog(title, content));
}
@PutMapping("/blog/{id}")
public Blog update(@PathVariable String id, @RequestBody Map<String, String> body){
int blogId = Integer.parseInt(id);
// getting blog
Blog blog = blogRepository.findById(blogId).orElse(new Blog());
blog.setTitle(body.get("title"));
blog.setContent(body.get("content"));
return blogRepository.save(blog);
}
@DeleteMapping("blog/{id}")
public boolean delete(@PathVariable String id){
int blogId = Integer.parseInt(id);
blogRepository.deleteById(blogId);
return true;
}
}
UserInfoController to create API create new user and insert it into database with password is encoded with BCryptPasswordEncoder:
import donald.apiwithspringboot.exceptions.ValidationException;
import donald.apiwithspringboot.model.UserInfo;
import donald.apiwithspringboot.repository.UserInfoRepository;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
@RestController
public class UserInfoController {
final
private UserInfoRepository userInfoRepository;
// private HashData hashData = new HashData();
public UserInfoController(UserInfoRepository userInfoRepository) {
this.userInfoRepository = userInfoRepository;
}
@PostMapping("/user")
public Boolean create(@RequestBody Map<String, String> body) throws NoSuchAlgorithmException {
String username = body.get("username");
if (userInfoRepository.existsByUsername(username)){
throw new ValidationException("Username already existed");
}
String password = body.get("password");
String encodedPassword = new BCryptPasswordEncoder().encode(password);
// String hashedPassword = hashData.get_SHA_512_SecurePassword(password);
String fullname = body.get("fullname");
userInfoRepository.save(new UserInfo(username, encodedPassword, fullname));
return true;
}
}
6.Exceptions:
BaseErrorHandles class for handleException as BAD_REQUEST:
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
@Slf4j
public class BaseErrorHandles {
@ResponseBody
@ExceptionHandler(value = ValidationException.class)
public ResponseEntity<?> handleException(ValidationException exception) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(exception.getMsg());
}
}
ValidationException
public class ValidationException extends RuntimeException {
private static final long serialVersionUID = 1L;
private String msg;
public ValidationException(String msg) {
this.msg = msg;
}
public String getMsg() {
return msg;
}
}
7.Model:
Blog model: define blog entity
@Entity
public class Blog {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String title;
private String content;
public Blog() { }
public Blog(String title, String content) {
this.setTitle(title);
this.setContent(content);
}
public Blog(int id, String title, String content) {
this.setId(id);
this.setTitle(title);
this.setContent(content);
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "Blog{" +
"id=" + id +
", title='" + title + '\'' +
", content='" + content + '\'' +
'}';
}
}
UserInfo class to define UserInfo enty:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class UserInfo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String username;
private String password;
private String fullname;
public UserInfo() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public UserInfo(String username, String password, String fullname) {
this.username = username;
this.password = password;
this.fullname = fullname;
}
public String getFullname() {
return fullname;
}
public void setFullname(String fullname) {
this.fullname = fullname;
}
}
JwtRequest model for authenticate username and password in the request in AuthController
public class JwtRequest implements Serializable {
private static final long serialVersionUID = 5926468583005150707L;
private String username;
private String password;
public JwtRequest()
{
}
public JwtRequest(String username, String password) {
this.setUsername(username);
this.setPassword(password);
}
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
}
JwtResponse create model for token response
import java.io.Serializable;
public class JwtResponse implements Serializable {
private static final long serialVersionUID = -8091879091924046844L;
private final String jwttoken;
public JwtResponse(String jwttoken) {
this.jwttoken = jwttoken;
}
public String getToken() {
return this.jwttoken;
}
}
8.Repository:
BlogRepository to work with MySQL database via JPA for blog table:
@Repository
public interface BlogRepository extends JpaRepository<Blog,Integer> {
// custom query to search to blog post by title or content
List<Blog> findByTitleContainingOrContentContaining(String text, String textAgain);
}
UserInfoRepository to work with MySQL database via JPA for user_info table
@Repository
public interface UserInfoRepository extends JpaRepository<UserInfo,Integer> {
Boolean existsByUsername(String username);
UserInfo findByUsername(String username);
}
9.Service:
Define JwtUserDetailsService for loadUserByUsername method:
@Component
public class JwtUserDetailsService implements UserDetailsService {
@Autowired
private UserInfoRepository userInfoRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserInfo user = userInfoRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found with username: " + username);
}
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
new ArrayList<>());
}
}
10.Run spring-boot application:
Simply run : mvn spring-boot:run
11.Interact with API using Postman:
Create new user
Create new user success:
Authenticate user credentials:
Authenticate user credentials success:
Create new blog request body:
Create new blog header:
Create new blog response:
That's it.
For the future tutorial, I will cover how to write unit tests and contract test for this.
You can find the sourcecode in github as usual in here
Thank you guys and girls!
Peace!!!
Notes: If you feel this blog help you and want to show the appreciation, feel free to drop by :
This will help me to contributing more valued contents.
Top comments (21)
This is the first sane alternative I've seen for JWT with Spring Boot. The OAuth approach (that seems to be the "default" approach nowadays is crazy complex for a JWT authentication).
Thanks for your time and dedication.
Thank you Alexandre.
Great to hear that from you!!
Hello Mr. coungld2 I really liked this article and gave me new ideas but, I have a question for you. I wanted to change the database from MySQL to H2 database. I configured the server as I always do in my Spring Boot applications. I could've connected to the H2 Database console with /h2-console.
When I try to login to the /h2-console, the server gives me "JWT Token does not begin with Bearer String". I added /h2-console endpoint to the WebSecurityConfig.java as shown below.
httpSecurity.csrf().disable().authorizeRequests().antMatchers("/authenticate","/user","/h2-console/**").permitAll()
What are the steps should I take to login the database.
Thank you.
Sincerely.
Dear Omer,
I'm glad that you liked this blog post.
Sorry but currently I don't have any idea about H2 database.
But I will take a look at that when I got the time.
Happy coding!!!
I really like the article and i have implemented the JWT token authentication in my spring boot application.it is working fine. When i tried to make it as jar and use it as dependency in another spring boot application, token authentication is perfectly happening but it is not redirecting to the Rest API.Please help me how to resolve the issue
Thanks for the comment.
Sorry for late response.
I dont understand the part "not redirecting to the rest api".
Could you give more details?
thanks for responding. after token authentication in the JWSrequest filter
Can i share my code.or can you suggest the approach when we use JWt token authentication as seperate jar in other spring boot application.Here my requirement is that JWT token authentication code as seperate jar in multiple applications in my project.
Thanks for you response. This will helpful for automate restful service using restful service. Could you suggest good books or internet site to learn develop restful service using spring boot . I am core java developer .
Thanks
Sridharbabu
Thank you SRIDHAR. (Not sure that's your firstname or last name so pardon me :) ).
I would suggest you take a look at Spring Boot in action book.
For online sites, please take a look at Baeldung or Dzone as they got a lot of java tutorials in general.
Cheers!!
thanks for responding. after token authentication in the JWSrequest filter
Can i share my code.or can you suggest the approach when we use JWt token authentication as seperate jar in other spring boot application.Here my requirement is that JWT token authentication code as seperate jar in multiple applications in my project.
Dear sree,
I've never met this situation before.
Really sorry I cannot help you on this :(.
Stay strong.
Maybe you can search online for the solution. :)
Hi cuongld2! Thanks for the tutorial, as a newbie in spring boot, I found it very helpful.
I have one question though. When a user signs in, a bearer token is generated; but where is it stored?
My goal is to create a log out functionality, where the user's token is destroyed upon logging out.
Hi,
Im really glad that my tutorial help you :d.
The jwt token is stored in the client side.
The server will verify the token is correct or not.
Im about to add api for logout so but does not have the time yet.
If you can please make a PR to add api for log out. :)
Thank you so much was really helpful
I'm glad this could help you.
:d
Can you help me how to resolve below issue.
You're my hero! Thank you sir, you saved my life ;) It works like a charm
So glad that could help you.
Stay tuned for more!!!
How to get the custom header value in spring security configure method ?
tuyệt vời quá anh, trên Baeldung họ viết hơi khó hiểu hơn bài này của anh, nếu được a có thể cho link github để xem source đc ko ạ. Vì có nhiều file ko biết anh để đâu nên hơi khó implement
Source code ở đây nhé Linh : github.com/cuongld2/springboot-sim...
Thanks em đã đọc bài :)