Logo

dev-resources.site

for different kinds of informations.

JWT Token authorisation in spring boot 3 without any external library

Published at
7/26/2024
Categories
Author
Naman Tamrakar
Categories
1 categories in total
open
JWT Token authorisation in spring boot 3 without any external library

In this blog post, we'll walk through a Spring Boot application that sets up a secure environment using JWT authentication. In most of the blogs or tutorials you must have implemented jwt authentication using a 3rd party library, But for this implement we would use spring-oauth2-authorization-server which is provided by spring framework itself. So without wasting much time let's start.

So before starting coding let's head over to start.spring.io to get our project ready. We will mainly add

  1. Spring web (For setting up servlet)
  2. Spring data jdbc and h2 (For database and ORM)
  3. Spring oauth2 authorization server (For JWT authorisation)
  4. Lombok (To reduce boiler plate code)

Our dependencies should look like this:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

Next we will create a protected REST endpoint to test our API.

class HomeController {
    @GetMapping
    String home() {
        return "home";
    }
}

Creating user model

import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;
import org.springframework.security.core.userdetails.UserDetails;

@ToString
@Table("USERS") // using custom table name as user keyword reserved
class User implements UserDetails {
    @Id
    Long id;
    String username;
    String password;
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of();
    }
    @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;
    }
}

Creating user repository

import org.springframework.data.repository.CrudRepository;
interface UserRepository extends CrudRepository<User, Long> {
    Optional<User> findByUsername(String username);
}

Now setting up our token authorisation

For signing our jwt token we will create RSA private/public key pair using openssl tool.

# run these command in resources/certs directory
# create rsa key pair
openssl genrsa -out keypair.pem 2048
# extract public key
openssl rsa -in keypair.pem -pubout -out public.pem
# create private key in PKCS#8 format
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in keypair.pem -out private.pem

Once we have our key in place add the following propertied in application.properties file.

rsa.private-key=classpath:certs/private.pem
rsa.public-key=classpath:certs/public.pem
# for logging add the properties
logging.level.org.springframework.security=TRACE
logging.level.org.springframework.jdbc=TRACE
server.error.include-message=always

Now add this security config

@EnableWebSecurity
@Configuration
class SecurityConfig {
    private @Value("${rsa.private-key}") RSAPrivateKey privateKey;
    private @Value("${rsa.public-key}") RSAPublicKey publicKey;
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                .csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/auth/**").permitAll()
                        .anyRequest().authenticated()
                )
                .oauth2ResourceServer(oauth -> oauth.jwt(Customizer.withDefaults()))
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .build();
    }
    @Bean
    public UserDetailsService userDetailsService(UserRepository userRepository) {
        return username -> userRepository
                .findByUsername(username)
                .orElseThrow(() ->
                        new UsernameNotFoundException("user with username " + username + " not found!!")
                );
    }
    @Bean
    JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withPublicKey(publicKey).build();
    }
    @Bean
    JwtEncoder jwtEncoder() {
        JWK jwk = new RSAKey.Builder(publicKey).privateKey(privateKey).build();
        JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
        return new NimbusJwtEncoder(jwks);
    }
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Bean
    AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }
}

To generate the token for logging in add a token generation service

@RequiredArgsConstructor
@Service
class TokenService {
    private final JwtEncoder encoder;
    public String generateToken(Authentication authentication) {
        Instant now = Instant.now();
        String scope = authentication.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.joining(" "));
        JwtClaimsSet claims = JwtClaimsSet.builder()
                .issuer("self")
                .issuedAt(now)
                .expiresAt(now.plus(1, ChronoUnit.HOURS))
                .subject(authentication.getName())
                .claim("scope", scope)
                .build();
        return this.encoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
    }
}

Adding an endpoint for logging in our controller

@PostMapping("/auth/login")
public String login(@RequestBody Map<String, String> user) {
    log.debug("User login: {}", user);
    Authentication auth = manager.authenticate(
            new UsernamePasswordAuthenticationToken(user.get("username"), user.get("password")));
    String token = tokenService.generateToken(auth);
    log.debug("JWT token: {}", token);
    return token;
}

And to create user for initial startup add this bean

@Bean
ApplicationRunner runner(UserRepository userRepository, PasswordEncoder encoder) {
    return args -> {
        User user = new User();
        user.username = "admin";
        user.password = encoder.encode("admin");
        var savedUser = userRepository.save(user);
        log.info("user: {}", savedUser);
    };
}

Now let's test our API

> curl -X POST http://localhost:8080/auth/login -d '{"username":"admin", "password": "admin"}' -H "Content-type: application/json"

output

eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJzZWxmIiwic3ViIjoiYWRtaW4iLCJleHAiOjE3MjIwMzUyOTksImlhdCI6MTcyMjAzMTY5OSwic2NvcGUiOiIifQ.SwRW7_P4xMp5-aLTcUYZyL9jncTIVHUFswaQoKupwfhaoAWYRpmP5g-Ras1gU2oAJ_saY7jt0N-WiezS977FAgDOYYmdxr2H4SBeg0qNqsSV6fOX9zRELYukdbEnT9GGaAk5u9WXd6JoHa5x_7vqXJ1Uu2ciDNxgRaBVtwoeWawZ53g0EFwjqturuQALWfBkkLafMbkY_dSqAoohmvTlVVGWiYPylGOsIhQ3CjamZVzqq8ma9WHUPx8p8luz7EBdsjm1MQYRrtC3NiH2TJaQqJTQenevZ8_YoKUUk6VddVzAxc0xycnmY-g94zEVJyjdKeOM98AjEE9C4oX5XgGuKA

and now test our protected enpoint with jwt token

> curl http://localhost:8080 -H "Authorization: Bearer eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJzZWxmIiwic3ViIjoiYWRtaW4iLCJleHAiOjE3MjIwMzUyOTksImlhdCI6MTcyMjAzMTY5OSwic2NvcGUiOiIifQ.SwRW7_P4xMp5-aLTcUYZyL9jncTIVHUFswaQoKupwfhaoAWYRpmP5g-Ras1gU2oAJ_saY7jt0N-WiezS977FAgDOYYmdxr2H4SBeg0qNqsSV6fOX9zRELYukdbEnT9GGaAk5u9WXd6JoHa5x_7vqXJ1Uu2ciDNxgRaBVtwoeWawZ53g0EFwjqturuQALWfBkkLafMbkY_dSqAoohmvTlVVGWiYPylGOsIhQ3CjamZVzqq8ma9WHUPx8p8luz7EBdsjm1MQYRrtC3NiH2TJaQqJTQenevZ8_YoKUUk6VddVzAxc0xycnmY-g94zEVJyjdKeOM98AjEE9C4oX5XgGuKA"

output

home

Source code

https://github.com/namantam1/spring-tutorial/tree/jwt-oauth

Conclusion

This code provides a comprehensive example of setting up JWT-based authentication in a Spring Boot application. It includes the creation of users, secure password storage, and JWT token generation. This setup ensures that only authenticated users can access certain endpoints, enhancing the security of your application.

Featured ones: