In this tutorial we’ll see how to create a Spring Boot application that uses Spring Security and JWT token based authentication to bring authentication and authorization to the exposed REST APIs. DB used is MySQL.
What does JWT do
JWT (JSON Web Token) is used for securing REST APIs.
In the JWT authentication process a client application first need to authenticate using credentials. The server side verifies the sent credentials, if valid then it generates and returns a JWT.
Once the client has been authenticated it has to sent the token in the request’s Authorization header in the Bearer Token form with each request. The server will check the validity of the token to verify the validity of the client and authorize or reject requests. You can also store roles and method usage will be authorized based on the role.
You can also configure the URLs that should be authenticated and those that will be permitted without authentication.
Spring Boot + Spring Security with JWT authentication example
In the application we’ll have the user signup and user signin logic. Once the signup is done user should be authenticated when logging in, that configuration would be done using Spring security and JWT.
Maven Dependencies
These are the dependencies needed in the pom.xml file which include Spring security, Spring Data JPA, JWT and MySQL.
<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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.netjs</groupId> <artifactId>springbootsecurity</artifactId> <version>0.0.1-SNAPSHOT</version> <name>SpringBootSecurity</name> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.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-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Project Structure
Once ready the project structure for the Spring Boot authentication application looks like as given below-
DB Tables
Since we are doing both authentication and authorization so there are two master tables for storing User and Role records. There is also a table user_role to capture roles assigned to particular users.
CREATE TABLE `netjs`.`users` ( `id` INT NOT NULL AUTO_INCREMENT, `name` VARCHAR(30) NOT NULL, `email` VARCHAR(45) NOT NULL, `password` VARCHAR(150) NOT NULL, PRIMARY KEY (`id`), UNIQUE INDEX `name_UNIQUE` (`name` ASC), UNIQUE INDEX `email_UNIQUE` (`email` ASC)); CREATE TABLE `netjs`.`role` ( `id` INT NOT NULL AUTO_INCREMENT, `name` VARCHAR(45) NULL, PRIMARY KEY (`id`)); CREATE TABLE `netjs`.`user_role` ( `user_id` INT NOT NULL, `role_id` INT NOT NULL);
Insert the required roles in the Role table.
insert into role (name) values ("ROLE_ADMIN"); insert into role (name) values ("ROLE_USER");
Model classes
Entity classes that map to the DB tables are as follows.
User.java
import java.util.HashSet; import java.util.Set; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.Table; @Entity @Table(name="users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column(name="name") private String userName; @Column(name="email") private String email; @Column(name="password") private String password; @ManyToMany(fetch = FetchType.LAZY) @JoinTable(name = "user_role", joinColumns = @JoinColumn(name="USER_ID", referencedColumnName="ID"), inverseJoinColumns = @JoinColumn(name="ROLE_ID", referencedColumnName="ID")) private Set<Role> roles = new HashSet<>(); public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Set<Role> getRoles() { return roles; } public void setRoles(Set<Role> roles) { this.roles = roles; } }
User has Many-to-Many relationship with Role, that association is captured using the join table user_role.
Role.java
import javax.persistence.Column; import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import javax.persistence.Entity; @Entity @Table(name="role") public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Enumerated(EnumType.STRING) @Column(name="name") private Roles roleName; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Roles getRoleName() { return roleName; } public void setRoleName(Roles roleName) { this.roleName = roleName; } }
Roles.java (Enum)
public enum Roles { ROLE_USER, ROLE_ADMIN }
Ensure that you follow the same nomenclature ROLE_XXX as Spring security uses ROLE_ prefix by default when using hasRole() method.
Apart from these Entity classes there are Model classes related to the request that is sent and the response received.
SignupRequest.java
This class captures the data for the SignUp request which is sent when a new user registers.
public class SignupRequest { private String userName; private String email; private String password; private String[] roles; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String[] getRoles() { return roles; } public void setRoles(String[] roles) { this.roles = roles; } }
AuthResponse.java
This class represents the response you get back when logging in. It includes the authentication token and the roles user is authorized for.
public class AuthResponse { private String token; private List<String> roles; public String getToken() { return token; } public void setToken(String token) { this.token = token; } public List<String> getRoles() { return roles; } public void setRoles(List<String> roles) { this.roles = roles; } }
CustomUserBean.java
Spring Security has an interface org.springframework.security.core.userdetails.UserDetails which provides core user information. You need to provide a concrete implemetation of this interface to add more fields. There is also a concrete implementation org.springframework.security.core.userdetails.User provided by Spring security that can be used directly. Here we are using our own implementation ConcreteUserBean.
Note that in the class there is also a getAuthorities() method that returns authorities of type GrantedAuthority. That list of GrantedAuthority objects is built using the instances of SimpleGrantedAuthority which is the basic concrete implementation of a GrantedAuthority. Stores a String representation of an authority granted to the Authentication object.
import java.util.Collection; import java.util.List; import java.util.stream.Collectors; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import com.fasterxml.jackson.annotation.JsonIgnore; public class CustomUserBean implements UserDetails { private static final long serialVersionUID = -4709084843450077569L; private Integer id; private String userName; private String email; @JsonIgnore private String password; private Collection<? extends GrantedAuthority> authorities; CustomUserBean(Integer id, String userName, String email, String password, Collection<? extends GrantedAuthority> authorities){ this.id = id; this.userName = userName; this.email = email; this.password = password; this.authorities = authorities; } public static CustomUserBean createInstance(User user) { List<GrantedAuthority> authorities = user.getRoles() .stream() .map(role -> new SimpleGrantedAuthority(role.getRoleName().name())) .collect(Collectors.toList()); return new CustomUserBean(user.getId(), user.getUserName(), user.getEmail(), user.getPassword(), authorities); } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } @Override public String getPassword() { return password; } @Override public String getUsername() { return userName; } public Integer getId() { return id; } public String getEmail() { return email; } @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 boolean equals(Object rhs) { if (rhs instanceof CustomUserBean) { return userName.equals(((CustomUserBean) rhs).userName); } return false; } @Override public int hashCode() { return userName.hashCode(); } }
Repositories
Since we are using Spring Data JPA so we just need to create interfaces extending the JpaRepository.
UserRepository
import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.netjstech.model.User; @Repository public interface UserRepository extends JpaRepository<User, Integer>{ public Optional<User> findByUserName(String userName); public boolean existsByEmail(String email); public boolean existsByUserName(String userName); }
RoleRepository
import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.netjstech.model.Role; import com.netjstech.model.Roles; @Repository public interface RoleRepository extends JpaRepository<Role, Integer> { Optional<Role> findByRoleName(Roles role); }
Service Class
Within Spring security there is an interface org.springframework.security.core.userdetails.UserDetailsService that has a method loadUserByUsername(java.lang.String username) to load user-specific data.
We’ll provide a concrete implementation of this interface where the loadUserByUsername() method implementation acts as a wrapper over the userRepository.findByUserName() method call. Returned user is passed to the CustomUserBean.createInstance() method to create instance of CustomUserBean as per our implementation.
import javax.transaction.Transactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import com.netjstech.dao.UserRepository; import com.netjstech.model.CustomUserBean; import com.netjstech.model.User; @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired UserRepository userRepository; @Override @Transactional public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUserName(username) .orElseThrow(() -> new UsernameNotFoundException("User with " + "user name "+ username + " not found")); return CustomUserBean.createInstance(user); } }
Configuring Spring Security and JWT
The following security configuration ensures that only authenticated users can access the APIs.
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; 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.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import com.netjstech.service.UserDetailsServiceImpl; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter{ @Autowired UserDetailsServiceImpl userDetailsService; @Autowired JwtAuthenticationEntryPoint authenticationEntryPoint; @Override public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception{ authenticationManagerBuilder.userDetailsService(userDetailsService) .passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Bean public JwtTokenFilter jwtTokenFilter(){ return new JwtTokenFilter(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { // We don't need CSRF for this example httpSecurity.cors().and().csrf().disable() .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests().antMatchers("/auth/**").permitAll() .antMatchers(HttpMethod.GET, "/user/allusers").permitAll() .anyRequest().authenticated(); httpSecurity.addFilterBefore(jwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); } }
Important points to note here-
The SecurityConfig class is annotated with @EnableWebSecurity to enable Spring Security’s web security support and provide the Spring MVC integration.
Spring Security must be configured in a bean that implements WebSecurityConfigurer or extends WebSecurityConfigurerAdapter. Since extending WebSecurityConfigurerAdapter is more convenient so that is done here and couple of its methods are overridden. Important one are the configure methods-
- configure(AuthenticationManagerBuilder)- Override to configure user-details services.
- configure(HttpSecurity)- Override to configure web based security for specific http requests.
- configure(WebSecurity)- Override to configure the FilterChainProxy known as the Spring Security Filter Chain.
@EnableGlobalMethodSecurity- Used to enable method level security based on annotations.
Spring Security supports three different kinds of security annotations:
- @Secured provided by Spring security itself
- JSR-250’s @RolesAllowed annotation
- Expression based annotations with @PreAuthorize, @PostAuthorize, @PreFilter and @PostFilter
We’ll be using @PreAuthorize annotation for securing methods as you will see in the Controller class.
If you see the configure(HttpSecurity httpSecurity) method any request whose path is /auth/*** and GET request with path /user/allusers should not be authenticated. Any other request should be authenticated. This method also configures which exception handler is used (AuthenticationEntryPoint), how to manage session (stateless in this case) and when to register filter (JwtTokenFilter)
configure(AuthenticationManagerBuilder authenticationManagerBuilder) method is used to add authentication based upon the custom UserDetailsService that is passed in. UserDetailsServiceImpl in our case. It then returns a DaoAuthenticationConfigurer to allow customization of the authentication.
JwtAuthenticationEntryPoint.java
Class that implements AuthenticationEntryPoint. It is used for exception handling and sends the 401 status code as response indicating that the request requires HTTPauthentication.
import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Component public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class); public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { logger.error("Unauthorized access error : " + authException.getMessage()); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized Access"); } }
JwtTokenUtil.java
A utility class that does following three tasks-
- Generate the token by setting user name, issued time, expiration time.
- Validate token
- Get user name from taken.
import java.util.Date; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; import com.netjstech.model.CustomUserBean; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureException; import io.jsonwebtoken.UnsupportedJwtException; @Component public class JwtTokenUtil { @Value("${jwttoken.secret}") private String jwtTokenSecret; @Value("${jwttoken.expiration}") private long jwtTokenExpiration; public String generateJwtToken(Authentication authentication) { CustomUserBean userPrincipal = (CustomUserBean)authentication.getPrincipal(); return Jwts.builder() .setSubject(userPrincipal.getUsername()) .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + jwtTokenExpiration)) .signWith(SignatureAlgorithm.HS512, jwtTokenSecret) .compact(); } public boolean validateJwtToken(String token) { try { Jwts.parser() .setSigningKey(jwtTokenSecret) .parseClaimsJws(token); return true; }catch(UnsupportedJwtException exp) { System.out.println("claimsJws argument does not represent Claims JWS" + exp.getMessage()); }catch(MalformedJwtException exp) { System.out.println("claimsJws string is not a valid JWS" + exp.getMessage()); }catch(SignatureException exp) { System.out.println("claimsJws JWS signature validation failed" + exp.getMessage()); }catch(ExpiredJwtException exp) { System.out.println("Claims has an expiration time before the method is invoked" + exp.getMessage()); }catch(IllegalArgumentException exp) { System.out.println("claimsJws string is null or empty or only whitespace" + exp.getMessage()); } return false; } public String getUserNameFromJwtToken(String token) { Claims claims =Jwts.parser() .setSigningKey(jwtTokenSecret) .parseClaimsJws(token) .getBody(); return claims.getSubject(); } }
JwtTokenFilter.java
A filter class that extends OncePerRequestFilter that guarantees a single execution per requestdispatch.
In the overridden doFilterInternal() method token is extracted from the request header and validated. If token is validated then get user name from it and use it to get UserDetails. From these user details and its authorities create an Authentication object.
import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; 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.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import com.netjstech.service.UserDetailsServiceImpl; public class JwtTokenFilter extends OncePerRequestFilter{ @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private UserDetailsServiceImpl userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { String token = getTokenFromRequest(request); //System.out.println("Token-- " + token); if (token != null && jwtTokenUtil.validateJwtToken(token)) { String username = jwtTokenUtil.getUserNameFromJwtToken(token); //System.out.println("User Name--JwtTokenFilter-- " + username); UserDetails userDetails = userDetailsService.loadUserByUsername(username); //System.out.println("Authorities--JwtTokenFilter-- " + userDetails.getAuthorities()); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } catch (Exception e) { //logger.error("Cannot set user authentication: {}", e); throw new RuntimeException("Cannot set user authentication" + e.getMessage()); } filterChain.doFilter(request, response); } private String getTokenFromRequest(HttpServletRequest request) { String token = request.getHeader("Authorization"); if (StringUtils.hasText(token) && token.startsWith("Bearer ")) { // remove "Bearer " return token.substring(7, token.length()); } return null; } }
src/main/resources/application.properties
Properties to configure Spring Datasource, JPA and also JWT.
spring.datasource.url=jdbc:mysql://localhost:3306/netjs spring.datasource.username=root spring.datasource.password=admin spring.jpa.properties.hibernate.sqldialect=org.hibernate.dialect.MySQL5InnoDBDialect spring.jpa.properties.hibernate.showsql=true jwttoken.secret=mysecretkey # in milliseconds (5 mins) jwttoken.expiration=300000
Controller classes
AuthController.java
This controller class has two methods-
- userSignup() which is a handler method for any POST request to /auth/signup path.
- userLogin() which is a handler method for any POST request to /auth/login path.
import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.password.PasswordEncoder; 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.netjstech.dao.RoleRepository; import com.netjstech.dao.UserRepository; import com.netjstech.model.AuthResponse; import com.netjstech.model.CustomUserBean; import com.netjstech.model.Role; import com.netjstech.model.Roles; import com.netjstech.model.SignupRequest; import com.netjstech.model.User; import com.netjstech.security.JwtTokenUtil; @RestController @RequestMapping("/auth") public class AuthController { @Autowired UserRepository userRepository; @Autowired RoleRepository roleRepository; @Autowired PasswordEncoder encoder; @Autowired AuthenticationManager authenticationManager; @Autowired JwtTokenUtil jwtTokenUtil; @PostMapping("/login") public ResponseEntity<?> userLogin(@Valid @RequestBody User user) { //System.out.println("AuthController -- userLogin"); Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword())); SecurityContextHolder.getContext().setAuthentication(authentication); String token = jwtTokenUtil.generateJwtToken(authentication); CustomUserBean userBean = (CustomUserBean) authentication.getPrincipal(); List<String> roles = userBean.getAuthorities().stream() .map(auth -> auth.getAuthority()) .collect(Collectors.toList()); AuthResponse authResponse = new AuthResponse(); authResponse.setToken(token); authResponse.setRoles(roles); return ResponseEntity.ok(authResponse); } @PostMapping("/signup") public ResponseEntity<?> userSignup(@Valid @RequestBody SignupRequest signupRequest) { if(userRepository.existsByUserName(signupRequest.getUserName())){ return ResponseEntity.badRequest().body("Username is already taken"); } if(userRepository.existsByEmail(signupRequest.getEmail())){ return ResponseEntity.badRequest().body("Email is already taken"); } User user = new User(); Set<Role> roles = new HashSet<>(); user.setUserName(signupRequest.getUserName()); user.setEmail(signupRequest.getEmail()); user.setPassword(encoder.encode(signupRequest.getPassword())); //System.out.println("Encoded password--- " + user.getPassword()); String[] roleArr = signupRequest.getRoles(); if(roleArr == null) { roles.add(roleRepository.findByRoleName(Roles.ROLE_USER).get()); } for(String role: roleArr) { switch(role) { case "admin": roles.add(roleRepository.findByRoleName(Roles.ROLE_ADMIN).get()); break; case "user": roles.add(roleRepository.findByRoleName(Roles.ROLE_USER).get()); break; default: return ResponseEntity.badRequest().body("Specified role not found"); } } user.setRoles(roles); userRepository.save(user); return ResponseEntity.ok("User signed up successfully"); } }
In the userSignup() method first thing is to verify that the passed user name or email are no already in use. Then create a User object using the values passed in SignupRequest object. Extract the roles and set those also in the User object and then save the User. If everything works fine then set the status code as ok in the response with the message.
In the userLogin() method authenticate the user and set the authentication object in SecurityContext. Then generate the token and get the list of user roles. Set both token and list of roles in the response that is sent back.
UserController.java
This controller just demonstrates the use of authorization by access controlling the methods using @PreAuthorize annotation.
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/user") public class UserController { @GetMapping("/allusers") public String displayUsers() { return "Display All Users"; } @GetMapping("/displayuser") @PreAuthorize("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')") public String displayToUser() { return "Display to both user and admin"; } @GetMapping("/displayadmin") @PreAuthorize("hasRole('ROLE_ADMIN')") public String displayToAdmin() { return "Display only to admin"; } }
As evident from the annotations-
- displayUsers() method can be accessed by all.
- displayToUser() method can be accessed by user having role user or admin.
- displayToAdmin() method can be accessed by user having role admin.
Application class
Class with main method to run the application.
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringBootAuthApp { public static void main(String[] args) { SpringApplication.run(SpringBootAuthApp.class, args); } }
Testing the application
You can run the above class with main method as a Java application that would set up the Spring Boot application and start the web server to listen on a given port.
Registering user by sending POST request to signup.
In the DB you can check the USER table to verify that user data is inserted.
Also the mapped roles.
With the registered user you can login. As a response you’ll get the generated token you need to send that token with other requests.
Display all users, this method displayUsers() is permitted to all as configured in SecurityConfig class. With this URL- http://localhost:8080/user/allusers user should be able to access it even when not logged in.
displayToUser() method need authentication and also authorization that the logged user should have role USER or ADMIN. You need to send the token which is returned after login so go to Authorization tab select “Bearer Token” in the Type dropdown and add the token.
displayToAdmin() method need authentication and also authorization that the logged user should have role ADMIN. Trying to access this method with the user having only USER role authorization results in “Forbidden” error.
Source code from GitHub- https://github.com/netjs/spring-boot-security-jwt
That's all for this topic Spring Boot + Spring Security JWT Authentication Example. If you have any doubt or any suggestions to make please drop a comment. Thanks!
>>>Return to Spring Tutorial Page
Related Topics
You may also like-
No comments:
Post a Comment