Implementing authorization and authentication no @RestController.
Tags: java, programming, webdev, springsecurity
Published: 2023-10-09 23:30:52


A reference below all this.

<u>STEPS</u>

**1.** Add dependencies to your pom.xml:

```XML
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-webmvc</artifactId>
	<version>6.0.12</version>
</dependency>	
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<optional>true</optional>
</dependency>
<!--Template-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-mustache</artifactId>
</dependency>
<!--********-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
	<version>3.1.2</version>
</dependency>
<dependency>
	<groupId>org.postgresql</groupId>
	<artifactId>postgresql</artifactId>
	<version>42.6.0</version>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
	<version>3.1.2</version>
</dependency>
<!--Devtools automatically reload the server when saving a change or press 'Ctrl+s'-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-devtools</artifactId>
	<version>3.1.2</version>
</dependency>
<dependency>
	<groupId>io.jsonwebtoken</groupId>
	<artifactId>jjwt-jackson</artifactId>
	<version>0.11.5</version>
	<scope>runtime</scope>
</dependency>
<dependency>
	<groupId>io.jsonwebtoken</groupId>
	<artifactId>jjwt-impl</artifactId>
	<version>0.11.5</version>
	<scope>runtime</scope>
</dependency>
<dependency>
	<groupId>io.jsonwebtoken</groupId>
	<artifactId>jjwt-api</artifactId>
	<version>0.11.5</version>
</dependency>
```
**2.** Create the User model:<br>
Note: 'USER_TABLE' is just an example, you can rename it.
```JAVA
@Entity
@ToString
@Table(name="USER_TABLE", schema = "public")
@Getter
@Setter
public class User implements UserDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long user_id;
    String email;
    String username;
    String password;
    LocalDateTime createdAt;
    LocalDateTime updatedAt;
    @Enumerated(EnumType.STRING)
    private Role user_role;

    public User(){}

    public User (String username, String password) {
        this.username = username;
        this.password = password;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> authorities = user_role.getPermissions().stream()
        .map(permissionEnum -> new SimpleGrantedAuthority(permissionEnum.name()))
        .collect(Collectors.toList());

        return authorities;

/*
Down, others 
methods implemented 
of UserDetails
*/
    }
```

**3.** Create the UserRepository for sourcing the User by username:
```JAVA
public interface UserRepository extends JpaRepository<User, Long>{ //User, Password
    Optional<User> findByUsername(String username);
}
```
**4.** Create two 'Enum': Permission and Role.

- - -
```JAVA
public enum Permission {READ_OVERVIEW;}
```
- - -
```JAVA
@AllArgsConstructor
public enum Role {
    ADMIN(Arrays.asList(Permission.READ_OVERVIEW));

    @Getter
    @Setter
    private List<Permission> permissions;
}
```
- - -

**5.** Before We generate the JWT We have to create an **AuthenticationResponse** class to store the token.
```JAVA
@AllArgsConstructor
@Getter
@Setter
public class AuthenticationResponse {private String JWT;}
```

**6.** At this point, We define the **JWTService** class, which is responsible for generating JSON Web Token (JWT). It are essential for the authentication and authorization process in our application. The class encapsulates the logic to create a JWT model, including the construction of the token's header, payload, and signature.
```JAVA
@Service
public class JWTService {
    
    @Value("${security.jwt.expiration-minutes}")
    private long EXPIRATION_MINUTES;

    @Value("${security.jwt.secret-key}")
    private String SECRET_KEY;

    public String generateToken (User user, Map<String, Object> extraClaims) {

        LocalDateTime emitedTime = LocalDateTime.now();
        LocalDateTime expirationTime = emitedTime.plusMinutes(EXPIRATION_MINUTES);
        Date issuedAt = Date.from(emitedTime.atZone(ZoneId.systemDefault()).toInstant());
        Date expiration = Date.from(expirationTime.atZone(ZoneId.systemDefault()).toInstant());

        return Jwts.builder()
        .setClaims(extraClaims)
        .setSubject(user.getUsername())
        .setIssuedAt(issuedAt)
        .setExpiration(expiration)
        .setHeaderParam(Header.TYPE, Header.JWT_TYPE) //Building the JWT's header.
        .signWith(generateKey(), SignatureAlgorithm.HS256) //Building the JWT's signature.
        .compact();
    }

    private Key generateKey () {
        byte[] secretAsBytes = Decoders.BASE64.decode(SECRET_KEY);
        return Keys.hmacShaKeyFor(secretAsBytes);
    }

    public String extractUsername (String jwt) {
        return extractAllClaims(jwt).getSubject();
    }

    private Claims extractAllClaims(String jwt) {
        return Jwts.parserBuilder().setSigningKey(generateKey()).build()
        .parseClaimsJws(jwt).getBody();
    }
}
```
Note: To customize the expiration time and secret key you can modify the 'application.properties' file.
```txt
//Add these lines into the 'application.properties' file
security.jwt.expiration-minutes=30
security.jwt.secret-key=bWluaW5ldC1zZXJ2aWNlX21pbmRsdW5ueV9waW5lYmVycnljb2Rl
``` 
The **security.jwt.expiration-minutes** property sets the token expiration time in minutes, while **security.jwt.secret-key** holds the encrypted secret key. You can use this [link](https://www.base64decode.org/es/) to encrypt your secret key remember make it before setting it in the configuration file.

**7.** We introduce the **AuthenticationService** class, responsible for authenticating user credentials and enhancing the JWT payload with specific attributes. The _login_ method verifies the provided username and password, authenticates the user, and generates a JWT token. Moreover, it includes custom claims such as the user's role and permissions in the JWT payload.

```JAVA
@Service
public class AuthenticationService {
    
    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private JWTService jwtService;

    public AuthenticationResponse login (User user) {
        User sourceUser = userRepository.findByUsername(user.getUsername()).get();
        
        UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
            user.getUsername(), user.getPassword()
        ); //Authenticate just username and password.
        authenticationManager.authenticate(authToken);
    
        SecurityContextHolder.getContext().setAuthentication(authToken);
        String jwt = jwtService.generateToken(sourceUser, generateExtraClaims(sourceUser));
        
        return new AuthenticationResponse(jwt);
    }

    private Map<String, Object> generateExtraClaims (User user) {
        Map<String, Object> extraClaims = new HashMap<>();
        extraClaims.put("user_role", user.getUser_role().name());
        extraClaims.put("permissions", user.getAuthorities());

        return extraClaims;
    }

}
```

**8.** Create two controllers for the "testing". Each of the **'returns'** to the endpoint is just an example.

- - -
The purpose of this file is to authenticate the User. Additionally, If you can appreciate the **login** method there is a cookie named _token_. This cookie is used to store the JWT on the client side, allowing communication between the client and server. So How can we send the token to the client's storage? For that, We support ourselves with **HttpServletResponse**. Once the JWT is generated through the **AuthenticationService** class, the token is stored in a cookie and sent to the client. Subsequently, redirects the **'overview'** template.

```JAVA
@Controller
@RequestMapping("/restricted")
public class LoginController {

    @Value("${security.jwt.expiration-minutes}")
    private int EXPIRATION_MINUTES;

    @Autowired
    private AuthenticationService authenticationService;

    private AuthenticationResponse jwt;

    @GetMapping("/admin")
    public String login () {
        return "login";
    }

    @PostMapping("/admin/login")
    public String login (
        @RequestParam("username") String username, 
        @RequestParam("password") String password,
        HttpServletResponse response) throws IOException, InterruptedException {
        jwt = authenticationService.login(new AuthenticationRequest(username, password));

        Cookie jwtCookie = new Cookie("token", jwt.getJWT());

        jwtCookie.setMaxAge(EXPIRATION_MINUTES*60);

        response.addCookie(jwtCookie);

        return "redirect:/restricted/admin/overview";
    }
}
```
****

For adding, the value of the variable _EXPIRATION_MINUTES_ is estimated in seconds you can remove and add it directly in the **setMaxAge** function.

Reference about the **storage** section.

![Image Storage-Inspect](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gl0qeprhxtec63x3hb9v.jpg)

- - -
The **OverviewController** contains a crucial method called _logout_. Its finality is to capture the token for then remove it. Setting the **setMaxAge** to 0, the cookie is effectively deleted. Within this method, the server identifies the cookie's name, resets its age to 0, and specifies the path. This ensures the removal of the token once the user prefers to log out.
```JAVA
@Controller
@RequestMapping("/restricted/admin")
public class OverviewController {

    @Autowired
    HttpServletRequest request;

    @Autowired
    HttpServletResponse response;

    @GetMapping("/overview")
    @PreAuthorize("hasAuthority('READ_OVERVIEW')")
    public String overview () {
        return Render.OVERVIEW;
    }

    @PostMapping("/overview/logout")
    public String logout () {
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("token".equals(cookie.getName())) {
                    cookie.setMaxAge(0);
                    cookie.setPath("/restricted/admin");
                    response.addCookie(cookie);
                    break;
                }
            }
        }
        return "redirect:/restricted/admin";
    }
}
```
- - -

**9. **We have three classes called **HttpSecurityConfig**, **SecurityBeansInjector**, and **JWTAuthenticationFilter**.

The class mentioned is responsible for verifying authorization after receiving the token sent by the client. It checks the received token from the client's cookie and authenticates the user based on the extracted username. If the token is valid and contains a username, the user is authenticated and allowed to proceed.

```JAVA
@Component
public class JWTAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JWTService jwtService;

    @Autowired
    private UserRepository userRepository;
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        
        //Save me
        Cookie[] cookies = request.getCookies();
        String token = null;
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("token".equals(cookie.getName())) {
                    token = cookie.getValue();
                    System.out.println("Cookie: "+token.toString());
                    break;
                }
            }
        }

        if (token == null || token.isEmpty()) {
            filterChain.doFilter(request, response);
            return;
        }

        String username = jwtService.extractUsername(token);

        User user = userRepository.findByUsername(username).get();

        UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
            username, null, user.getAuthorities()
        );

        SecurityContextHolder.getContext().setAuthentication(authToken);

        filterChain.doFilter(request, response);

    }
}
```

**SecurityBeansInjector** checks whether a user exists or not. then It is utilized by the AuthenticationManager component, which, in turn, leverages the "authenticate" function in the AuthenticationService class. This class is responsible for configuring crucial security components, namely **AuthenticationProvider** and **AuthenticationManager**. Besides, it interacts with the **UserRepository** to access the database and retrieve user information.

```JAVA
@Component
public class SecurityBeansInjector {
    @Autowired
    private UserRepository userRepository;

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

    @Bean
    public AuthenticationProvider authenticationProvider () {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService());
        provider.setPasswordEncoder(passwordEncoder());
        return provider;
    }

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

    @Bean
    public UserDetailsService userDetailsService () {
        return username -> {
            return userRepository.findByUsername(username)
            .orElseThrow(() -> new RuntimeException("User not found."));
        };
    }
}
```

In this class, We configured the endpoints that require specific permissions. Only two endpoints necessitate permissions. Additionally, We have disabled the Spring Security **CSRF** protection.

```JAVA
@Component
@EnableWebSecurity
@EnableMethodSecurity
public class HttpSecurityConfig {
    
    @Autowired
    private AuthenticationProvider authenticationProvider;

    @Autowired
    private JWTAuthenticationFilter authenticationFilter;

    @Bean
    public SecurityFilterChain securityFilterChain (HttpSecurity httpSecurity) throws Exception {
        httpSecurity
        .csrf(csrfConfig -> csrfConfig.disable())
        .sessionManagement(sessionManagementConfig -> sessionManagementConfig.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .authenticationProvider(authenticationProvider)
        .addFilterBefore(authenticationFilter, UsernamePasswordAuthenticationFilter.class)
        .authorizeHttpRequests(builderRequestMatchers());
        
        return httpSecurity.build();
    }

    private static Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> builderRequestMatchers () {
        return authConfig -> {
            authConfig.requestMatchers(HttpMethod.POST, "/restricted/admin/login").permitAll();
            authConfig.requestMatchers(HttpMethod.GET, "/restricted/admin/overview").hasAuthority(Permission.READ_OVERVIEW.name());
            authConfig.requestMatchers(HttpMethod.POST, "/restricted/admin/overview/logout").hasAuthority(Permission.READ_OVERVIEW.name());

            authConfig.requestMatchers("/error").permitAll();
            authConfig.anyRequest().denyAll();
        };
    }

}
```
Note: AuthenticationProvider is particularly a Spring Security class.

To make the long story short You can find guidance in this [Github repository](https://github.com/PineberryCode/MINI-NETSERVICE)