Skip to content
Snippets Groups Projects
Commit 9baf35da authored by Henrik's avatar Henrik
Browse files

Merge branch 'feat/jwt-token-system' into 'master'

feat/jwt token system

See merge request !3
parents b165dd6b 29c75bd5
No related branches found
No related tags found
1 merge request!3feat/jwt token system
Pipeline #273410 failed
package no.ntnu.idi.stud.savingsapp.model;
public enum Role {
USER,
ADMIN
}
package no.ntnu.idi.stud.savingsapp.model;
import jakarta.annotation.Nonnull;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "user")
public class User implements UserDetails{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NonNull
@Column(name = "first_name", nullable = false)
private String firstName;
@NonNull
@Column(name = "last_name", nullable = false)
private String lastName;
@NonNull
@Column(name = "email", nullable = false)
private String email;
@NonNull
@Column(name = "password", nullable = false)
private String password;
@NonNull
private Date createdAt;
@Nonnull
@Enumerated(EnumType.STRING)
private Role role;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority(role.name()));
}
@Override
public String getUsername() {
return this.email;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
package no.ntnu.idi.stud.savingsapp.properties;
import org.springframework.stereotype.Component;
/**
* Configuration properties for token generation.
*/
@Component
public final class TokenProperties {
/**
* The secret key used for token generation.
*/
public static final String SECRET = "topsecretkey";
/**
* The duration of the token validity in minutes.
*/
public static final int DURATION = 30;
}
package no.ntnu.idi.stud.savingsapp.security;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* Represents the identity of an authenticated user.
*/
@Data
@AllArgsConstructor
public class AuthIdentity {
/**
* The ID of the authenticated user.
*/
private long id;
/**
* The role of the authenticated user.
*/
private String role;
}
package no.ntnu.idi.stud.savingsapp.security;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import no.ntnu.idi.stud.savingsapp.model.Role;
import no.ntnu.idi.stud.savingsapp.properties.TokenProperties;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.Collections;
/**
* Filter responsible for JSON Web Token (JWT) authorization.
* It extracts the JWT from the request header, validates it, and sets
* the authentication context.
*/
public class AuthorizationFilter extends OncePerRequestFilter {
private static final Logger LOGGER = LogManager.getLogger(AuthorizationFilter.class);
/**
* Filters incoming requests and processes JWT authorization.
*
* @param request The HTTP servlet request.
* @param response The HTTP servlet response.
* @param filterChain The filter chain for the request.
* @throws ServletException If an error occurs during servlet processing.
* @throws IOException If an I/O error occurs during request processing.
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
final String header = request.getHeader(HttpHeaders.AUTHORIZATION);
if (header == null || !header.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
String token = header.substring(7);
final DecodedJWT decodedJWT = validateToken(token);
if (decodedJWT == null) {
filterChain.doFilter(request, response);
return;
}
long userId = Long.parseLong(decodedJWT.getSubject());
String userRole = decodedJWT.getClaim("user_role").asString();
String role = userRole.equals(Role.ADMIN.name()) ? "ADMIN" : "USER";
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
new AuthIdentity(userId, role), null,
Collections.singletonList(new SimpleGrantedAuthority(role)));
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
/**
* Validates the JWT token.
*
* @param token The JWT token to validate.
* @return The decoded JWT if valid, null otherwise.
*/
public DecodedJWT validateToken(final String token) {
try {
final Algorithm hmac512 = Algorithm.HMAC512(TokenProperties.SECRET);
final JWTVerifier verifier = JWT.require(hmac512).build();
return verifier.verify(token);
} catch (final JWTVerificationException verificationEx) {
LOGGER.warn("token is invalid: {}", verificationEx.getMessage());
return null;
}
}
}
package no.ntnu.idi.stud.savingsapp.security;
import jakarta.servlet.DispatcherType;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
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.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
import java.util.List;
/**
* Configuration class responsible for defining security configurations for the application.
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig {
/**
* Provides a bean for password encoder.
*
* @return A PasswordEncoder instance.
*/
@Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
/**
* Configures the security filter chain.
*
* @param http The HttpSecurity object to configure.
* @return A SecurityFilterChain instance.
* @throws Exception If an error occurs during configuration.
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http.cors()
.and()
.csrf()
.disable()
.authorizeHttpRequests(auth -> {
auth.dispatcherTypeMatchers(DispatcherType.ERROR).permitAll()
.requestMatchers("/swagger/**", "/api/auth/**").permitAll().anyRequest().authenticated();
})
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.addFilterBefore(new AuthorizationFilter(), UsernamePasswordAuthenticationFilter.class)
.build();
}
/**
* Provides a bean for configuring CORS (Cross-Origin Resource Sharing).
*
* @return A CorsConfigurationSource instance.
*/
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
config.setAllowedOrigins(List.of("http://localhost:5173"));
config.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type", "Cache-Control"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
}
package no.ntnu.idi.stud.savingsapp.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import no.ntnu.idi.stud.savingsapp.model.User;
import no.ntnu.idi.stud.savingsapp.properties.TokenProperties;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.time.Instant;
/**
* Utility class for working with JWT (JSON Web Token) generation and validation.
*/
@Component
public final class TokenUtils {
/**
* Generates a JWT (JSON Web Token) for the given user.
*
* @param user The user for whom the token is generated.
* @return The generated JWT token as a string.
*/
public static String generateToken(final User user) {
final Instant now = Instant.now();
final Algorithm hmac512 = Algorithm.HMAC512(TokenProperties.SECRET);;
return JWT.create()
.withSubject(String.valueOf(user.getId()))
.withClaim("user_role", user.getRole().name())
.withIssuer("sparesti")
.withIssuedAt(now)
.withExpiresAt(now.plusMillis(Duration.ofMinutes(TokenProperties.DURATION).toMillis()))
.sign(hmac512);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment