Securing Spring Boot Microservices with Spring Security

Overview

Basically, our microservices are secured this way :

  1. The user provides his credentials via a public endpoint
  2. If authenticated, the server returns an authentication token (JWT)
  3. The JWT is attached to each subsequent request via an HTTP header: Authorization:Bearer TOKEN

User Microservice

This microservice uses a MySQL database. We begin by creating the Account table:

CREATE TABLE IF NOT EXISTS `metrolab_db_users`.`Account` (
`id` SMALLINT(5) UNSIGNED NOT NULL AUTO_INCREMENT,
`email` VARCHAR(255) NOT NULL,
`password` VARCHAR(255) NOT NULL,
`full_name` VARCHAR(60) NOT NULL,
`security_role` VARCHAR(45) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `User_u1` (`email` ASC))
ENGINE = InnoDB ;
Below is a script to insert some dummy data:

INSERT INTO metrolab_db_users.Account (email, password, full_name, security_role) VALUES
('finance@vedrax.com','$2a$10$.6QjV9sL8OukcnF0aHZxgOCBe7iX29xoJ4dURBx9i8dx8MdhVQpHK','Penchenat Remy','ADMIN');
view raw account_db_data hosted with ❤ by GitHub

The Account entity will be created using JPA:

package com.vedrax.user.domain;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
/**
*
* Represents a user account
*
* @author remypenchenat
*/
@Entity
public class Account implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email;
private String password;
@Column(name = "full_name")
private String fullName;
@Column(name = "security_role")
private String securityRole;
public Account() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public String getSecurityRole() {
return securityRole;
}
public void setSecurityRole(String securityRole) {
this.securityRole = securityRole;
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(this.email)
.append(this.password)
.toHashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (getClass() != obj.getClass()) {
return false;
}
Account object = (Account) obj;
return new EqualsBuilder()
.append(this.email, object.email)
.append(this.password, object.password)
.isEquals();
}
@Override
public String toString() {
return new ReflectionToStringBuilder(this).toString();
}
}


The account repository:

package com.vedrax.user.repository;
import com.vedrax.user.domain.Account;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
*
* The account repository
*
* @author remypenchenat
*/
@Repository
public interface AccountRepository extends JpaRepository<Account, Long> {
/**
* Find account by email
*
* @param email
* @return
*/
Optional<Account> findByEmail(String email);
}


User Principal

We create a class named UserPrincipal which implements UserDetails. In order to be accessible by all microservices, this class is located in our shared module.

package com.vedrax.security;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
/**
*
* @author remypenchenat
*/
public class UserPrincipal implements UserDetails {
private final static String ROLE_PREFIX = "ROLE_";
private final String username;
private final String fullName;
private final String role;
public UserPrincipal(String username, String fullName, String role) {
this.username = username;
this.fullName = fullName;
this.role = role;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> list = new ArrayList<>();
list.add(new SimpleGrantedAuthority(ROLE_PREFIX + role));
return list;
}
@Override
public String getPassword() {
return null;
}
@Override
public String getUsername() {
return username;
}
public String getFullName() {
return fullName;
}
public String getRole() {
return role;
}
@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 String toString() {
return new ReflectionToStringBuilder(this).toString();
}
}
view raw user_principal hosted with ❤ by GitHub

JWT Token Service


This service is responsible for creating expiring JWT. We can also parse a JWT for getting the UserPrincipal. We use the io.jsonwebtoken dependency for that.

package com.vedrax.security;
import static com.vedrax.security.SecurityConstants.ISSUER;
import static com.vedrax.security.SecurityConstants.JWT_SECRET;
import static com.vedrax.utils.DateUtils.addUnitToLocalDateTime;
import static com.vedrax.utils.DateUtils.convertToDateTime;
import static com.vedrax.utils.DateUtils.convertToLocalDateTime;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Optional;
import org.apache.commons.lang3.Validate;
import org.springframework.stereotype.Service;
/**
*
* Service for creating security token (JWT)
*
* @author remypenchenat
*/
@Service
public class TokenServiceImpl implements TokenService {
/**
* Parse the provided JWT
*
* @param token the JWT to be parsed
* @return an optional {@link UserPrincipal}
*/
@Override
public Optional<UserPrincipal> parseToken(String token) {
try {
Claims body = getClaimsWithToken(token);
UserPrincipal userPrincipal = claimsToPrincipal(body);
return Optional.of(userPrincipal);
} catch (JwtException | ClassCastException e) {
return Optional.empty();
}
}
/**
* Get {@link Claims} with the provided JWT
*
* @param token
* @return
*/
private Claims getClaimsWithToken(String token) {
return Jwts.parser()
.setSigningKey(JWT_SECRET)
.requireIssuer(ISSUER)
.parseClaimsJws(token)
.getBody();
}
/**
* Get {@link UserPrincipal} with the provided {@link Claims}
*
* @param body
* @return
*/
private UserPrincipal claimsToPrincipal(Claims body) {
String username = body.getSubject();
String fullName = (String) body.get(UserPrincipal.FULL_NAME);
String role = (String) body.get(UserPrincipal.ROLE);
return new UserPrincipal(username, fullName, role);
}
/**
* Creates an expiring JWT - 24 hours
*
* @param user the {@link UserPrincipal}
* @return
*/
@Override
public String expiring(UserPrincipal user) {
return createToken(user, 86400);//24hours
}
/**
* Creates an expiring JWT
*
* @param user the {@link UserPrincipal}
* @param expiresInSec the expiration in seconds
* @return
*/
private String createToken(final UserPrincipal user, final int expiresInSec) {
Validate.notNull(user, "user should not be null");
Claims claims = setClaims();
setDuration(claims, expiresInSec);
setAttributes(claims, user);
return Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, JWT_SECRET)
.compact();
}
/**
* Set a new @{link Claims}
*
* @return
*/
private Claims setClaims() {
LocalDateTime now = LocalDateTime.now();
return Jwts
.claims()
.setIssuer(ISSUER)
.setIssuedAt(convertToDateTime(now));
}
/**
* Set the expiration time
*
* @param claims the @{link Claims}
* @param expiresInSec the expiration in seconds
*/
private void setDuration(Claims claims, int expiresInSec) {
if (expiresInSec > 0) {
LocalDateTime issuedAt = convertToLocalDateTime(claims.getIssuedAt());
LocalDateTime expiresAt = addUnitToLocalDateTime(issuedAt, expiresInSec, ChronoUnit.SECONDS);
claims.setExpiration(convertToDateTime(expiresAt));
}
}
/**
* Set the attributes of the provided {@link Claims} with the
* {@link UserPrincipal}
*
* @param claims the @{link Claims}
* @param user the {@link UserPrincipal}
*/
private void setAttributes(Claims claims, UserPrincipal user) {
claims.setSubject(user.getUsername());
claims.put(UserPrincipal.FULL_NAME, user.getFullName());
claims.put(UserPrincipal.ROLE, user.getRole());
}
}

Account Service


The account service via the login method logs in a user and returns a JWT. This service is located in the user module.

package com.vedrax.user.service;
import com.vedrax.exception.ApiException;
import com.vedrax.security.TokenService;
import com.vedrax.security.UserPrincipal;
import com.vedrax.user.domain.Account;
import com.vedrax.user.dto.AccountDto;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.apache.commons.lang3.Validate;
import com.vedrax.user.repository.AccountRepository;
/**
*
* An {@link AccountService} implementation for managing user account
*
* @author remypenchenat
*/
@Service
public class AccountServiceImpl implements AccountService {
private final AccountRepository accountRepository;
private final TokenService tokenService;
private final PasswordEncoder passwordEncoder;
@Autowired
public AccountServiceImpl(AccountRepository accountRepository,
TokenService tokenService,
PasswordEncoder passwordEncoder) {
this.accountRepository = accountRepository;
this.tokenService = tokenService;
this.passwordEncoder = passwordEncoder;
}
/**
* Sign up a new user with the provided DTO
*
* @param accountDto
* @return
*/
@Override
public Account register(AccountDto accountDto) {
Validate.notNull(accountDto, "accountDto should be provided");
validateIfNotRegistered(accountDto.getEmail());
Account account = constructAccountWithDto(accountDto);
return accountRepository.save(account);
}
/**
* Throws an {@link ApiException} if the user is already registered
*
* @param email
*/
private void validateIfNotRegistered(String email) {
Validate.notNull(email, "email should be provided");
Optional<Account> userOpt = accountRepository.findByEmail(email);
if (userOpt.isPresent()) {
throw new ApiException("User with email [" + email + "] already registered.");
}
}
/**
* Constructs an account with the provided account DTO
*
* @param accountDto
* @return
*/
private Account constructAccountWithDto(AccountDto accountDto) {
Account account = new Account();
account.setEmail(accountDto.getEmail());
account.setFullName(accountDto.getFullName());
account.setSecurityRole(accountDto.getSecurityRole());
account.setPassword(passwordEncoder.encode(accountDto.getPassword()));
return account;
}
/**
* Gain access to a user with the provided credentials. If the user is
* authenticated returns a security token
*
* @param username
* @param password
* @return The security token if authenticated
*/
@Override
public Optional<String> login(String username, String password) {
Validate.notNull(username, "username should be provided");
Validate.notNull(password, "password should be provided");
return accountRepository
.findByEmail(username)
.filter(user -> passwordEncoder.matches(password, user.getPassword()))
.map(user -> createToken(user));
}
/**
* Create a security token with the provided account
*
* @param account
* @return
*/
private String createToken(Account account) {
UserPrincipal userPrincipal = new UserPrincipal(account.getEmail(),
account.getFullName(), account.getSecurityRole());
return tokenService.expiring(userPrincipal);
}
}

Authentication Filter


The authentication filter is responsible of extracting the JWT from the Authorization header.

package com.vedrax.security;
import static com.vedrax.utils.ServletUtils.getHeader;
import java.io.IOException;
import java.util.Optional;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;
/**
* The authentication filter is responsible of retrieving the security token
* (JWT) from the <code>Authorization</code> header
*
* @author remypenchenat
*/
public class AuthenticationFilter extends AbstractAuthenticationProcessingFilter {
/**
* Filter enables only for a given set of URLs.
*
* @param requiresAuth
*/
public AuthenticationFilter(final RequestMatcher requiresAuth) {
super(requiresAuth);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
String token = safeGetSecurityToken(request);
//The security token will be available both in principal and credentials attributes
Authentication auth = new UsernamePasswordAuthenticationToken(token, token);
return getAuthenticationManager().authenticate(auth);
}
/**
* Retrieves the security token from the <code>Authorization</code> header
* if any, otherwise throws {@link BadCredentialsException}
*
* @param request
* @return
*/
private String safeGetSecurityToken(HttpServletRequest request) {
Optional<String> headerOpt = getHeader(request, SecurityConstants.AUTHORIZATION_HEADER);
return headerOpt
.map(header -> extractSecurityToken(header))
.orElseThrow(() -> new BadCredentialsException("Missing Authentication token"));
}
private String extractSecurityToken(String header) {
//Thrown exception if the token has only bearer or if it does not start with the prefix
if (header.length() <= 7 || !header.startsWith(SecurityConstants.TOKEN_PREFIX)) {
throw new BadCredentialsException("Authorization header is not valid");
}
return header.substring(7);
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
super.successfulAuthentication(request, response, chain, authResult);
chain.doFilter(request, response);
}
}

Authentication Provider

The authentication provider is responsible of validating the JWT. If the token is valid, we return a UserPrincipal otherwise we throw an exception. As you can see we don't access the database at all (our stateless solution!). In order to be accessible by all microservices, this class is also located in our shared module.

package com.vedrax.security;
import java.util.Optional;
import org.apache.commons.lang3.Validate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
/**
*
* @author remypenchenat
*/
@Component
public class AuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private final TokenService tokenService;
@Autowired
public AuthenticationProvider(TokenService tokenService) {
this.tokenService = tokenService;
}
@Override
protected void additionalAuthenticationChecks(UserDetails ud, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
}
/**
* Retrieve authenticated user with the provided security token
*
* @param string
* @param authentication
* @return
* @throws AuthenticationException
*/
@Override
protected UserDetails retrieveUser(String string, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
String token = getSecurityToken(authentication);
return tokenService.parseToken(token)
.orElseThrow(() -> new UsernameNotFoundException("JWT Token [" + token + "] is not valid"));
}
/**
* Extracts security token from {@link UsernamePasswordAuthenticationToken}
*
* @param authentication
* @return
*/
private String getSecurityToken(UsernamePasswordAuthenticationToken authentication) {
Validate.notNull(authentication, "A UsernamePasswordAuthenticationToken must be provided");
Object token = authentication.getCredentials();
return Optional
.ofNullable(token)
.map(String::valueOf)
.orElseThrow(() -> new UsernameNotFoundException("Cannot find security token for [" + token + "]."));
}
}

Security Config

We will configure in the next configuration class all the Spring security staff. In order to be available to all modules, this configuration will be placed in our shared module.

package com.vedrax.security;
import java.util.Objects;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.springframework.http.HttpStatus.FORBIDDEN;
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 static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
/**
*
* The Spring Boot Security configuration
*
* @author remypenchenat
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final RequestMatcher PUBLIC_URLS = new OrRequestMatcher(
new AntPathRequestMatcher("/public/**")
);
private static final RequestMatcher PROTECTED_URLS = new NegatedRequestMatcher(PUBLIC_URLS);
AuthenticationProvider provider;
public SecurityConfig(final AuthenticationProvider provider) {
super();
this.provider = Objects.requireNonNull(provider);
}
@Override
protected void configure(final AuthenticationManagerBuilder auth) {
auth.authenticationProvider(provider);
}
@Override
public void configure(final WebSecurity web) {
web.ignoring().requestMatchers(PUBLIC_URLS);
}
@Override
protected void configure(final HttpSecurity http) throws Exception {
http
// use stateless session
.sessionManagement().sessionCreationPolicy(STATELESS)
.and()
// when you request a protected page and you are not yet authenticated
.exceptionHandling().defaultAuthenticationEntryPointFor(forbiddenEntryPoint(), PROTECTED_URLS)
.and()
// Add our custom JWT authenticate provider
.authenticationProvider(provider)
// Add our custom security filter
.addFilterBefore(authenticationFilter(), AnonymousAuthenticationFilter.class)
// authorization requests config
.authorizeRequests()
// Set Admin URL
.antMatchers("/administrator/**").hasRole("ADMIN")
// Set other Urls
.requestMatchers(PROTECTED_URLS)
.authenticated()
.and()
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable();
}
@Bean
public AuthenticationEntryPoint forbiddenEntryPoint() {
return new HttpStatusEntryPoint(FORBIDDEN);
}
@Bean
public AuthenticationFilter authenticationFilter() throws Exception {
final AuthenticationFilter filter = new AuthenticationFilter(PROTECTED_URLS);
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationSuccessHandler(successHandler());
return filter;
}
@Bean
public SimpleUrlAuthenticationSuccessHandler successHandler() {
final SimpleUrlAuthenticationSuccessHandler successHandler = new SimpleUrlAuthenticationSuccessHandler();
successHandler.setRedirectStrategy(new NoRedirectStrategy());
return successHandler;
}
@Bean
public FilterRegistrationBean disableAutoRegistration(final AuthenticationFilter filter) {
final FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
}

As you can see, all non public endpoints are protected.

Public Controllers

In the user module, we create a controller for logging in a user into the application.

package com.vedrax.user.controller;
import com.vedrax.user.dto.LoginDto;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
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.vedrax.user.service.AccountService;
import org.springframework.security.authentication.BadCredentialsException;
/**
*
* Public Endpoint for user authentication
*
* @author remypenchenat
*/
@RestController
@RequestMapping("/public/auth")
public class AuthenticationController {
private final AccountService accountService;
@Autowired
public AuthenticationController(AccountService accountService) {
this.accountService = accountService;
}
/**
* Sign in a user with the provided credentials
*
* @param loginDto The credentials
* @return Security token if the user is authenticated
*/
@PostMapping("/login")
public String login(@Valid @RequestBody LoginDto loginDto) {
return accountService
.login(loginDto.getUsername(), loginDto.getPassword())
.orElseThrow(() -> new BadCredentialsException("Invalid credentials"));
}
}

Protected Controllers

The authenticated user can be accessed via the AuthenticationPrincipal annotation.

package com.vedrax.user.controller;
import com.vedrax.security.UserPrincipal;
import com.vedrax.user.domain.Account;
import com.vedrax.user.dto.AccountDto;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
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.vedrax.user.service.AccountService;
/**
*
* Endpoints for creating and retrieving user
*
* @author remypenchenat
*/
@RestController
@RequestMapping("/accounts")
public class AccountController {
private final AccountService userService;
@Autowired
public AccountController(AccountService userService) {
this.userService = userService;
}
/**
* Registers a new user
*
* @param accountDto The account data
* @return The created account
*/
@PostMapping()
public Account registerNewAccount(@Valid @RequestBody AccountDto accountDto) {
return userService.register(accountDto);
}
/**
* Get the current authenticated user
*
* @param user The authenticated user
* @return The account information extracted from the security token
*/
@GetMapping("/current")
public UserPrincipal getCurrent(@AuthenticationPrincipal final UserPrincipal user) {
return user;
}
}

Testing

You can test a protected controller this way :

package com.vedrax.user.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.vedrax.security.TokenService;
import com.vedrax.security.UserPrincipal;
import com.vedrax.user.Application;
import com.vedrax.user.domain.Account;
import com.vedrax.user.dto.AccountDto;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.mockito.Mockito.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import com.vedrax.user.service.AccountService;
import com.vedrax.user.util.AccountUtility;
import static org.hamcrest.Matchers.*;
/**
*
* @author remypenchenat
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
@AutoConfigureMockMvc
public class AccountControllerUnitTest {
private final String BASE_URL = "/accounts";
private final String AUTHORIZATION = "Authorization";
@Autowired
private MockMvc mvc;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private TokenService tokenService;
@MockBean
private AccountService accountService;
private String securityToken;
private String validAccountDtoStr;
private String invalidAccountDtoStr;
private UserPrincipal userPrincipal;
@Before
public void setUp() throws Exception {
validAccountDtoStr = mockAccount("e.robert@vedrax.com", "Elodie Robert");
invalidAccountDtoStr = mockAccount("invalid", "Elodie Robert");
//create JWT
userPrincipal = new UserPrincipal("finance@vedrax.com", "Remy Penchenat", "ADMIN");
securityToken = "Bearer " + tokenService.expiring(userPrincipal);
}
private String mockAccount(String email, String fullName) throws Exception {
AccountDto accountDto = AccountUtility.buildAccountDto(email, fullName);
Account account = AccountUtility.getAccount(accountDto);
when(accountService.register(accountDto)).thenReturn(account);
return objectMapper.writeValueAsString(accountDto);
}
@After
public void tearDown() {
validAccountDtoStr = null;
invalidAccountDtoStr = null;
securityToken = null;
}
@Test
public void whenRegistrationIsValid_thenReturnsStatus200() throws Exception {
mvc.perform(post(BASE_URL)
.header(AUTHORIZATION, securityToken)
.contentType(MediaType.APPLICATION_JSON)
.content(validAccountDtoStr))
.andExpect(status().isOk());
}
@Test
public void whenRegistrationHasNoSecurityToken_thenReturnsStatus401() throws Exception {
mvc.perform(post(BASE_URL)
.contentType(MediaType.APPLICATION_JSON)
.content(validAccountDtoStr))
.andExpect(status().isUnauthorized());
}
@Test
public void whenRegistrationIsInvalid_thenReturnsStatus400() throws Exception {
mvc.perform(post(BASE_URL)
.header(AUTHORIZATION, securityToken)
.contentType(MediaType.APPLICATION_JSON)
.content(invalidAccountDtoStr))
.andExpect(status().isBadRequest())
.andReturn();
}
@Test
public void whenGettingCurrentUserWithSecurityToken_thenReturnsStatus200() throws Exception {
mvc.perform(get(BASE_URL + "/current")
.header(AUTHORIZATION, securityToken)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(jsonPath("$.username", is("finance@vedrax.com")));
}
@Test
public void whenGettingCurrentUserWitoutSecurityToken_thenReturnsStatus401() throws Exception {
mvc.perform(get(BASE_URL + "/current"))
.andExpect(status().isUnauthorized());
}
}


Thanks for sharing...



Comments

Popular posts from this blog

Spring JPA : Using Specification with Projection

Chip input using Reactive Form