Skip to content

4. Security

1. HTTPS

Nowadays, we need to add all we can do to secure our applications. We are going to study about tokens in order to authorize and authenticate our request, but we need an extra layer, https.

In this webpage https://tiptopsecurity.com/how-does-https-work-rsa-encryption-explained/ you can find how https works

1.1. Certificate

Firstly, we need to generate certificates, or buy it. We will use keytool tool included with java development kit in order to generate. This command generates a pair (public and private) certificate.

Bash
keytool -genkeypair -alias joange -keyalg RSA -keysize 2048
-storetype PKCS12 -keystore jgce.p12 -validity 3650

After running this command, we must answer about who we are, as follows:

![keytool](./img/keytool.png){width=95%}

1.2. Configure Spring

Once the process has finished, we must add the certificate inside our project. For instance inside /resources/keystore. Finally, we must load the certificate and enable SSL, simply adding this lines on application.properties:

Bash
# The format used for the keystore.
server.ssl.key-store-type=PKCS12
# The path to the keystore containing the certificate
server.ssl.key-store=classpath:keystore/jgce.p12
# The password used to generate the certificate
server.ssl.key-store-password=joangeca
# The alias mapped to the certificate
server.ssl.key-alias=joange

# Use HTTPS instead of HTTP
server.ssl.enabled=true
![HTTPS_Spring_Project](./img/HTTPS_Spring_Project.png){width=95%}

And it is all, when Spring starts we will see that is working with https protocol:

Bash
2023-01-13 08:29:37.267  INFO 83291 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 9091 (https)
![HTTPS_Working](./img/HTTPS_Working.png){width=95%}

and in the request probably browsers will not trust with our certificate (we must add a confidence exception):

![Certificate](./img/Certificate.png){width=95%}

2. Spring Security

Spring Security is an umbrella project that group all mechanisms referent to security. We need to add:

XML
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId> 
</dependency>

and magically:

  • The default configuration is enabled, through a filter, called SpringSecurityFilterChain.
  • A bean of type UserDetailsService is created with a user named user and a random password that is printed by the console.
  • The filter is registered in the servlet container for all requests.

Although you have not configured much, it has many consequences:

  • Requires authentication to interact with our application
  • Generates a default login form.
  • Generates a logout mechanism
  • Protect password storage with BCrypt.
  • Provides against CSRF attacks, Session Fixation, Clickjacking...

We can create a configuration class, with this content:

Java
1
2
3
4
@Configuration
public class SecurityConfig {

}

Note

We will come back to this class to add more configurations. Most interesting is method configure, and creation of several Beans used by another classes.

2.1. Users Data for Authentication and Authorization (Models)

Important

From now, this sample is based from a famous webpage https://www.bezkoder.com. We explain all necessary from the sample who you can view here.

In this section we are going to prepare our application to identify users, with several roles. With these roles, we could grant access or not to several resources.

2.2. User & Roles

To do this, we net to create a User class, to store this information in our database. This user could have a collection of roles. We can do it with a many-to-many relationship.

Java
1
2
3
4
5
public enum ERole {
  ROLE_USER,
  ROLE_MODERATOR,
  ROLE_ADMIN
}

based on this enumeration, we will do a Role class to store the roles that our application will support:

Java
1
2
3
4
5
6
7
@Data
@Entity
@Table(name = "roles")
public class Role {
  private Integer id;
  private ERole name;
}

finally, then a User class as follows. We add new annotations of validation:

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

}

2.3. User & Roles Repository

We need to create our repos about last entities, creating interfaces like we normally create.

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

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

  Boolean existsByUsername(String username);

  Boolean existsByEmail(String email);
}

We also add methods to check User existance by name and email, and methods to find by name, either User and Role

2.4. UserDetails

Spring needs that someone implements UserDetails interface, very important because Spring Security will use a UserDetails. UserDetails contains necessary information to build an Authentication object from DAOs or other source of security data. We create a class, called UserDetailsImpl, who:

  • Must have username and passsord fields, and getters. You must respect names, any changes is prohibited. These methods will be used by authentication classes.
  • Override methods from UserDetails, for instance getAuthorities() and several methods to control if the user is blocked, expired, etc.

Full class:

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


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

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

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

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

}

Notice than:

  • We use private Collection<? extends GrantedAuthority> authorities; to store the authorities (i.e. roles) in format who is understood by Spring Security.
  • Instead of create a constructor, we create a builder(), who recieve a User, extract the information from it and transform the List<Role> into authorities, and then, the builder call to constructor.

Instead of create a User and Role Service, we will create a UserDetailSeriveImpl (who implements Spring's UserDetailService), in order to recover a User from repository, and then returns a UserDetailImpl, as follows:

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

}

2.5. Payloads DTO's

In this section we are going to see the necessary classes who store information to:

  • Signup a new User, to receive information from client and store a new user. This class is SignupRequest
  • Signin a User, to login in our system. This class is LoginRequest
  • JwtResponse, related as response to the signin request. This response will contain a JWT Token, used to Authorize subsequent request. This class is JwtResponse

2.6. SignupRequest

This class contains information who register a new user. Is contains validation annotations.

Java
public class SignupRequest {
  @NotBlank
  @Size(min = 3, max = 20)
  private String username;

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

  private Set<String> role;

  @NotBlank
  @Size(min = 6, max = 40)
  private String password;

  // get and set

To send this information, the json object will be something like:

JSON
1
2
3
4
5
6
{
    "username":"joange",
    "email":"jg.camarenaestruch@edu.gva.es",
    "password":"123456",
    "role":["admin","user"]
}

2.7. LoginRequest

Very easy class:

Java
1
2
3
4
5
6
public class LoginRequest {
    @NotBlank
  private String username;

    @NotBlank
    private String password;

and the json object is something like:

JSON
1
2
3
4
{
    "username":"joange",
    "password":"123456"
}

Note

In this class we could mark fields as requireds, with annotation @NotBlank from javax.validation.constraints.NotBlank. You must add:

XML
1
2
3
4
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>

2.8. JwtResponse

This DTO class is the class that is returned as a login request. It should contain little information about our user and must important think, a JWT token. This token will be used to authorize us, as we will study in next section.

Java
1
2
3
4
5
6
7
public class JwtResponse {
  private String token;
  private String type = "Bearer";
  private Long id;
  private String username;
  private String email;
  private List<String> roles;

Be notice than:

  • Fields on this clase will be populated from User class, like a DTO.
  • We have changed role format, from Role class to String, with a better management in clients.
  • The String token is where the JWT token is stored. In fact a token is a String, as we will show now.

3. Tokens JWT

3.1. What is a token?

JSON Web Tokens (JWT) have been introduced as a method of secure communication between two parties. It was introduced with the RFC 7519 specification by the Internet Engineering Task Force (IETF). Although we can use JWT with any kind of communication method, nowadays, JWT is very popular for handling authentication and authorization over HTTP.

First, you'll need to know some features of HTTP:

  • HTTP is a stateless protocol, which means that an HTTP request does not maintain state. The server is not aware of any previous requests sent by the same client.
  • HTTP requests should be standalone. They must include information about previous requests that the user made in the same request.

There are a few ways to do this, but the most popular way is to set a session_id, which is a reference to the user's information:

  • The server will store this session ID in memory or in a database. The client will send each request with this session ID.
  • The server can then obtain information about the client using this reference.
  • Normally, this session ID is sent to the user as a cookie.

Here is the diagram of how session-based authentication works.

![authentication-and-authorization-in-expressjs-using-jwt-1](./img/authentication-and-authorization-in-expressjs-using-jwt-1.png){width=95%}

On the other hand, with JWT, when the client sends an authentication request to the server, it will send a token (token) JSON to the client, which includes all the information about the user along with the response.

The client will submit this token with all subsequent requests. Therefore, the server will not have to store any information about the session. But there is a problem with this approach. Anyone can send a fake request with a fake JSON token and pretend to be someone they're not.

For example, let's say that after authentication, the server returns a JSON object to the client with the username and expiration time. So, since the JSON object is readable, anyone can edit this information and submit a request for it. The problem is that there is no way to validate this request.

This is where the witness signature comes in. So instead of just sending a normal JSON token, the server will send a signed token, which can verify that the information doesn't change.

![authentication-and-authorization-in-expressjs-using-jwt-2](./img/authentication-and-authorization-in-expressjs-using-jwt-2.png){width=95%}

3.2. Structure of JWT

Let's talk about the structure of a JWT through a sample token:

![authentication-and-authorization-in-expressjs-using-jwt-3](./img/authentication-and-authorization-in-expressjs-using-jwt-3.png){width=95%}

As you can see, there are three sections to this JWT, each separated by a dot.

Note

Base64 encoding is a way to ensure that the data is not corrupted, as they do not compress or encrypt the data, but simply encode it in a way that most systems can understand. You can read any Base64 encoded text simply by decoding them.

First section of the JWT is a header, which is a Base64-encoded string. If you decode the header, it would look something like this:

JSON
1
2
3
4
{
     "alg":"HS256"
     "type":"JWT"
}

The header section contains the hash algorithm, which was used to generate the token's sign and type.

The second section is the payload containing the JSON object that was sent back to the user. Since it is only Base64 encoded, anyone can easily decode it. It is mandatory not to include sensitive data in JWTs, such as passwords or personally identifiable information.

Typically, the body of the JWT will look something like this, although it doesn't necessarily apply:

JSON
1
2
3
4
5
{
   "sub": "1234567890",
   "name": "John Doe",
   "iat": 1516239022
}

Note

Most of the time, the sub property will contain the user ID, the iat (issued at) property, abbreviated as issued at, is the token's issuance timestamp. You may also see some common properties, such as eat or exp, which is the token's expiration time.

All these properties are the claims of the token, the information.

The final section is the token signature. This is generated by hashing the string created with previous two sections and a secret password, using the algorithm mentioned in the header section.

You can visit https://www.javainuse.com/jwtgenerator and https://jwt.io to generate tokens and test with several data, secrets and hashes.

3.3. JWT Library class

We must update our pom.xml with dependencies:

XML
<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt-api</artifactId>
  <version>0.10.7</version>
</dependency>
<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt-impl</artifactId>
  <version>0.10.7</version>
  <scope>runtime</scope>
</dependency>
<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt-jackson</artifactId>
  <version>0.10.7</version>
  <scope>runtime</scope>
</dependency>

This class is who generate and checks token integrity. Let's go to show this class and what elements it needs. This class will be a library class with methods to create token, validate it and extract information from it. We will find this class with names like JWTUtils or JWTTokenProvider. This class skeleton is like follows:

  • Loading or definition of token constants
  • Method to generate JWT from a Authentication object
  • Methods to get information from the token
  • Method to validate token (signature)
Java
1
2
3
4
5
6
7
8
9
// constants and secrets

  public String generateJwtToken(Authentication authentication);

  public boolean validateJwtToken(String authToken);

  public String getUserNameFromJwtToken(String token);

  // another methods.

3.4. Token generation

Let's go to see each method. Firt, we find an Authentication object. We need to now that this object represents another token (not our JWT) with the credentials of the object we want to identify.

Java
public String generateJwtToken(Authentication authentication) {

    UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal();

    return Jwts.builder()
        .setSubject((userPrincipal.getUsername()))
        .setIssuedAt(new Date())
        .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
        .signWith(SignatureAlgorithm.HS512, jwtSecret)
        .compact();
  }

We see that:

  • We recieve an Authentication objects, whom we have saved a UserDetailsImpl inside it. As all user details implementations, we could get username, and we use it to set the token subject.
  • We set IAT tho the current time stamp.
  • We establish the expiration time.
  • We set the cipher algorithm and the secret word, and the token is ready to...
  • the compact() method creates and transform the token to String

Note

The jwtSecret is the password we nedd. It is a good idea to store it inside application.properties and recover it to a variable, as jwtExpiration. We could use @Value annotation:

Java
1
2
3
4
5
  @Value("${app.jwtSecret}")
  private String jwtSecret;

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

3.5. Token validation

Very easy method:

Java
public boolean validateJwtToken(String authToken) {
    try {
      Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
      return true;
    } catch (SignatureException e) {
      logger.error("Invalid JWT signature: {}", e.getMessage());
    } 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());
    }

In this method we check if it is a valida token. We use parseClaimsJws(String token), after assign secret password to get the claims. If exists any problem with the token integrity, could appear a Exception. We get the claims inside a try-catch block and inform if something happens. We will return true if no Exception was catched.

Note

Remember that the claims are the content of the token payload. We do not need in this method the claims, only check if all is good

4. Authentication Controller

Now we also know how to create tokens and the User structure in our database, is time to expose our path in order to register and login users. And so, only two methods are mandatory. The class could be something like:

Java
1
2
3
4
5
6
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/auth")
public class AuthController {
    ...
}
  • @CrossOrigin Annotation for permitting cross-origin requests on specific handler classes and/or handler methods. Processed if an appropriate HandlerMapping is configured. Cross-Origin Resource Sharing (CORS) is a security concept that allows restricting the resources implemented in web browsers. It prevents the JavaScript code producing or consuming the requests against different origin. For example, your web application is running on 8080 port and by using JavaScript you are trying to consume RESTful web services from 9090 port. Under such situations, you will face the Cross-Origin Resource Sharing security issue on your web browsers.
  • @RequestMapping("/api/auth") tells that all controllers are inside /api/auth path.

Let's go to see them, but before studying methods, this class has these required variables:

  • @Autowired AuthenticationManager authenticationManager; → used to create an Authentication token, used by Spring security context and by token generator.
  • @Autowired UserRepository userRepository; → used to access and save users.
  • @Autowired RoleRepository roleRepository; → to check if the roles that come in the request are valids.
  • @Autowired PasswordEncoder encoder; → used to encrypt user password
  • @Autowired JwtUtils jwtUtils; → used to create JWT tokens.

Now that we have presented the actors, let's go to the function

4.1. Signup (new user)

The method in charge to create new users, will receive a SignupRequest with this data:

JSON
1
2
3
4
5
6
{
    "username":"joange",
    "email":"jg.camarenaestruch@edu.gva.es",
    "password":"123456",
    "role":["admin","user"]
}

the method body is following, and let we see by blocks:

Java
@PostMapping("/signup")
  public ResponseEntity<?> registerUser(@Valid @RequestBody SignupRequest signUpRequest) {
    if (userRepository.existsByUsername(signUpRequest.getUsername())) {
      return ResponseEntity
          .badRequest()
          .body(new MessageResponse("Error: Username is already taken!"));
    }

    if (userRepository.existsByEmail(signUpRequest.getEmail())) {
      return ResponseEntity
          .badRequest()
          .body(new MessageResponse("Error: Email is already in use!"));
    }
    // Create new user's account
    User user = new User(signUpRequest.getUsername(), 
               signUpRequest.getEmail(),
               encoder.encode(signUpRequest.getPassword()));

In this first part:

  • We check if any user exists with same username or email, asking our repository. If any error appears, we will return a ResponseEntity as a bad request with a description message.
  • Finally, we create a ner User with username, email and an encrypted password.

Next block is responsible to get the roles (stored in a String's JSONArray) and transform to a Set<Role>.

Java
    Set<String> strRoles = signUpRequest.getRole();
    Set<Role> roles = new HashSet<>();

    if (strRoles == null) {
      Role userRole = roleRepository.findByName(ERole.ROLE_USER)
          .orElseThrow(() -> new RuntimeException("Error: Role is not found."));
      roles.add(userRole);
    } else {
      strRoles.forEach(role -> {
        switch (role) {
        case "admin":
          Role adminRole = roleRepository.findByName(ERole.ROLE_ADMIN)
              .orElseThrow(() -> new RuntimeException("Error: Role is not found."));
          roles.add(adminRole);

          break;
        case "mod":
          Role modRole = roleRepository.findByName(ERole.ROLE_MODERATOR)
              .orElseThrow(() -> new RuntimeException("Error: Role is not found."));
          roles.add(modRole);

          break;
        default:
          Role userRole = roleRepository.findByName(ERole.ROLE_USER)
              .orElseThrow(() -> new RuntimeException("Error: Role is not found."));
          roles.add(userRole);
        }
      });
    }

    user.setRoles(roles);

Let's go to see:

  • First we check if the role set is empty. If true, we set a new Role with ERole.ROLE_USER by default.
  • Otherwise, we must loop over all roles we get, checking each role in database and creating as appropiate Role object.

Finally, we set the Set<Role> to the user created in first block, and in the third block we only need to store new user with UserRepositoy and send an ok response to client.

Java
1
2
3
4
    userRepository.save(user);

    return ResponseEntity.ok(new MessageResponse("User registered successfully!"));
  }

4.2. Signin (register or signin)

Note

In spanish:

  • Signin: registrarse, acceder al login.
  • Signup: inscribirse, darse de alta.

This controller is used to create login a user in our app. As we have studied, the DTO that we recieve in the HTTP_POST is LoginRequest class, like this:

JSON
1
2
3
4
{
    "username":"joange",
    "password":"123456"
}

And the method in charge is:

Java
@PostMapping("/signin")
  public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) {

    Authentication authentication = authenticationManager.authenticate(
        new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));

    SecurityContextHolder.getContext().setAuthentication(authentication);
    String jwt = jwtUtils.generateJwtToken(authentication);

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

    List<String> roles = userDetails.getAuthorities().stream()
        .map(item -> item.getAuthority())
        .collect(Collectors.toList());

    return ResponseEntity.ok(new JwtResponse(jwt, 
                         userDetails.getId(), 
                         userDetails.getUsername(), 
                         userDetails.getEmail(), 
                         roles));
  }

Take into account:

  • @Valid annotation check that json object match with LoginRequest class (take a look to this attribute class annotations).
  • We create an Authentication token (this is not the JWT Token !!!) with username and password recieved. Password is not encripted yet.
  • Last Authentication token is setted to the SecurityContextHolder, and object that contains a minimum security in a thread level. In this step is when Spring security subsystem works, asking UserDetailService for a user with this username and password in our database, and it is stored in a UserDetails Bean in memory. If any credential is wrong, it will throw an Exception, that itwill be managed by our system (we will study this later).
  • With this token (authentication) we generate our token. Remember that inside jwtUtils, we only get username in order to generate our token subject.
  • We get the UserDetails object with getPrincipal() method, and
  • Transform the List of authorities into a List of String (have you studied how to map lists? And filter? And reduce?)
  • Finally, we create and return a ResponseEntity, with http_Status ok, with a JwtResponse inside it. Token plus attributes. A sample of JWTrespone is:
JSON
{
    "id": 1,
    "username": "joange",
    "email": "joange.sales@edu.gva.es",
    "roles": [
        "ROLE_USER",
        "ROLE_ADMIN"
    ],
    "accessToken": "eyJhbGciOiJIUzUxMiJ9.
    eyJzdWIiOiJqb2FuZ2UiLCJpYXQiOjE2NzM2MjY1OTYsImV4cCI6MTY3MzcxMjk5Nn0.
    6yMcAYYvHsQ4XKmmT6tr0PmkpJKfPusxnMVHDmIl4WJQ_KtaY08vbt27KdvJHkWCZPO
    4dA2a2HtnAq13vMKAPw",
    "tokenType": "Bearer"
}

Note

Last token has extra newlines in order to avoid exit out of paper width, is one full String

if the username is not found on database the following response is created by our system:

JSON
1
2
3
4
5
6
{
    "path": "/api/auth/signin",
    "error": "Unauthorized",
    "message": "Bad credentials",
    "status": 401
}

5. Tokens working

Now that user registration and login are implemented, let's ask a question, What do the client application does with the JWTResponse he has received afetr login process? User data is normally used to interface (full name, avatar, etc.). But what happens with tokens?

The answer, as we will study later is store it, and then send to the server in each request as Authorization and Authentication method.

5.1. Sending tokens

To send a token, we need to attach it in the Header section, creating an Authorization parameter with value Bearer token_recieved, as you can see on this Postman screenshot:

![Send_JWT](./img/Send_JWT.png){width=95%}

Important

Remember: word Bearer plus white space plus the whole token recieved (as String)

Ok thus the client application will send us the token through the request, but, how does our server to get and check the token. The answer is than we have to say it in a filter way.

6. Security Config

The class that configure security is WebSecurityConfig. Let's go to explain it by blocks again. This class is composed by a set of Beans who will be used in all project (remember code injection). We will explain only necessary.

Java
1
2
3
4
5
6
@Configuration
@EnableGlobalMethodSecurity(
    prePostEnabled = true)
public class WebSecurityConfig { 

}

This @Configuration tells Spring to load this class. We say that Spring allow to pre and post filters annotations (let's study later)

Java
  @Autowired
  UserDetailsServiceImpl userDetailsService;

  @Autowired
  private AuthEntryPointJwt unauthorizedHandler;

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

These beans will be explained later. In a few words are responsible for manage errors as Exception handler and how to apply filter chains to authenticate users.

Java
1
2
3
4
5
6
7
8
9
  @Bean
  public DaoAuthenticationProvider authenticationProvider() {
      DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();

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

      return authProvider;
  }

This Bean use the UserDetailService and PasswordEncoder to do the authentication process. This mean access the database and check user and password (with the same encoder we use to store users). Next Beans create AuthenticationManager and encoder.

Java
1
2
3
4
5
6
7
8
9
  @Bean
  public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
    return authConfig.getAuthenticationManager();
  }

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

And finally one of the most important (and hard to understand) configuration, the security filter chain. Filter chain are code that we put in the middle between client and server. These filters intercepts the request, analyze it and then, dependeding the result of the filter, simple pass the control to the server or send a response to the client. This filter could change the request, adding or removing information that will be used by the server.

Java
  @Bean
  public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.cors().and().csrf().disable()
        .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
        .authorizeRequests().antMatchers("/api/auth/**").permitAll()
        .antMatchers("/api/test/**").permitAll()
        .anyRequest().authenticated();

    http.authenticationProvider(authenticationProvider());

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

    return http.build();
  }

Take notice that we:

  • Enable CORS (Cross-Origin Requests) and disble CSRF (Cross Site Request Forgery)
  • Set the authenticationEntryPoint
  • Allow access to all paths in /api/auth/**
  • Allow access to all paths in /api/auth/**
  • Any onther request will need to be authenticated

Finaly we add the before filter and the authenticationprovider.

In a relaxed view, to understand it, filters are combined with anotations that where and when we have to check Authentication and Authorization.

![FilterChain](./img/FilterChain.png){width=95%}

6.1. AuthTokenFilter

This class contains the process of the token received from clients. It must implement OncePerRequestFilter and override doFilterInternal().

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

In our word this method will be executed when a request is done. and:

  • Extract token from the request, returning only relevant information (removes Bearer).
  • The check that the token is valid, and:
  • Extract the username (is in the token's payload) and
  • Get that UserDetails and create a UsernamePasswordAuthenticationToken to be injected throw rest of the request-response, obviously with authorities.
  • Finally, it continues with next filter, if exists, calling filterChain.doFilter(req,res). If not filter were, then the dispatcher pass control to the controller requested.

6.2. AuthEntryPoint

This class contains code that will be executed when an Exception appears. Then it creates a generic body into the response and create to set an accurate answer to client.

7. Controller Authorization

We only rest to say what request need to be Authorized. Remember that with other classes we prepare the Authentication, Who are you?. Now we need to ask What can you do?

As we mark in our filterChain we addFilterBefore, to check roles. But, where must we say the roles who catch each request? The answer is easy: the controllers. Let's take a look a test controller with Authorization filter:

Java
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/test")
public class TestController {
  @GetMapping("/all")
  public String allAccess() {
    return "Public Content.";
  }

  @GetMapping("/user")
  @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')")
  public String userAccess() {
    return "User Content.";
  }

  @GetMapping("/mod")
  @PreAuthorize("hasRole('MODERATOR')")
  public String moderatorAccess() {
    return "Moderator Board.";
  }

  @GetMapping("/admin")
  @PreAuthorize("hasRole('ADMIN')")
  public String adminAccess() {
    return "Admin Board.";
  }
}
If:

  • No annotation appears: all roles can ask for this request
  • @PreAuthorize("hasRole('role')") → This annotation tells the role who is authorized to get this request. You can combine more than one role with or

8. Exercise. How to use this project?

You probably have a API rest unsecured. Now you have the task to merge your API Rest with this secure project, in order to secure and authorize only registered users. It's a hard work, but the results will be very grateful.