Basic security working.

This commit is contained in:
JesseBrault0709 2024-06-18 14:26:20 +02:00
parent 0844818591
commit 5449a2c934
9 changed files with 222 additions and 0 deletions

View File

@ -18,6 +18,7 @@ repositories {
}
dependencies {
// From Spring Initalizr
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
@ -25,6 +26,11 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
// Custom
implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
implementation 'io.jsonwebtoken:jjwt-jackson:0.12.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5'
}
tasks.named('test') {

View File

@ -0,0 +1,16 @@
package app.mealsmadeeasy.api;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller("/")
public class GreetingController {
@GetMapping("/greeting")
@ResponseBody
public String get() {
return "Hello, World!";
}
}

View File

@ -1,7 +1,15 @@
package app.mealsmadeeasy.api;
import io.jsonwebtoken.Jwts;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import javax.crypto.SecretKey;
@SpringBootApplication
public class MealsMadeEasyApiApplication {
@ -10,4 +18,21 @@ public class MealsMadeEasyApiApplication {
SpringApplication.run(MealsMadeEasyApiApplication.class, args);
}
@Bean
public SecretKey secretKey() {
return Jwts.SIG.HS256.key().build();
}
@Bean
public UserDetailsService userDetailsService() {
@SuppressWarnings("deprecation")
UserDetails testUser = User
.withDefaultPasswordEncoder()
.username("test")
.password("test")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(testUser);
}
}

View File

@ -0,0 +1,58 @@
package app.mealsmadeeasy.api.security;
import io.jsonwebtoken.Jwts;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.crypto.SecretKey;
import java.io.IOException;
public final class JwtFilter extends OncePerRequestFilter {
private final SecretKey secretKey;
private final UserDetailsService userDetailsService;
public JwtFilter(SecretKey secretKey, UserDetailsService userDetailsService) {
this.secretKey = secretKey;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
final String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
if (authorizationHeader == null) {
filterChain.doFilter(request, response);
return;
}
if (authorizationHeader.startsWith("Bearer ")
&& authorizationHeader.length() > 7) {
final String token = authorizationHeader.substring(7);
final var jws = Jwts.parser()
.verifyWith(this.secretKey)
.build()
.parseSignedClaims(token);
final String username = jws.getPayload().getSubject();
final UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
final var authenticationToken = new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
filterChain.doFilter(request, response);
}
}

View File

@ -0,0 +1,5 @@
package app.mealsmadeeasy.api.security;
public interface JwtService {
String generateToken(String username);
}

View File

@ -0,0 +1,42 @@
package app.mealsmadeeasy.api.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.jackson.io.JacksonSerializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.crypto.SecretKey;
import java.time.Instant;
import java.util.Date;
@Service
public final class JwtServiceImpl implements JwtService {
private final ObjectMapper objectMapper;
private final long tokenLifetime;
private final SecretKey secretKey;
public JwtServiceImpl(
ObjectMapper objectMapper,
@Value("${app.mealsmadeeasy.api.security.token-lifetime}") Long tokenLifetime,
SecretKey secretKey
) {
this.objectMapper = objectMapper;
this.tokenLifetime = tokenLifetime;
this.secretKey = secretKey;
}
@Override
public String generateToken(String username) {
final Instant now = Instant.now();
return Jwts.builder()
.subject(username)
.issuedAt(Date.from(now))
.expiration(Date.from(Instant.now().plusSeconds(this.tokenLifetime)))
.signWith(this.secretKey)
.json(new JacksonSerializer<>(this.objectMapper))
.compact();
}
}

View File

@ -0,0 +1,55 @@
package app.mealsmadeeasy.api.security;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.crypto.SecretKey;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
private final SecretKey secretKey;
private final UserDetailsService userDetailsService;
public SecurityConfiguration(SecretKey secretKey, UserDetailsService userDetailsService) {
this.secretKey = secretKey;
this.userDetailsService = userDetailsService;
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return web -> web.ignoring().requestMatchers("/greeting");
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeHttpRequests(requests -> requests.anyRequest().authenticated());
httpSecurity.csrf(AbstractHttpConfigurer::disable);
httpSecurity.cors(Customizer.withDefaults());
httpSecurity.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(
SessionCreationPolicy.STATELESS
));
httpSecurity.exceptionHandling(exceptionHandling -> {
exceptionHandling.authenticationEntryPoint((request, response, authException) -> {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
});
});
httpSecurity.addFilterBefore(
new JwtFilter(this.secretKey, this.userDetailsService),
UsernamePasswordAuthenticationFilter.class
);
return httpSecurity.build();
}
}

View File

@ -1 +1,7 @@
spring.application.name=meals-made-easy-api
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:55001/meals_made_easy_api
spring.datasource.username=meals-made-easy-api-user
spring.datasource.password=devpass
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
app.mealsmadeeasy.api.security.token-lifetime=60

View File

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Home</title>
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>