Introduction

In this post we 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. We will implement Spring Security’s UserDetailsService to load user from database. We will use Spring JDBC API to perform database operations for fetching or saving user and roles into database. We will use H2 in-memory database to build a quick Spring Boot application.

Authentication and Authorization Flow

Here I will tell you how authentication and authorization work in this application that we are 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

Eclipse Neon, Java 1.8, Gradle 5.4.1, Spring Boot 2.1.6, H2 1.4.196

Creating Project

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

Updating Build Script

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

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

apply plugin: 'java'
apply plugin: 'org.springframework.boot'
    
sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
	mavenLocal()
    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")
	runtime("com.h2database:h2:1.4.196")
}

Creating SQL Scripts

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

Creating Table – user

We 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`)
);

Creating Table – user_role

We 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`)
);

Creating Database Config Class

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

We are using H2 in-memory database, you may use other external database for your application.

We don’t need to use username, password, database URL, database name because Spring Boot will use default values for these parameters.

package com.jeejava.spring.security.api.auth.jwt.config;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;

@Configuration
public class DatabaseConfig {

	@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 JdbcTemplate jdbcTemplate() {
		return new JdbcTemplate(dataSource());
	}

}

Creating Model Classes

We 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.

package com.jeejava.spring.security.api.auth.jwt.model;

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.

package com.jeejava.spring.security.api.auth.jwt.model;

public class Role {

	private String role;

	//getter and setter

}

Class – UserRole

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

package com.jeejava.spring.security.api.auth.jwt.model;

import java.util.Set;

public class UserRole {

	private String username;
	private String userpwd;
	private Set<String> roles;

	//getters and setters
}

Creating RowMapper

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

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

package com.jeejava.spring.security.api.auth.jwt.rowmapper;

import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.jdbc.core.RowMapper;

import com.jeejava.spring.security.api.auth.jwt.model.User;

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;
	}

}

Creating DAO Class

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

We autowire JdbcTemplate in the below DAO class.

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

A user may have multiple roles, so we are 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 we save user information into the table using saveUser() method.

package com.jeejava.spring.security.api.auth.jwt.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.stereotype.Repository;

import com.jeejava.spring.security.api.auth.jwt.model.Role;
import com.jeejava.spring.security.api.auth.jwt.model.User;
import com.jeejava.spring.security.api.auth.jwt.model.UserRole;
import com.jeejava.spring.security.api.auth.jwt.rowmapper.UserRowMapper;

@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;
			}
		}));
	}

}

Creating 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.

package com.jeejava.spring.security.api.auth.jwt.vo;

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.

package com.jeejava.spring.security.api.auth.jwt.vo;

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.

package com.jeejava.spring.security.api.auth.jwt.vo;

import java.util.Set;

public class UserVo {

	private String username;
	private String userpwd;
	private Set<String> roles;

	// getters and setters

}

Creating Service Class

We 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.

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

package com.jeejava.spring.security.api.auth.jwt.service;

import java.util.List;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import com.jeejava.spring.security.api.auth.jwt.dao.UserDao;
import com.jeejava.spring.security.api.auth.jwt.model.Role;
import com.jeejava.spring.security.api.auth.jwt.model.User;
import com.jeejava.spring.security.api.auth.jwt.model.UserRole;
import com.jeejava.spring.security.api.auth.jwt.vo.UserVo;

@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 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);
	}

}

Creating 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.

jwt.secret=secretkey
#3 minutes validity
jwt.token.validity=180000
spring.jackson.default-property-inclusion=NON_NULL

Creating Few Exception Classes

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

DisabledUserException

This exception is thrown when user is inactive.

package com.jeejava.spring.security.api.auth.jwt.exception;

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.

package com.jeejava.spring.security.api.auth.jwt.exception;

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.

package com.jeejava.spring.security.api.auth.jwt.exception;

import org.springframework.security.core.AuthenticationException;

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.

package com.jeejava.spring.security.api.auth.jwt.exception;

import org.springframework.security.core.AuthenticationException;

public class JwtTokenMissingException extends AuthenticationException {

	private static final long serialVersionUID = 1L;

	public JwtTokenMissingException(String msg) {
		super(msg);
	}

}

JWT (JSON Web Token)

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

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

We 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.

package com.jeejava.spring.security.api.auth.jwt.util;

import java.util.Arrays;
import java.util.Date;
import java.util.Set;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import com.jeejava.spring.security.api.auth.jwt.exception.JwtTokenMalformedException;
import com.jeejava.spring.security.api.auth.jwt.exception.JwtTokenMissingException;
import com.jeejava.spring.security.api.auth.jwt.vo.UserVo;

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 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 we 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.

package com.jeejava.spring.security.api.auth.jwt.config;

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;

@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.

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

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

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

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

package com.jeejava.spring.security.api.auth.jwt.filter;

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.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import com.jeejava.spring.security.api.auth.jwt.exception.JwtTokenMissingException;
import com.jeejava.spring.security.api.auth.jwt.service.UserAuthService;
import com.jeejava.spring.security.api.auth.jwt.util.JwtUtil;
import com.jeejava.spring.security.api.auth.jwt.vo.UserVo;

@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);
	}

}

Creating Security Config

Now it’s time to configure Spring Security part.

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

configure(HttpSecurity http)

We disable csrf because it’s not a form based authentication. We 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 we have configured authenticationEntryPoint().

We do not store anything in the session so we have stateless session.

For authentication verification we 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)

We 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.

package com.jeejava.spring.security.api.auth.jwt.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.RegistrationBean;
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.builders.WebSecurity;
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.jeejava.spring.security.api.auth.jwt.filter.JwtAuthenticationFilter;
import com.jeejava.spring.security.api.auth.jwt.service.UserAuthService;

@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();
	}

}

Creating REST End-Points

We 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.

package com.jeejava.spring.security.api.auth.jwt.rest.controller;

import java.util.Set;
import java.util.stream.Collectors;

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.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.jeejava.spring.security.api.auth.jwt.exception.DisabledUserException;
import com.jeejava.spring.security.api.auth.jwt.exception.InvalidUserCredentialsException;
import com.jeejava.spring.security.api.auth.jwt.service.UserAuthService;
import com.jeejava.spring.security.api.auth.jwt.util.JwtUtil;
import com.jeejava.spring.security.api.auth.jwt.vo.JwtRequest;
import com.jeejava.spring.security.api.auth.jwt.vo.JwtResponse;
import com.jeejava.spring.security.api.auth.jwt.vo.UserVo;

@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) {
		userAuthService.saveUser(userVo);
		return new ResponseEntity<String>("User successfully registered", HttpStatus.OK);
	}

}

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.

package com.jeejava.spring.security.api.auth.jwt.rest.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@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);
	}

}

Creating 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.

package com.jeejava.spring.security.api.auth.jwt;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(scanBasePackages = "com.jeejava.spring.security.api.auth.jwt")
public class SpringSecurityApiAuthJwtApp {

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

}

Testing the 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.

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

You can download source code.

Thanks for reading.

Tags:

Leave a Reply

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