Basic security working.
This commit is contained in:
parent
0844818591
commit
5449a2c934
@ -18,6 +18,7 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
// From Spring Initalizr
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||||
@ -25,6 +26,11 @@ dependencies {
|
|||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
testImplementation 'org.springframework.security:spring-security-test'
|
testImplementation 'org.springframework.security:spring-security-test'
|
||||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
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') {
|
tasks.named('test') {
|
||||||
|
16
src/main/java/app/mealsmadeeasy/api/GreetingController.java
Normal file
16
src/main/java/app/mealsmadeeasy/api/GreetingController.java
Normal 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!";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,7 +1,15 @@
|
|||||||
package app.mealsmadeeasy.api;
|
package app.mealsmadeeasy.api;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
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
|
@SpringBootApplication
|
||||||
public class MealsMadeEasyApiApplication {
|
public class MealsMadeEasyApiApplication {
|
||||||
@ -10,4 +18,21 @@ public class MealsMadeEasyApiApplication {
|
|||||||
SpringApplication.run(MealsMadeEasyApiApplication.class, args);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
58
src/main/java/app/mealsmadeeasy/api/security/JwtFilter.java
Normal file
58
src/main/java/app/mealsmadeeasy/api/security/JwtFilter.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package app.mealsmadeeasy.api.security;
|
||||||
|
|
||||||
|
public interface JwtService {
|
||||||
|
String generateToken(String username);
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1 +1,7 @@
|
|||||||
spring.application.name=meals-made-easy-api
|
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
|
||||||
|
9
src/main/resources/templates/home.html
Normal file
9
src/main/resources/templates/home.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Home</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Hello, World!</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user