Spring Security – Authentication and Role Based Authorization using JWT

Spring Security JWT Auth

In this post you will see an example on Spring Security authentication and role based authorization using JWT (JSON Web Token) on REST or RESTful services. I won’t explain here about JWT as there is already very good article on JWT. I will implement Spring Security’s UserDetailsService to load user from database.

I will use Spring JDBC API to perform database operations for fetching or saving user and roles into database. I will use H2 in-memory database to build a quick Spring Boot application. I will also use MySQL database to store the user information.

Authentication and Authorization Flow

Here I will tell you how authentication and authorization work in this application that I am going to implement in with Spring Boot and JWT APIs in subsequent sections.

  1. User signup at end-point /signup with username, password and role(s).
  2. The user information are stored into database.
  3. User signin at end-point /signin using the username and password, which user used at step 1.
  4. User receives JWT (JSON Web Token) on successful signin.
  5. User continues to access the end-points for which user has role(s) as long as the token is valid. User must send JWT in HTTP header with key/value as Authorization/Bearer <generated JWT on signin>.

Note: The JWT is valid for 3 minutes, so token gets expired automatically after 3 minutes. Therefore it’s not mandatory to verify the token expiration but optionally you can check for expiration as you will see in this example.

Prerequisites

Java 12/19, Gradle 6.1.1, Maven 3.6.3/3.8.5, Spring Boot 2.2.4/3.1.4, H2 1.4.196, MySQL 8.0.17/8.1.0

Project Setup

Create a gradle based project in Eclipse with Group Id: com.roytuts and Artifact Id: spring-security-api-auth-jwt.

For spring boot version 3.x.x I am using the pom.xml file for building the project:

<?xml version="1.0" encoding="UTF-8"?>

<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>com.roytuts</groupId>
	<artifactId>spring-security-api-auth-jwt</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>19</maven.compiler.source>
		<maven.compiler.target>19</maven.compiler.target>
	</properties>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.1.4</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-jdbc</artifactId>
		</dependency>

		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-api</artifactId>
			<version>0.11.5</version>
		</dependency>

		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-jackson</artifactId>
			<version>0.11.5</version>
			<scope>runtime</scope>
		</dependency>

		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-impl</artifactId>
			<version>0.11.5</version>
			<scope>runtime</scope>
		</dependency>

		<dependency>
			<groupId>com.mysql</groupId>
			<artifactId>mysql-connector-j</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

For spring boot 2.x.x you can follow the below build files.

Replace the default generated build.gradle file with below content to include the required dependencies.

buildscript {
	ext {
		springBootVersion = '2.2.4.RELEASE'
	}
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

plugins {
    id 'java-library'
    id 'org.springframework.boot' version '2.2.4.RELEASE'
}

sourceCompatibility = 12
targetCompatibility = 12

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
	implementation("org.springframework.boot:spring-boot-starter-security:${springBootVersion}")
	implementation("org.springframework.boot:spring-boot-starter-data-jpa:${springBootVersion}")
	implementation("io.jsonwebtoken:jjwt:0.9.1")
	implementation('javax.servlet:jstl:1.2')
	implementation('mysql:mysql-connector-java:8.0.17')
	//required only if jdk 9 or higher version is used
    runtimeOnly('javax.xml.bind:jaxb-api:2.4.0-b180830.0359')
	//runtime("com.h2database:h2:1.4.196")
}

If you are creating maven based project in Eclipse then you can use below pom.xml file:

<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>com.roytuts</groupId>
	<artifactId>spring-security-api-auth-jwt</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.4.RELEASE</version>
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<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>
		
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt</artifactId>
			<version>0.9.1</version>
		</dependency>
		
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>
		
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.17</version>
		</dependency>
		
		<!--required only if jdk 9 or higher version is used-->
		<dependency>
			<groupId>javax.xml.bind</groupId>
			<artifactId>jaxb-api</artifactId>
			<version>2.4.0-b180830.0359</version>
			<scope>runtime</scope>
		</dependency>
		
		<!--<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<version>1.4.196</version>
			<scope>runtime</scope>
		</dependency>-->
	</dependencies>

    <build>
        <plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.8.1</version>
				<configuration>
					<source>at least 8</source>
					<target>at least 8</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

SQL Scripts

The SQL scripts are created only for spring boot version 2.x.x and for spring boot 3.x.x I will use only the MySQL server.

I will store user information – user details and user role details – into H2 database. Therefore I need to create tables using SQL scripts as I am using in-memory database.

If you are using MySQL database then you should execute the below table creation statements into MySQL server and you don’t need to create any SQL script under classpath directory.

Table – user

I will store username, password and active flag in the below table.

Create a file user.sql under src/main/resources folder with below content.

/*Table structure for table `user` */
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `user_name` varchar(30) NOT NULL,
  `user_pass` varchar(255) NOT NULL,
  `enable` tinyint(1) NOT NULL DEFAULT '1',
  PRIMARY KEY (`user_name`)
);

Table – user_role

I will store user role along with username as a foreign key that references to the above user table.

Create a file user-role.sql under src/main/resources folder with below content.

/*Table structure for table `user_role` */
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
  `user_name` varchar(30) NOT NULL,
  `user_role` varchar(15) NOT NULL,
  FOREIGN KEY (`user_name`) REFERENCES `user` (`user_name`)
);

Database Config Class

The database configuration class is required only for spring boot 2.x.x. I am going to use the auto-configuration feature for spring boot 3.x.x.

I will define DataSource and JdbcTemplate beans to perform database operations.

I am using H2 in-memory database as well as MySQL as an external database for the application.

If you are using in-memory database then you don’t need to use username, password, database URL, database name because Spring Boot will use default values for these parameters.

But for MySQL database I need such configurations, therefore I have created two separate datasources for each type of database.

@Configuration
public class DatabaseConfig {

	@Autowired
	private Environment environment;
	
	/*@Bean
	public DataSource dataSource() {
		EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
		EmbeddedDatabase db = builder.setType(EmbeddedDatabaseType.H2) // .H2 or .DERBY, etc.
				.addScript("user.sql").addScript("user-role.sql").build();
		return db;
	}*/

	@Bean
	public DataSource dataSource() {
		DriverManagerDataSource dataSource = new DriverManagerDataSource();

		dataSource.setDriverClassName(environment.getProperty("jdbc.driverClassName"));
		dataSource.setUrl(environment.getProperty("jdbc.url"));
		dataSource.setUsername(environment.getProperty("jdbc.username"));
		dataSource.setPassword(environment.getProperty("jdbc.password"));

		return dataSource;
	}

	@Bean
	public JdbcTemplate getJdbcTemplate() throws ClassNotFoundException {
		JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
		return jdbcTemplate;
	}

}

Model Classes

I will create model classes to map our data to database table or vice versa.

Class – User

Class User will map Java class to user table in database.

public class User {
	private String username;
	private String userpwd;
	//getters and setters
}

Class – Role

Class Role maps to the user_role table in the database.

public class Role {
	private String role;
	//getter and setter
}

Class – UserRole

This class carries user and role details for a particular user.

public class UserRole {
	private String username;
	private String userpwd;
	private Set<String> roles;
	//getters and setters
}

RowMapper Class

RowMapper class is required to map table’s row value to Java object.

The following class maps user table row data to User class.

public class UserRowMapper implements RowMapper<User> {
	@Override
	public User mapRow(ResultSet rs, int rowNum) throws SQLException {
		User user = new User();
		user.setUsername(rs.getString("user_name"));
		user.setUserpwd(rs.getString("user_pass"));
		return user;
	}
}

DAO Class

I will create DAO class to fetch or save user information.

I autowire JdbcTemplate in the below DAO class.

I fetch a user detail for a given username using getUser() method.

A user may have multiple roles, so I am fetching a list of roles for a given username using getRoles() method. This method shows how to select multiple rows using Spring JDBC API.

When user signs up or register then I save user information into the table using saveUser() method.

For spring boot 3.x.x use the following class:

@Repository
public class UserDao {

	@Autowired
	private JdbcTemplate jdbcTemplate;

	public User getUser(String username) {
		List<User> users = jdbcTemplate.query("select user_name, user_pass from user where user_name = ?",
				new UserRowMapper(), new Object[] { username });

		if (users.isEmpty()) {
			return null;
		}

		return users.get(0);
	}

	public List<Role> getRoles(String username) {
		List<Map<String, Object>> results = jdbcTemplate
				.queryForList("select user_role from user_role where user_name = ?", new Object[] { username });

		List<Role> roles = results.stream().map(m -> {
			Role role = new Role();
			role.setRole(String.valueOf(m.get("user_role")));
			return role;
		}).collect(Collectors.toList());

		return roles;
	}

	public void saveUser(UserRole user) {
		jdbcTemplate.update("insert into user(user_name, user_pass) values(?, ?)",
				new Object[] { user.getUsername(), user.getUserpwd() });

		user.getRoles().forEach(r -> jdbcTemplate.update(new PreparedStatementCreator() {
			public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
				PreparedStatement ps = connection.prepareStatement(
						"insert into user_role(user_name, user_role) values(?, ?)",
						new String[] { "user_name", "user_role" });
				ps.setString(1, user.getUsername());
				ps.setString(2, r);
				return ps;
			}
		}));
	}
}

For spring boot 2.x.x use the following class:

@Repository
public class UserDao {
	@Autowired
	private JdbcTemplate jdbcTemplate;
	public User getUser(String username) {
		return jdbcTemplate.queryForObject("select user_name, user_pass from user where user_name = ?",
				new Object[] { username }, new UserRowMapper());
	}
	public List<Role> getRoles(String username) {
		List<Map<String, Object>> results = jdbcTemplate
				.queryForList("select user_role from user_role where user_name = ?", new Object[] { username });
		List<Role> roles = results.stream().map(m -> {
			Role role = new Role();
			role.setRole(String.valueOf(m.get("user_role")));
			return role;
		}).collect(Collectors.toList());
		return roles;
	}
	public void saveUser(UserRole user) {
		jdbcTemplate.update("insert into user(user_name, user_pass) values(?, ?)",
				new Object[] { user.getUsername(), user.getUserpwd() });
		user.getRoles().forEach(r -> jdbcTemplate.update(new PreparedStatementCreator() {
			public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
				PreparedStatement ps = connection.prepareStatement(
						"insert into user_role(user_name, user_role) values(?, ?)",
						new String[] { "user_name", "user_role" });
				ps.setString(1, user.getUsername());
				ps.setString(2, r);
				return ps;
			}
		}));
	}
}

VO Classes

It is always good idea to create request/response objects separately instead of using entity or model classes for non-DAO classes.

Class – JwtRequest

This class is used as a RequestBody for /signin end-point for sending credentials for user authentication and generating token.

public class JwtRequest {
	private String username;
	private String userpwd;
	// getters and setters
}

Class – JwtResponse

This class is used to wrap the generated token to send as a response to the client. You may simply send the generated token as a string.

public class JwtResponse {
	private String token;
	public JwtResponse(String token) {
		this.token = token;
	}
	public String getToken() {
		return token;
	}
}

Class – UserVo

This class is used to hold user information, such as, username, password and roles.

public class UserVo {
	private String username;
	private String userpwd;
	private Set<String> roles;
	// getters and setters
}

Related posts:

Service Class

I will implement Spring Security’s UserDetailsService to load user by username using loadUserByUsername() method.

This service class is required to authenticate and role based authorization.

I have also added additional method saveUser() to save new user’s information when a user signs up.

@Service
public class UserAuthService implements UserDetailsService {

	@Autowired
	private UserDao userDao;

	@Autowired
	private PasswordEncoder passwordEncoder;

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		User user = userDao.getUser(username);
		if (user == null) {
			throw new UsernameNotFoundException("User '" + username + "' not found.");
		}
		List<Role> roles = userDao.getRoles(username);
		List<GrantedAuthority> grantedAuthorities = roles.stream().map(r -> {
			return new SimpleGrantedAuthority(r.getRole());
		}).collect(Collectors.toList());
		return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getUserpwd(),
				grantedAuthorities);
	}

	public UserVo getUserByUsername(String username) {
		User user = userDao.getUser(username);

		if (user != null) {

			List<Role> roles = userDao.getRoles(username);

			Set<String> rls = roles.stream().map(r -> r.getRole()).collect(Collectors.toSet());

			UserVo userVo = new UserVo();
			userVo.setUsername(user.getUsername());
			userVo.setUserpwd(user.getUserpwd());
			userVo.setRoles(rls);

			return userVo;
		}

		return null;
	}

	public void saveUser(UserVo userVo) {
		UserRole user = new UserRole();
		user.setUsername(userVo.getUsername());
		user.setUserpwd(passwordEncoder.encode(userVo.getUserpwd()));
		user.setRoles(userVo.getRoles());
		userDao.saveUser(user);
	}

}

Config File

Create an application.properties file under src/main/resources folder with below content.

This file includes JWT secret key, JWT expiry time and exclusion for null fields in JSON response.

I have also put the MySQL database details in case you are using MySQL database for your application.

For spring boot 3.x.x use the following configuration:

spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/roytuts
spring.datasource.username=root
spring.datasource.password=root

For spring boot 2.x.x use the following configuration:

#datasource
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/roytuts
jdbc.username=root
jdbc.password=root

#secret key - should be encrypted
jwt.secret=secretkey
#3 minutes validity
jwt.token.validity=180000
#ignore null fields in json
spring.jackson.default-property-inclusion=NON_NULL

Exception Classes

I will create our own exception class to wrap the message and throw it.

DisabledUserException

This exception is thrown when user is inactive.

public class DisabledUserException extends RuntimeException {
	private static final long serialVersionUID = 1L;
	public DisabledUserException(String msg) {
		super(msg);
	}
}

InvalidUserCredentialsException

This exception is thrown when user credentials are invalid.

public class InvalidUserCredentialsException extends RuntimeException {
	private static final long serialVersionUID = 1L;
	public InvalidUserCredentialsException(String msg) {
		super(msg);
	}
}

JwtTokenMalformedException

This exception is thrown when JWT is malformed or tempered in some way.

public class JwtTokenMalformedException extends AuthenticationException {
	private static final long serialVersionUID = 1L;
	public JwtTokenMalformedException(String msg) {
		super(msg);
	}
}

JwtTokenMissingException

This exception is thrown when JWT is not found in the HTTP header.

public class JwtTokenMissingException extends AuthenticationException {
	private static final long serialVersionUID = 1L;
	public JwtTokenMissingException(String msg) {
		super(msg);
	}
}

JWT (JSON Web Token)

Now you will see how to generate token using generateToken() method. This method takes UserVo object and generates token based on username and roles.

I will also see how to get the user information back from JWT using getUser() method. This method takes generated JWT as a parameter.

I will create an optional method validateToken() to validate token. This method takes generated JWT as a parameter and throws appropriate exception based on issues in token.

Why validateToken() is optional?

If you do not check the token expiry then also application will throw exception because it is automatically taken care by JWT API.

For spring boot 3.x.x use the following class:

@Component
public class JwtUtil {

	public UserVo getUser(final String token) {
		try {
			Claims body = Jwts.parserBuilder().setSigningKey(key()).build().parseClaimsJws(token).getBody();

			UserVo user = new UserVo();
			user.setUsername(body.getSubject());

			Set<String> roles = Arrays.asList(body.get("roles").toString().split(",")).stream().map(r -> new String(r))
					.collect(Collectors.toSet());
			user.setRoles(roles);

			return user;
		} catch (Exception e) {
			System.out.println(e.getMessage() + " => " + e);
		}
		return null;
	}

	public String generateToken(UserVo u) {
		Claims claims = Jwts.claims().setSubject(u.getUsername());
		claims.put("roles", u.getRoles());
		long nowMillis = System.currentTimeMillis();
		long expMillis = nowMillis + 180000;
		Date exp = new Date(expMillis);
		return Jwts.builder().setClaims(claims).setIssuedAt(new Date(nowMillis)).setExpiration(exp)
				.signWith(key(), SignatureAlgorithm.HS256).compact();
	}

	public boolean validateToken(final String token) {
		try {
			Jwts.parserBuilder().setSigningKey(key()).build().parse(token);
		} catch (SignatureException ex) {
			return false;
		}

		return true;
	}

	private Key key() {
		return Keys.hmacShaKeyFor(Decoders.BASE64.decode("jwtsecretkeyshouldbelongenoughatleast256bits"));
	}

}

For spring boot 2.x.x use the following class:

@Component
public class JwtUtil {
	@Value("${jwt.secret}")
	private String jwtSecret;
	@Value("${jwt.token.validity}")
	private long tokenValidity;
	public UserVo getUser(final String token) {
		try {
			Claims body = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody();
			UserVo user = new UserVo();
			user.setUsername(body.getSubject());
			Set<String> roles = Arrays.asList(body.get("roles").toString().split(",")).stream().map(r -> new String(r))
					.collect(Collectors.toSet());
			user.setRoles(roles);
			return user;
		} catch (Exception e) {
			System.out.println(e.getMessage() + " => " + e);
		}
		return null;
	}
	public String generateToken(UserVo u) {
		Claims claims = Jwts.claims().setSubject(u.getUsername());
		claims.put("roles", u.getRoles());
		long nowMillis = System.currentTimeMillis();
		long expMillis = nowMillis + tokenValidity;
		Date exp = new Date(expMillis);
		return Jwts.builder().setClaims(claims).setIssuedAt(new Date(nowMillis)).setExpiration(exp)
				.signWith(SignatureAlgorithm.HS512, jwtSecret).compact();
	}
	public void validateToken(final String token) {
		try {
			Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
		} catch (SignatureException ex) {
			throw new JwtTokenMalformedException("Invalid JWT signature");
		} catch (MalformedJwtException ex) {
			throw new JwtTokenMalformedException("Invalid JWT token");
		} catch (ExpiredJwtException ex) {
			throw new JwtTokenMalformedException("Expired JWT token");
		} catch (UnsupportedJwtException ex) {
			throw new JwtTokenMalformedException("Unsupported JWT token");
		} catch (IllegalArgumentException ex) {
			throw new JwtTokenMissingException("JWT claims string is empty.");
		}
	}
}

Now you will look into Security part of the application.

Authentication Entry Point

Instead of triggering the authentication process by redirecting to a login page when a client requests a secured resource, the REST server authenticates all requests using the data available in the request itself, the JWT token in this case. If such an authentication fails, redirection makes no sense. The REST API simply sends an HTTP code 401 Unauthorized response and clients should know what to do.

This class just returns HTTP code 401 Unauthorized when authentication fails, overriding default Spring’s redirecting.

@Component
public class ApiAuthenticationEntryPoint implements AuthenticationEntryPoint {
	@Override
	public void commence(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException authException) throws IOException, ServletException {
		response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
	}
}

Extending OncePerRequestFilter

Filter base class that aims to guarantee a single execution per request dispatch, on any servlet container. As of Servlet 3.0, a filter may be invoked as part of a REQUEST or ASYNC dispatches that occur in separate threads. You may read more on OncePerRequestFilter.

A common use case is in Spring Security, where authentication and access control functionality is typically implemented as a filter that sits in front of the main application Servlet. When a request is dispatched using a request dispatcher, it has to go through the filter chain again (or possibly a different one) before it gets to the Servlet that is going to deal with it. The problem is that some of the security filter actions should only be performed once for a request. Hence the need for OncePerRequestFilter.

You must override the method doFilterInternal() method in order to extend the functionalities of this filter.

I check for Authorization with Bearer token (JWT) in the HTTP header and if JWT presents then I extract it for further operations.

I optionally validate the token before extracting user information from this token.

Further I load user details from database and set authentication into SecurityContext.

For spring boot 3.x.x use the following class:

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

	@Autowired
	private JwtUtil jwtUtil;

	@Autowired
	private UserAuthService userAuthService;

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		String header = request.getHeader(HttpHeaders.AUTHORIZATION);

		if (header == null || !header.startsWith("Bearer")) {
			filterChain.doFilter(request, response);
			return;
		}

		final String token = header.split(" ")[1].trim();// header.substring(7);

		if (!jwtUtil.validateToken(token)) {
			filterChain.doFilter(request, response);
			return;
		}

		UserVo userVo = jwtUtil.getUser(token);

		UserDetails userDetails = userAuthService.loadUserByUsername(userVo.getUsername());

		UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
				userDetails, null, userDetails.getAuthorities());

		usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

		SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);

		filterChain.doFilter(request, response);
	}
}

For spring boot 2.x.x use the following class:

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
	@Autowired
	private JwtUtil jwtUtil;
	@Autowired
	private UserAuthService userAuthService;
	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		String header = request.getHeader("Authorization");
		if (header == null || !header.startsWith("Bearer")) {
			throw new JwtTokenMissingException("No JWT token found in the request headers");
		}
		String token = header.substring(7);
		// Optional - verification
		jwtUtil.validateToken(token);
		UserVo userVo = jwtUtil.getUser(token);
		UserDetails userDetails = userAuthService.loadUserByUsername(userVo.getUsername());
		UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
				userDetails, null, userDetails.getAuthorities());
		usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
		if (SecurityContextHolder.getContext().getAuthentication() == null) {
			SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
		}
		filterChain.doFilter(request, response);
	}
}

Security Config

Now it’s time to configure Spring Security part.

For spring boot 3.x.x use the following security configuration:

@Configuration
public class ApiSecurityConfig {

	@Autowired
	private PasswordEncoder passwordEncoder;

	@Autowired
	private UserAuthService userAuthService;

	@Autowired
	private JwtAuthenticationFilter jwtAuthenticationFilter;

	@Autowired
	private ApiAuthenticationEntryPoint authenticationEntryPoint;

	@Autowired
	public void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(userAuthService).passwordEncoder(passwordEncoder);
	}

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http.csrf(csrf -> csrf.disable());

		http.authorizeHttpRequests(
				auth -> auth.requestMatchers("/signin", "/signup").permitAll().anyRequest().authenticated());

		http.exceptionHandling(ex -> ex.authenticationEntryPoint(authenticationEntryPoint));

		http.sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

		http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

		return http.build();
	}

	@Bean
	public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration)
			throws Exception {
		return authenticationConfiguration.getAuthenticationManager();
	}

}

The Password Encoder bean is created in a separate class to avoid circular dependency issue.

@Configuration
public class EncoderConfig {

	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}

}

For spring boot 2.x.x use the following security configuration:

I have three main methods – configure(WebSecurity web), configure(AuthenticationManagerBuilder auth) and configure(HttpSecurity http).

configure(HttpSecurity http)

I disable csrf because it’s not a form based authentication. I disable security for endpoints – /signin and /signup. Because these end-points should accessed without any security or authentication.

All other end-points must go through authentication process and I have configured authenticationEntryPoint().

I do not store anything in the session so I have stateless session.

For authentication verification I have configured authentication filter using addFilterBefore().

configure(AuthenticationManagerBuilder auth)

This is required to configure database authentication and role based authorization using Spring JDBC on UserDetailsService.

configure(WebSecurity web)

I had already excluded the /signin and /signup end-points in the configure(HttpSecurity http) but it still requires in WebSecurity.

RegistrationBean

Spring Boot automatically creates beans for us on the fly. As a result JwtAuthenticationFilter class was being automatically put into the filter chain by Spring Boot, and also being included in the Security filter chain when it was declared it in Spring Security config.

Although /signin and /signup end-points were excluded in Spring Security config, that wasn’t enough to stop the filter from happening in the context of Spring Boot itself.

The solution was to configure a bean that explicitly prevents it from being added by Spring Boot.

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ApiSecurityConfig extends WebSecurityConfigurerAdapter {
	@Autowired
	private UserAuthService userAuthService;
	@Autowired
	private JwtAuthenticationFilter jwtAuthenticationFilter;
	@Autowired
	private ApiAuthenticationEntryPoint authenticationEntryPoint;
	@Override
	public void configure(WebSecurity web) throws Exception {
		web.ignoring().antMatchers("/signin", "/signup");
	}
	@Autowired
	public void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(userAuthService).passwordEncoder(passwordEncoder());
	}
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.csrf().disable().authorizeRequests().antMatchers("/signin", "/signup").permitAll().anyRequest()
				.authenticated().and().exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and()
				.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
		http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
	}
	@Bean
	public RegistrationBean jwtAuthFilterRegister(JwtAuthenticationFilter filter) {
		FilterRegistrationBean<JwtAuthenticationFilter> registrationBean = new FilterRegistrationBean<>(filter);
		registrationBean.setEnabled(false);
		return registrationBean;
	}
	@Bean
	@Override
	public AuthenticationManager authenticationManagerBean() throws Exception {
		return super.authenticationManagerBean();
	}
	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
}

REST End-Points

I will create two REST controller classes.

JwtRestController

This REST controller class exposes two end-points – /signup that a user will use to register himself/herself and /signin that a user will login using same credentials used during signup to get the JWT.

If user already exists in the database then I will show a message that user already exists.

@RestController
public class JwtRestController {

	@Autowired
	private JwtUtil jwtUtil;

	@Autowired
	private UserAuthService userAuthService;

	@Autowired
	private AuthenticationManager authenticationManager;

	@PostMapping("/signin")
	public ResponseEntity<JwtResponse> generateJwtToken(@RequestBody JwtRequest jwtRequest) {
		try {
			authenticationManager.authenticate(
					new UsernamePasswordAuthenticationToken(jwtRequest.getUsername(), jwtRequest.getUserpwd()));
		} catch (DisabledException e) {
			throw new DisabledUserException("User Inactive");
		} catch (BadCredentialsException e) {
			throw new InvalidUserCredentialsException("Invalid Credentials");
		}
		UserDetails userDetails = userAuthService.loadUserByUsername(jwtRequest.getUsername());
		String username = userDetails.getUsername();
		String userpwd = userDetails.getPassword();
		Set<String> roles = userDetails.getAuthorities().stream().map(r -> r.getAuthority())
				.collect(Collectors.toSet());
		UserVo user = new UserVo();
		user.setUsername(username);
		user.setUserpwd(userpwd);
		user.setRoles(roles);
		String token = jwtUtil.generateToken(user);
		return new ResponseEntity<JwtResponse>(new JwtResponse(token), HttpStatus.OK);
	}

	@PostMapping("/signup")
	public ResponseEntity<String> signup(@RequestBody UserVo userVo) {
		UserVo u = userAuthService.getUserByUsername(userVo.getUsername());

		if (u == null) {
			userAuthService.saveUser(userVo);
			return new ResponseEntity<String>("User successfully registered", HttpStatus.OK);
		} else {
			return new ResponseEntity<String>("User already exists", HttpStatus.CONFLICT);
		}
	}

}

ApiRestController

This REST controller class exposes few end-points based on user roles and if user has the appropriate role, then user will be able to access the corresponding end-point.

@RestController
public class ApiRestController {
	@PreAuthorize("hasRole('USER')")
	@GetMapping("/greet/user")
	public ResponseEntity<String> greetingUser() {
		return new ResponseEntity<String>("Welcome, you have USER role", HttpStatus.OK);
	}
	@PreAuthorize("hasRole('ADMIN')")
	@GetMapping("/greet/admin")
	public ResponseEntity<String> greetingAdmin() {
		return new ResponseEntity<String>("Welcome, you have ADMIN role", HttpStatus.OK);
	}
	@PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
	@GetMapping("/greet/userOrAdmin")
	public ResponseEntity<String> greetingUserOrAdmin() {
		return new ResponseEntity<String>("Welcome, you have USER and ADMIN role", HttpStatus.OK);
	}
	@PreAuthorize("hasRole('ANONYMOUS')")
	@GetMapping("/greet/anonymous")
	public ResponseEntity<String> greetingAnonymous() {
		return new ResponseEntity<String>("Welcome, you have USER and ADMIN role", HttpStatus.OK);
	}
}

Spring Boot Main Class

Creating a main would be sufficient to deploy our application into the embedded Tomcat server.

This is a great advantage that you just need to let Spring know that it is your Spring Boot Application using @SpringBootApplication and main class.

@SpringBootApplication
public class SpringSecurityApiAuthJwtApp {

	public static void main(String[] args) {
		SpringApplication.run(SpringSecurityApiAuthJwtApp.class, args);
	}

}

Testing the Spring Security JWT Auth Application

Now if you run the above main class, your application will be deployed into embedded Tomcat server on port 8080.

Now test your application using Postman tool or any REST client tool.

Signup

Method – POST

URL – http://localhost:8080/signup

Request Body

{
	"username":"admin",
	"userpwd":"admin",
	"roles":["ROLE_USER", "ROLE_ADMIN"]
}

Response – User successfully registered

Signin

Method – POST

URL – http://localhost:8080/signin

Request Body

{
	"username":"admin",
	"userpwd":"admin"
}

Response

{
    "token": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsInJvbGVzIjpbIlJPTEVfVVNFUiIsIlJPTEVfQURNSU4iXSwiaWF0IjoxNTY1MDg5OTM5LCJleHAiOjE1NjUwOTAxMTl9.jD3dla-3E2ivj2otU1xDaVk1y4pwBVIByve2TELS6dz9mU1CycnYtHwHkJ1gkYAYgZwfm_BZcvBoDuaHfUkCeg"
}

Accessing End-Points

Method – GET

URL – http://localhost:8080/greet/admin

Header

Key – Authorization

Value

Bearer  eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsInJvbGVzIjpbIlJPTEVfVVNFUiIsIlJPTEVfQURNSU4iXSwiaWF0IjoxNTY1MDg5OTM5LCJleHAiOjE1NjUwOTAxMTl9.jD3dla-3E2ivj2otU1xDaVk1y4pwBVIByve2TELS6dz9mU1CycnYtHwHkJ1gkYAYgZwfm_BZcvBoDuaHfUkCeg

Response – Welcome, you have ADMIN role

You can test for all end-points with different roles.

If your token expires then you will get Unauthorized error.

Source Code

Download

2 thoughts on “Spring Security – Authentication and Role Based Authorization using JWT

  1. For me, it is coming to JwtAuthenticationFilter :: doFilterInternal method which is OncePerRequest Filter

    Inside doFilterInternal Method where we are hitting the database for each request.

    Is this expected behavior or it should go into this filter while login only ?

Leave a Reply

Your email address will not be published. Required fields are marked *