0% found this document useful (0 votes)
47 views15 pages

Authentication Authorisation JWT

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
47 views15 pages

Authentication Authorisation JWT

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 15

Controller - receives and handles request after it was filtered by

OncePerRequestFilter.
AuthController handles signup/login requests

– TestController has accessing protected resource methods with role based


validations.

for deep understanding of security :


https://bezkoder.com/spring-boot-jwt-mysql-spring-security-architecture/
Spring Boot Security and JWT tutorial with example
---------------------------------------------------
In this tutorial, we’re gonna build a Spring Boot, Spring Security that supports
JWT working with H2 embedded Database.
Flow for User Login, Registration, Authorization with JWT and HttpOnly Cookie
Spring Boot Rest Api Architecture with Spring Security and JWT
How to configure Spring Security to work with JWT
How to define Data Models and association for Authentication and Authorization
Way to use Spring Data JPA to interact with H2 Database
Overview of Spring Boot Security JWT example
--------------------------------------------
We will build a Spring Boot + Spring Security application with JWT in that:

User can signup new account (registration), or login with username & password.
By User’s role (admin, moderator, user), we authorize the User to access resources.
POST /api/auth/signup signup new account
POST /api/auth/signin login an account
POST /api/auth/signout logout the account
GET /api/test/all retrieve public content
GET /api/test/user access User’s content
GET /api/test/mod access Moderator’s content
GET /api/test/admin access Admin’s content

The database we will use is H2 by configuring project dependency & datasource.

The database we will use is H2 by configuring project dependency & datasource.


Flow of Spring Boot Security JWT example
A legal JWT will be stored in HttpOnly Cookie if Client accesses protected
resources.

You may need to implement Refresh Token:

Spring Boot Architecture with Spring Security


---------------------------------------------

Spring Security

– WebSecurityConfig is the crux of our security implementation. It configures cors,


csrf, session management, rules for protected resources.
We can also extend and customize the default configuration that contains the
elements below.
(WebSecurityConfigurerAdapter is deprecated from Spring 2.7.0, you can check the
source code for update. More details at:
WebSecurityConfigurerAdapter Deprecated in Spring Boot)
UserDetailsService interface has a method to load User by username and returns a
UserDetails object that Spring Security can use for authentication and validation.
– UserDetails contains necessary information (such as: username, password,
authorities) to build an Authentication object.
– UsernamePasswordAuthenticationToken gets {username, password} from login Request,
AuthenticationManager will use it to authenticate a login account.

security: we configure Spring Security & implement Security Objects here.


------------------------------------------------------------------------
WebSecurityConfig
(WebSecurityConfigurerAdapter is deprecated from Spring 2.7.0, you can check
the source code for update. More details at:
WebSecurityConfigurerAdapter Deprecated in Spring Boot)

UserDetailsServiceImpl implements UserDetailsService


UserDetailsImpl implements UserDetails
AuthEntryPointJwt implements AuthenticationEntryPoint
AuthTokenFilter extends OncePerRequestFilter
JwtUtils provides methods for generating, parsing, validating JWT

controllers handle signup/login requests & authorized requests.


----------------------------------------------------------------
AuthController: @PostMapping(‘/signup’), @PostMapping(‘/signin’),
@PostMapping(‘/signout’)
TestController: @GetMapping(‘/api/test/all’), @GetMapping(‘/api/test/[role]’)

<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>

<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>

<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>

<artifactId>spring-boot-starter-web</artifactId>

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

<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>

<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>

<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
Configure Spring Datasource, JPA, App properties
-----------------------------------------------------

spring.h2.console.enabled=true
# default path: h2-console
spring.h2.console.path=/h2-ui

spring.datasource.url=jdbc:h2:file:./testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update

# App Properties
bezkoder.app.jwtCookieName=bezkoder
bezkoder.app.jwtSecret=
======================BezKoder=Spring===========================
bezkoder.app.jwtExpirationMs=86400000

Create the models


users, roles and user_roles

package com.bezkoder.spring.security.login.models;

public enum ERole {


ROLE_USER,
ROLE_MODERATOR,
ROLE_ADMIN
}

package com.bezkoder.spring.security.login.models;

import jakarta.persistence.*;

@Entity
@Table(name = "roles")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;

@Enumerated(EnumType.STRING)
@Column(length = 20)
private ERole name;

public Role() {

}
public Role(ERole name) {
this.name = name;
}

// getters and setters


}

package com.bezkoder.spring.security.login.models;

import java.util.HashSet;
import java.util.Set;

import jakarta.persistence.*;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

@Entity
@Table(name = "users",
uniqueConstraints = {
@UniqueConstraint(columnNames = "username"),
@UniqueConstraint(columnNames = "email")
})
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@NotBlank
@Size(max = 20)
private String username;

@NotBlank
@Size(max = 50)
@Email
private String email;

@NotBlank
@Size(max = 120)
private String password;

@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();

public User() {
}

public User(String username, String email, String password) {


this.username = username;
this.email = email;
this.password = password;
}

// getters and setters


}
Implement Repositories
------------------------

UserRepository
package com.bezkoder.spring.security.login.repository;

import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.bezkoder.spring.security.login.models.User;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);

Boolean existsByUsername(String username);

Boolean existsByEmail(String email);


}

RoleRepository
package com.bezkoder.spring.security.login.repository;

import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.bezkoder.spring.security.login.models.ERole;
import com.bezkoder.spring.security.login.models.Role;

@Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
Optional<Role> findByName(ERole name);
}

Configure Spring Security


--------------------------------
Without WebSecurityConfigurerAdapter

WebSecurityConfig.java

package com.bezkoder.spring.security.login.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import
org.springframework.security.config.annotation.authentication.configuration.Authent
icationConfiguration;
import
org.springframework.security.config.annotation.method.configuration.EnableMethodSec
urity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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.SecurityFilterChain;
import
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilte
r;

import com.bezkoder.spring.security.login.security.jwt.AuthEntryPointJwt;
import com.bezkoder.spring.security.login.security.jwt.AuthTokenFilter;
import com.bezkoder.spring.security.login.security.services.UserDetailsServiceImpl;

@Configuration
@EnableMethodSecurity
//(securedEnabled = true,
//jsr250Enabled = true,
//prePostEnabled = true) // by default
public class WebSecurityConfig {

@Value("${spring.h2.console.path}")
private String h2ConsolePath;

@Autowired
UserDetailsServiceImpl userDetailsService;

@Autowired
private AuthEntryPointJwt unauthorizedHandler;

@Bean
public AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter();
}

@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();

authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());

return authProvider;
}

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

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

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable())
.exceptionHandling(exception ->
exception.authenticationEntryPoint(unauthorizedHandler))
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth ->
auth.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/test/**").permitAll()
.anyRequest().authenticated()
);

// fix H2 database console: Refused to display ' in a frame because it set 'X-
Frame-Options' to 'deny'
http.headers(headers -> headers.frameOptions(frameOption ->
frameOption.sameOrigin()));

http.authenticationProvider(authenticationProvider());

http.addFilterBefore(authenticationJwtTokenFilter(),
UsernamePasswordAuthenticationFilter.class);

return http.build();
}
}

– @EnableWebSecurity allows Spring to find and automatically apply the class to the
global Web Security.

*For Spring Boot 2: @EnableGlobalMethodSecurity provides AOP security on methods.


It enables @PreAuthorize, @PostAuthorize, it also supports JSR-250. You can find
more parameters in configuration in Method Security Expressions.

– @EnableGlobalMethodSecurity is deprecated in Spring Boot 3. You can use


@EnableMethodSecurity instead. For more details, please visit Method Security.

– We override the configure(HttpSecurity http) method from


WebSecurityConfigurerAdapter interface. It tells Spring Security how we configure
CORS and CSRF, when we want to require all users to be authenticated or not, which
filter (AuthTokenFilter) and when we want it to work (filter before
UsernamePasswordAuthenticationFilter), which Exception Handler is chosen
(AuthEntryPointJwt).

– Spring Security will load User details to perform authentication & authorization.
So it has UserDetailsService interface that we need to implement.

– The implementation of UserDetailsService will be used for configuring


DaoAuthenticationProvider by AuthenticationManagerBuilder.userDetailsService()
method.

– We also need a PasswordEncoder for the DaoAuthenticationProvider. If we don’t


specify, it will use plain text.

If the authentication process is successful, we can get User’s information such as


username, password, authorities from an Authentication object.

Authentication authentication =
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(username, password)
);

UserDetails userDetails = (UserDetails) authentication.getPrincipal();


// userDetails.getUsername()
// userDetails.getPassword()
// userDetails.getAuthorities()

If we want to get more data (id, email…), we can create an implementation of this
UserDetails interface.

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

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import com.bezkoder.spring.security.login.models.User;
import com.fasterxml.jackson.annotation.JsonIgnore;

public class UserDetailsImpl implements UserDetails {


private static final long serialVersionUID = 1L;

private Long id;

private String username;

private String email;

@JsonIgnore
private String password;

private Collection<? extends GrantedAuthority> authorities;

public UserDetailsImpl(Long id, String username, String email, String password,


Collection<? extends GrantedAuthority> authorities) {
this.id = id;
this.username = username;
this.email = email;
this.password = password;
this.authorities = authorities;
}

public static UserDetailsImpl build(User user) {


List<GrantedAuthority> authorities = user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority(role.getName().name()))
.collect(Collectors.toList());

return new UserDetailsImpl(


user.getId(),
user.getUsername(),
user.getEmail(),
user.getPassword(),
authorities);
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
public Long getId() {
return id;
}

public String getEmail() {


return email;
}

@Override
public String getPassword() {
return password;
}

@Override
public String getUsername() {
return username;
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
UserDetailsImpl user = (UserDetailsImpl) o;
return Objects.equals(id, user.id);
}
}

Look at the code above, you can notice that we convert Set<Role> into
List<GrantedAuthority>. It is important to work with Spring Security and
Authentication object later.

As I have said before, we need UserDetailsService for getting UserDetails object.


You can look at UserDetailsService interface that has only one method:

public interface UserDetailsService {


UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

So we implement it and override loadUserByUsername() method.

package com.bezkoder.spring.security.login.security.services;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.bezkoder.spring.security.login.models.User;
import com.bezkoder.spring.security.login.repository.UserRepository;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
UserRepository userRepository;

@Override
@Transactional
public UserDetails loadUserByUsername(String username) throws
UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User Not Found with
username: " + username));

return UserDetailsImpl.build(user);
}
}

In the code above, we get full custom User object using UserRepository, then we
build a UserDetails object using static build() method.
Filter the Requests
Let’s define a filter that executes once per request. So we create AuthTokenFilter
class that extends OncePerRequestFilter and override doFilterInternal() method.

Filter the Requests


Let’s define a filter that executes once per request. So we create AuthTokenFilter
class that extends OncePerRequestFilter and override doFilterInternal() method.

package com.bezkoder.spring.security.login.security.jwt;

import java.io.IOException;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import
org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;

import com.bezkoder.spring.security.login.security.services.UserDetailsServiceImpl;

public class AuthTokenFilter extends OncePerRequestFilter {


@Autowired
private JwtUtils jwtUtils;

@Autowired
private UserDetailsServiceImpl userDetailsService;

private static final Logger logger =


LoggerFactory.getLogger(AuthTokenFilter.class);

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
response, FilterChain filterChain)
throws ServletException, IOException {
try {
String jwt = parseJwt(request);
if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
String username = jwtUtils.getUserNameFromJwtToken(jwt);

UserDetails userDetails = userDetailsService.loadUserByUsername(username);

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

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

SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
logger.error("Cannot set user authentication: {}", e);
}

filterChain.doFilter(request, response);
}

private String parseJwt(HttpServletRequest request) {


String jwt = jwtUtils.getJwtFromCookies(request);
return jwt;
}
}

What we do inside doFilterInternal():


– get JWT from the HTTP Cookies
– if the request has JWT, validate it, parse username from it
– from username, get UserDetails to create an Authentication object
– set the current UserDetails in SecurityContext using
setAuthentication(authentication) method.

UserDetails userDetails =
(UserDetails)
SecurityContextHolder.getContext().getAuthentication().getPrincipal();

// userDetails.getUsername()
// userDetails.getPassword()
// userDetails.getAuthorities()

package com.bezkoder.spring.security.login.security.jwt;

import java.security.Key;
import java.util.Date;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseCookie;
import org.springframework.stereotype.Component;
import org.springframework.web.util.WebUtils;

import com.bezkoder.spring.security.login.security.services.UserDetailsImpl;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;

@Component
public class JwtUtils {
private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class);

@Value("${bezkoder.app.jwtSecret}")
private String jwtSecret;

@Value("${bezkoder.app.jwtExpirationMs}")
private int jwtExpirationMs;

@Value("${bezkoder.app.jwtCookieName}")
private String jwtCookie;

public String getJwtFromCookies(HttpServletRequest request) {


Cookie cookie = WebUtils.getCookie(request, jwtCookie);
if (cookie != null) {
return cookie.getValue();
} else {
return null;
}
}

public ResponseCookie generateJwtCookie(UserDetailsImpl userPrincipal) {


String jwt = generateTokenFromUsername(userPrincipal.getUsername());
ResponseCookie cookie = ResponseCookie.from(jwtCookie,
jwt).path("/api").maxAge(24 * 60 * 60).httpOnly(true).build();
return cookie;
}

public ResponseCookie getCleanJwtCookie() {


ResponseCookie cookie = ResponseCookie.from(jwtCookie,
null).path("/api").build();
return cookie;
}

public String getUserNameFromJwtToken(String token) {


return Jwts.parserBuilder().setSigningKey(key()).build()
.parseClaimsJws(token).getBody().getSubject();
}

private Key key() {


return Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtSecret));
}

public boolean validateJwtToken(String authToken) {


try {
Jwts.parserBuilder().setSigningKey(key()).build().parse(authToken);
return true;
} catch (MalformedJwtException e) {
logger.error("Invalid JWT token: {}", e.getMessage());
} catch (ExpiredJwtException e) {
logger.error("JWT token is expired: {}", e.getMessage());
} catch (UnsupportedJwtException e) {
logger.error("JWT token is unsupported: {}", e.getMessage());
} catch (IllegalArgumentException e) {
logger.error("JWT claims string is empty: {}", e.getMessage());
}

return false;
}

public String generateTokenFromUsername(String username) {


return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
.signWith(key(), SignatureAlgorithm.HS256)
.compact();
}
}

Handle Authentication Exception

package com.bezkoder.spring.security.login.security.jwt;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;

@Component
public class AuthEntryPointJwt implements AuthenticationEntryPoint {

private static final Logger logger =


LoggerFactory.getLogger(AuthEntryPointJwt.class);

@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException)
throws IOException, ServletException {
logger.error("Unauthorized error: {}", authException.getMessage());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error: Unauthorized");
}
}

HttpServletResponse.SC_UNAUTHORIZED is the 401 Status code. It indicates that the


request requires HTTP authentication.

If you want to customize the response data, just use an ObjectMapper like following
code:

@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException)
throws IOException, ServletException {
logger.error("Unauthorized error: {}", authException.getMessage());

response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

final Map<String, Object> body = new HashMap<>();


body.put("status", HttpServletResponse.SC_UNAUTHORIZED);
body.put("error", "Unauthorized");
body.put("message", authException.getMessage());
body.put("path", request.getServletPath());

final ObjectMapper mapper = new ObjectMapper();


mapper.writeValue(response.getOutputStream(), body);
}

Define payloads for Authentication Controller


----------------------------------------------

Let me summarize the payloads for our RestAPIs:


– Requests:

LoginRequest: { username, password }


SignupRequest: { username, email, password }
– Responses:

UserInfoResponse: { id, username, email, roles }


MessageResponse: { message }

Create Spring Rest Controllers


-------------------------------
– /api/auth/signup
check existing username/email
create new User (with ROLE_USER if not specifying role)
save User to database using UserRepository

– /api/auth/signin

authenticate { username, pasword }


update SecurityContext using Authentication object
generate JWT
get UserDetails from Authentication object
response contains JWT and UserDetails data

– /api/auth/signout: clear the Cookie.

You might also like