[no_toc]JSON Web Token defines a compact and self-contained way for securely transmitting information as a JSON object. In the tutorial, we show how to build a SpringBoot Security RestAPIs with JSON Web Token (JWT).
Related posts:
– Spring Security – JDBC Authentication – SpringBoot + PostgreSQL + Bootstrap
– SQL Tutorial – MySQL Many-to-Many Relationship
– Spring JPA Hibernate Many to Many – SpringBoot + PostgreSQL
Technologies
– Spring Boot
– jjwt – 0.9.0
– Spring Security
– Spring JPA
– PostgreSQL
JSON Web Token
JSON Web Token (JWT) defines a compact and self-contained way for securely transmitting information between parties as a JSON object.
Scenarios where JSON Web Tokens are useful:
- Authorization: the most common scenario for using JWT. Single Sign On is a feature that widely uses JWT
- Information Exchange: Because JWTs can be signed, JSON Web Tokens are a good way of securely transmitting information between parties.
JSON Web Tokens consist of 3 parts:
- Header
- Payload
- Signature
-> JWT
looks like Header-Base64-String.Payload-Base64-String.Signature-Base64-String
Header consists of two parts:
- token type.
- hashing algorithm.
-> Example:
{ "alg": "HS256", "typ": "JWT" }
Payload contains the claims. Claims are statements about an entity and additional information.
There are 3 types of claims ->
Registered claims
-> These are a set of predefined claims:iss
(issuer),exp
(expiration time),sub
(subject)Public claims
Private claims
Example ->
{ "sub": "thomasgkz", "iat": 1537603195, "exp": 1537689595 }
Signature -> To create the signature part you have to take the encoded header, the encoded payload, a secret, the algorithm specified in the header, and sign that.
Example ->
HMACSHA512( base64UrlEncode(header) + "." + base64UrlEncode(payload), your-256-bit-secret )
Combine all together, we get 3 Base64-URL strings separated by dots,
-> Example:
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0aG9tYXNna3oiLCJpYXQiOjE1Mzc2MDMxOTUsImV4cCI6MTUzNzY4OTU5NX0.m2YMjTYmOnfR7nnVNxqCzWbQ2FhKRe1eiizxnC2TF4eAoEzKlwo7PheVkKcxj08ST3vB-ZOIhiORvYVfSgzcog
When accessing a protected route or resource, the user agent should send the JWT
, typically in the Authorization header using the Bearer schema.
-> Example:
Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0aG9tYXNna3oiLCJpYXQiOjE1Mzc2MDMxOTUsImV4cCI6MTUzNzY4OTU5NX0.m2YMjTYmOnfR7nnVNxqCzWbQ2FhKRe1eiizxnC2TF4eAoEzKlwo7PheVkKcxj08ST3vB-ZOIhiORvYVfSgzcog
Overview
Project Structure
We create a SpringBoot project as below:
– model
package defines 2 entities User
& Role
that have many-to-many relationship:
– repository
package contains interfaces that use Hibernate JPA to store/retrieve data from PostgreSQL database.
– controller
package defines RestAPIs for user signup/signin and testing protected resources that is secured with JWT.
– message
package defines payload data transferred from user agents (Browser/RestClient…) to RestAPIs and message back.
– security
package is the main part of the project that implements JWT security.
Goal
– We expose 2 RestAPIs to signup and signin:
/api/auth/signup
-> sign up/api/auth/signin
-> sign in
– We expose 3 RestAPIs to test protected resources:
@GetMapping("/api/test/user") @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") public String userAccess() { return ">>> User Contents!"; } @GetMapping("/api/test/pm") @PreAuthorize("hasRole('PM') or hasRole('ADMIN')") public String projectManagementAccess() { return ">>> Board Management Project"; } @GetMapping("/api/test/admin") @PreAuthorize("hasRole('ADMIN')") public String adminAccess() { return ">>> Admin Contents"; }
- Access Successfully ->
- Unauthorized ->
Practice
Create SpringBoot project
We create a SpringBoot project with below dependencies:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</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-web</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>
Create Models
– User.java
model contains 5 attributes:
- id
- name
- username
- password
package com.ozenero.jwtauthentication.model; import java.util.HashSet; import java.util.Set; 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; import javax.persistence.UniqueConstraint; import javax.validation.constraints.Email; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; import org.hibernate.annotations.NaturalId; @Entity @Table(name = "users", uniqueConstraints = { @UniqueConstraint(columnNames = { "username" }), @UniqueConstraint(columnNames = { "email" }) }) public class User{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotBlank @Size(min=3, max = 50) private String name; @NotBlank @Size(min=3, max = 50) private String username; @NaturalId @NotBlank @Size(max = 50) @Email private String email; @NotBlank @Size(min=6, max = 100) private String password; @ManyToMany(fetch = FetchType.LAZY) @JoinTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) private Setroles = new HashSet<>(); public User() {} public User(String name, String username, String email, String password) { this.name = name; this.username = username; this.email = email; this.password = password; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getName() { return name; } public void setName(String name) { this.name = name; } 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 getRoles() { return roles; } public void setRoles(Set roles) { this.roles = roles; } }
– Role.java
model contains 2 attributes:
- id
- rolename
package com.ozenero.jwtauthentication.model; import javax.persistence.Column; import javax.persistence.Entity; 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 org.hibernate.annotations.NaturalId; @Entity @Table(name = "roles") public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Enumerated(EnumType.STRING) @NaturalId @Column(length = 60) private RoleName name; public Role() {} public Role(RoleName name) { this.name = name; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public RoleName getName() { return name; } public void setName(RoleName name) { this.name = name; } }
– RoleName.java
->
package com.ozenero.jwtauthentication.model; public enum RoleName { ROLE_USER, ROLE_PM, ROLE_ADMIN }
Implement Repository
– UserRepository
->
package com.ozenero.jwtauthentication.repository; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.ozenero.jwtauthentication.model.User; @Repository public interface UserRepository extends JpaRepository{ Optional findByUsername(String username); Boolean existsByUsername(String username); Boolean existsByEmail(String email); }
– RoleRepository.java
->
package com.ozenero.jwtauthentication.repository; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.ozenero.jwtauthentication.model.Role; import com.ozenero.jwtauthentication.model.RoleName; @Repository public interface RoleRepository extends JpaRepository{ Optional findByName(RoleName roleName);
Implement JWT Security
– Configure WebSecurityConfig.java
:
package com.ozenero.jwtauthentication.security; 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.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import com.ozenero.jwtauthentication.security.jwt.JwtAuthEntryPoint; import com.ozenero.jwtauthentication.security.jwt.JwtAuthTokenFilter; import com.ozenero.jwtauthentication.security.services.UserDetailsServiceImpl; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity( prePostEnabled = true ) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired UserDetailsServiceImpl userDetailsService; @Autowired private JwtAuthEntryPoint unauthorizedHandler; @Bean public JwtAuthTokenFilter authenticationJwtTokenFilter() { return new JwtAuthTokenFilter(); } @Override public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { authenticationManagerBuilder .userDetailsService(userDetailsService) .passwordEncoder(passwordEncoder()); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and().csrf().disable(). authorizeRequests() .antMatchers("/api/auth/**").permitAll() .anyRequest().authenticated() .and() .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); } }
– @EnableWebSecurity
is used to enable web security in a project.
– @EnableGlobalMethodSecurity(prePostEnabled = true)
is used to enable Spring Security global method security.
-> Example:
@PreAuthorize("hasRole('USER') or hasRole('ADMIN')") public String userAccess() { @GetMapping("/api/test/pm") @PreAuthorize("hasRole('PM') or hasRole('ADMIN')") @GetMapping("/api/test/admin") @PreAuthorize("hasRole('ADMIN')")
– PasswordEncoder
uses the BCrypt strong hashing function.
UserDetails Service
– UserDetailsServiceImpl
implements UserDetailsService
that will override loadUserByUsername
method.
loadUserByUsername
method will find a record from users
database tables to build a UserDetails
object for authentication.
package com.ozenero.jwtauthentication.security.services; import com.ozenero.jwtauthentication.model.User; import com.ozenero.jwtauthentication.repository.UserRepository; 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 org.springframework.transaction.annotation.Transactional; @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 Not Found with -> username or email : " + username) ); return UserPrinciple.build(user); } }
-> UserPrinciple
will implement UserDetails
.
UserPrinciple
is not used directly by Spring Security for security purposes.
It simply stores user information which is later encapsulated into Authentication
objects. This allows non-security related user information (such as email addresses, telephone numbers etc) to be stored.
package com.ozenero.jwtauthentication.security.services; import com.ozenero.jwtauthentication.model.User; import com.fasterxml.jackson.annotation.JsonIgnore; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; public class UserPrinciple implements UserDetails { private static final long serialVersionUID = 1L; private Long id; private String name; private String username; private String email; @JsonIgnore private String password; private Collection extends GrantedAuthority> authorities; public UserPrinciple(Long id, String name, String username, String email, String password, Collection extends GrantedAuthority> authorities) { this.id = id; this.name = name; this.username = username; this.email = email; this.password = password; this.authorities = authorities; } public static UserPrinciple build(User user) { Listauthorities = user.getRoles().stream().map(role -> new SimpleGrantedAuthority(role.getName().name()) ).collect(Collectors.toList()); return new UserPrinciple( user.getId(), user.getName(), user.getUsername(), user.getEmail(), user.getPassword(), authorities ); } public Long getId() { return id; } public String getName() { return name; } public String getEmail() { return email; } @Override public String getUsername() { return username; } @Override public String getPassword() { return password; } @Override public Collection extends GrantedAuthority> getAuthorities() { return authorities; } @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 o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; UserPrinciple user = (UserPrinciple) o; return Objects.equals(id, user.id); } }
JWT Authentication Classes
– JwtAuthTokenFilter
extends OncePerRequestFilter
.
org.springframework.web.filter.OncePerRequestFilter
-> Executes once per request. This is a filter base class that is used to guarantee a single execution per request dispatch. It provides a doFilterInternal
method with HttpServletRequest
and HttpServletResponse
arguments.
In JwtAuthTokenFilter
class, the doFilterInternal
method will do:
- get
JWT
token from header - validate
JWT
- parse
username
from validatedJWT
- load data from
users
table, then build anauthentication
object - set the
authentication
object to Security Context
package com.ozenero.jwtauthentication.security.jwt; import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.web.filter.OncePerRequestFilter; import com.ozenero.jwtauthentication.security.services.UserDetailsServiceImpl; public class JwtAuthTokenFilter extends OncePerRequestFilter { @Autowired private JwtProvider tokenProvider; @Autowired private UserDetailsServiceImpl userDetailsService; private static final Logger logger = LoggerFactory.getLogger(JwtAuthTokenFilter.class); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { String jwt = getJwt(request); if (jwt!=null && tokenProvider.validateJwtToken(jwt)) { String username = tokenProvider.getUserNameFromJwtToken(jwt); UserDetails userDetails = userDetailsService.loadUserByUsername(username); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } catch (Exception e) { logger.error("Can NOT set user authentication -> Message: {}", e); } filterChain.doFilter(request, response); } private String getJwt(HttpServletRequest request) { String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { return authHeader.replace("Bearer ",""); } return null; } }
– JwtAuthEntryPoint
is used to handle Error exception when having unauthorized
requests.
package com.ozenero.jwtauthentication.security.jwt; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; @Component public class JwtAuthEntryPoint implements AuthenticationEntryPoint { private static final Logger logger = LoggerFactory.getLogger(JwtAuthEntryPoint.class); @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException { logger.error("Unauthorized error. Message - {}", e.getMessage()); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error -> Unauthorized"); } }
– JwtProvider
is an util class -> it implements useful functions:
- generate a
JWT
token - valiate a
JWT
token - parse
username
fromJWT
token
package com.ozenero.jwtauthentication.security.jwt; import io.jsonwebtoken.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; import com.ozenero.jwtauthentication.security.services.UserPrinciple; import java.util.Date; @Component public class JwtProvider { private static final Logger logger = LoggerFactory.getLogger(JwtProvider.class); @Value("${ozenero.app.jwtSecret}") private String jwtSecret; @Value("${ozenero.app.jwtExpiration}") private int jwtExpiration; public String generateJwtToken(Authentication authentication) { UserPrinciple userPrincipal = (UserPrinciple) authentication.getPrincipal(); return Jwts.builder() .setSubject((userPrincipal.getUsername())) .setIssuedAt(new Date()) .setExpiration(new Date((new Date()).getTime() + jwtExpiration)) .signWith(SignatureAlgorithm.HS512, jwtSecret) .compact(); } public String getUserNameFromJwtToken(String token) { return Jwts.parser() .setSigningKey(jwtSecret) .parseClaimsJws(token) .getBody().getSubject(); } public boolean validateJwtToken(String authToken) { try { Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken); return true; } catch (SignatureException e) { logger.error("Invalid JWT signature -> Message: {} ", e); } catch (MalformedJwtException e) { logger.error("Invalid JWT token -> Message: {}", e); } catch (ExpiredJwtException e) { logger.error("Expired JWT token -> Message: {}", e); } catch (UnsupportedJwtException e) { logger.error("Unsupported JWT token -> Message: {}", e); } catch (IllegalArgumentException e) { logger.error("JWT claims string is empty -> Message: {}", e); } return false; } }
Implement RestControllers
Create Payload Message
– LoginForm.java
contains username
& password
->
package com.ozenero.jwtauthentication.message.request; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; public class LoginForm { @NotBlank @Size(min=3, max = 60) private String username; @NotBlank @Size(min = 6, max = 40) private String password; 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; } }
– SignUpForm.java
contains:
- name
- username
- role
- password
package com.ozenero.jwtauthentication.message.request; import java.util.Set; import javax.validation.constraints.*; public class SignUpForm { @NotBlank @Size(min = 3, max = 50) private String name; @NotBlank @Size(min = 3, max = 50) private String username; @NotBlank @Size(max = 60) @Email private String email; private Setrole; @NotBlank @Size(min = 6, max = 40) private String password; public String getName() { return name; } public void setName(String name) { this.name = name; } 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 getRole() { return this.role; } public void setRole(Set role) { this.role = role; } }
– JwtResponse.java
is returned by SpringBoot server after successful authentication, it contains 2 parts:
- JWT Token
- Schema Type of Token
package com.ozenero.jwtauthentication.message.response; public class JwtResponse { private String token; private String type = "Bearer"; public JwtResponse(String accessToken) { this.token = accessToken; } public String getAccessToken() { return token; } public void setAccessToken(String accessToken) { this.token = accessToken; } public String getTokenType() { return type; } public void setTokenType(String tokenType) { this.type = tokenType; } }
RestAPIs Controller
– AuthRestAPIs.java
defines 2 APIs:
/api/auth/signup
: sign up
-> check username/email is already in use.
-> createUser
object
-> store to database/api/auth/signin
: sign in
-> attempt to authenticate withAuthenticationManager
bean.
-> addauthentication
object toSecurityContextHolder
-> GenerateJWT
token, then returnJWT
to client
package com.ozenero.jwtauthentication.controller; import java.util.HashSet; import java.util.Set; import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; 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.CrossOrigin; 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.ozenero.jwtauthentication.message.request.LoginForm; import com.ozenero.jwtauthentication.message.request.SignUpForm; import com.ozenero.jwtauthentication.message.response.JwtResponse; import com.ozenero.jwtauthentication.model.Role; import com.ozenero.jwtauthentication.model.RoleName; import com.ozenero.jwtauthentication.model.User; import com.ozenero.jwtauthentication.repository.RoleRepository; import com.ozenero.jwtauthentication.repository.UserRepository; import com.ozenero.jwtauthentication.security.jwt.JwtProvider; @CrossOrigin(origins = "*", maxAge = 3600) @RestController @RequestMapping("/api/auth") public class AuthRestAPIs { @Autowired AuthenticationManager authenticationManager; @Autowired UserRepository userRepository; @Autowired RoleRepository roleRepository; @Autowired PasswordEncoder encoder; @Autowired JwtProvider jwtProvider; @PostMapping("/signin") public ResponseEntity> authenticateUser(@Valid @RequestBody LoginForm loginRequest) { Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( loginRequest.getUsername(), loginRequest.getPassword() ) ); SecurityContextHolder.getContext().setAuthentication(authentication); String jwt = jwtProvider.generateJwtToken(authentication); return ResponseEntity.ok(new JwtResponse(jwt)); } @PostMapping("/signup") public ResponseEntityregisterUser(@Valid @RequestBody SignUpForm signUpRequest) { if(userRepository.existsByUsername(signUpRequest.getUsername())) { return new ResponseEntity ("Fail -> Username is already taken!", HttpStatus.BAD_REQUEST); } if(userRepository.existsByEmail(signUpRequest.getEmail())) { return new ResponseEntity ("Fail -> Email is already in use!", HttpStatus.BAD_REQUEST); } // Creating user's account User user = new User(signUpRequest.getName(), signUpRequest.getUsername(), signUpRequest.getEmail(), encoder.encode(signUpRequest.getPassword())); Set strRoles = signUpRequest.getRole(); Set roles = new HashSet<>(); strRoles.forEach(role -> { switch(role) { case "admin": Role adminRole = roleRepository.findByName(RoleName.ROLE_ADMIN) .orElseThrow(() -> new RuntimeException("Fail! -> Cause: User Role not find.")); roles.add(adminRole); break; case "pm": Role pmRole = roleRepository.findByName(RoleName.ROLE_PM) .orElseThrow(() -> new RuntimeException("Fail! -> Cause: User Role not find.")); roles.add(pmRole); break; default: Role userRole = roleRepository.findByName(RoleName.ROLE_USER) .orElseThrow(() -> new RuntimeException("Fail! -> Cause: User Role not find.")); roles.add(userRole); } }); user.setRoles(roles); userRepository.save(user); return ResponseEntity.ok().body("User registered successfully!"); } }
– TestRestAPIs
define 3 RestAPIs:
/api/test/user
-> access by users hasUSER_ROLE
orADMIN_ROLE
/api/test/pm
-> access by users hasUSER_PM
orADMIN_ROLE
/api/test/admin
-> access by users hasADMIN_ROLE
package com.ozenero.jwtauthentication.controller; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class TestRestAPIs { @GetMapping("/api/test/user") @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") public String userAccess() { return ">>> User Contents!"; } @GetMapping("/api/test/pm") @PreAuthorize("hasRole('PM') or hasRole('ADMIN')") public String projectManagementAccess() { return ">>> Board Management Project"; } @GetMapping("/api/test/admin") @PreAuthorize("hasRole('ADMIN')") public String adminAccess() { return ">>> Admin Contents"; } }
Application Properties
application.properties
file ->
spring.datasource.url=jdbc:postgresql://localhost/testdb spring.datasource.username=postgres spring.datasource.password=123 spring.jpa.generate-ddl=true # App Properties ozenero.app.jwtSecret=jwtGrokonezSecretKey ozenero.app.jwtExpiration=86400
Run & Check Results
Start SpringBoot
– Start Springboot server by commandline mvn spring-boot:run
– Check database tables ->
– Insert data to roles
table ->
INSERT INTO roles(name) VALUES('ROLE_USER'); INSERT INTO roles(name) VALUES('ROLE_PM'); INSERT INTO roles(name) VALUES('ROLE_ADMIN');
SignUp
Sign-Up 3 users:
- Jack has
ROLE_USER
role - Adam has
ROLE_PM
&ROLE_USER
roles - Thomas has
ROLE_ADMIN
role
– Check database’s tables ->
SignIn and Access Protected Resources
– Adam can access api/test/user
url, can NOT access others.
-> Access Protected Resources:
– Jack can access api/test/user
and api/test/pm
url.
Can NOT access /api/test/admin
url.
-> Sign In:
-> Access Protected Resources:
– Thomas can access all URLs.
-> Sign In:
-> Access Protected Resource: