Basic Users and authentication.
This commit is contained in:
parent
34c13d3315
commit
bf2b7138ca
@ -4,8 +4,8 @@ import org.springframework.stereotype.Controller;
|
|||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
@Controller("/")
|
@Controller
|
||||||
public class GreetingController {
|
public final class GreetingController {
|
||||||
|
|
||||||
@GetMapping("/greeting")
|
@GetMapping("/greeting")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
package app.mealsmadeeasy.api;
|
package app.mealsmadeeasy.api;
|
||||||
|
|
||||||
import io.jsonwebtoken.Jwts;
|
import app.mealsmadeeasy.api.user.UserService;
|
||||||
|
import org.springframework.boot.CommandLineRunner;
|
||||||
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.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;
|
import java.util.Set;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class MealsMadeEasyApiApplication {
|
public class MealsMadeEasyApiApplication {
|
||||||
@ -18,21 +15,17 @@ public class MealsMadeEasyApiApplication {
|
|||||||
SpringApplication.run(MealsMadeEasyApiApplication.class, args);
|
SpringApplication.run(MealsMadeEasyApiApplication.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
private final UserService userService;
|
||||||
public SecretKey secretKey() {
|
|
||||||
return Jwts.SIG.HS256.key().build();
|
public MealsMadeEasyApiApplication(UserService userService) {
|
||||||
|
this.userService = userService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public UserDetailsService userDetailsService() {
|
public CommandLineRunner addTestUser() {
|
||||||
@SuppressWarnings("deprecation")
|
return args -> {
|
||||||
UserDetails testUser = User
|
this.userService.createUser("test", "test@test.com", "test", Set.of());
|
||||||
.withDefaultPasswordEncoder()
|
};
|
||||||
.username("test")
|
|
||||||
.password("test")
|
|
||||||
.roles("USER")
|
|
||||||
.build();
|
|
||||||
return new InMemoryUserDetailsManager(testUser);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
44
src/main/java/app/mealsmadeeasy/api/auth/AuthController.java
Normal file
44
src/main/java/app/mealsmadeeasy/api/auth/AuthController.java
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package app.mealsmadeeasy.api.auth;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseCookie;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/auth")
|
||||||
|
public final class AuthController {
|
||||||
|
|
||||||
|
private final AuthService authService;
|
||||||
|
|
||||||
|
public AuthController(AuthService authService) {
|
||||||
|
this.authService = authService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/login")
|
||||||
|
public ResponseEntity<LoginView> login(@RequestBody LoginBody loginBody, HttpServletResponse response) {
|
||||||
|
try {
|
||||||
|
final LoginDetails loginDetails = this.authService.login(loginBody.getUsername(), loginBody.getPassword());
|
||||||
|
final String serializedToken = loginDetails.getRefreshToken().getToken();
|
||||||
|
final ResponseCookie refreshCookie = ResponseCookie.from("refresh-token", serializedToken)
|
||||||
|
.httpOnly(true)
|
||||||
|
.secure(true)
|
||||||
|
.maxAge(loginDetails.getRefreshToken().getLifetime())
|
||||||
|
.build();
|
||||||
|
final LoginView loginView = new LoginView(
|
||||||
|
loginDetails.getUsername(), loginDetails.getAccessToken().getToken()
|
||||||
|
);
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.header(HttpHeaders.SET_COOKIE, refreshCookie.toString())
|
||||||
|
.body(loginView);
|
||||||
|
} catch (LoginException loginException) {
|
||||||
|
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package app.mealsmadeeasy.api.auth;
|
||||||
|
|
||||||
|
public interface AuthService {
|
||||||
|
LoginDetails login(String username, String password) throws LoginException;
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package app.mealsmadeeasy.api.auth;
|
||||||
|
|
||||||
|
import app.mealsmadeeasy.api.security.JwtService;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public final class AuthServiceImpl implements AuthService {
|
||||||
|
|
||||||
|
private final AuthenticationManager authenticationManager;
|
||||||
|
private final JwtService jwtService;
|
||||||
|
|
||||||
|
public AuthServiceImpl(AuthenticationManager authenticationManager, JwtService jwtService) {
|
||||||
|
this.authenticationManager = authenticationManager;
|
||||||
|
this.jwtService = jwtService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LoginDetails login(String username, String password) throws LoginException {
|
||||||
|
try {
|
||||||
|
this.authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
|
||||||
|
username,
|
||||||
|
password
|
||||||
|
));
|
||||||
|
return new LoginDetails(
|
||||||
|
username,
|
||||||
|
this.jwtService.generateAccessToken(username),
|
||||||
|
this.jwtService.generateRefreshToken(username)
|
||||||
|
);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new LoginException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
24
src/main/java/app/mealsmadeeasy/api/auth/LoginBody.java
Normal file
24
src/main/java/app/mealsmadeeasy/api/auth/LoginBody.java
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package app.mealsmadeeasy.api.auth;
|
||||||
|
|
||||||
|
public final class LoginBody {
|
||||||
|
|
||||||
|
private String username;
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return this.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return this.password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
29
src/main/java/app/mealsmadeeasy/api/auth/LoginDetails.java
Normal file
29
src/main/java/app/mealsmadeeasy/api/auth/LoginDetails.java
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package app.mealsmadeeasy.api.auth;
|
||||||
|
|
||||||
|
import app.mealsmadeeasy.api.security.AuthToken;
|
||||||
|
|
||||||
|
public final class LoginDetails {
|
||||||
|
|
||||||
|
private final String username;
|
||||||
|
private final AuthToken accessToken;
|
||||||
|
private final AuthToken refreshToken;
|
||||||
|
|
||||||
|
public LoginDetails(String username, AuthToken accessToken, AuthToken refreshToken) {
|
||||||
|
this.username = username;
|
||||||
|
this.accessToken = accessToken;
|
||||||
|
this.refreshToken = refreshToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return this.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthToken getAccessToken() {
|
||||||
|
return this.accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthToken getRefreshToken() {
|
||||||
|
return this.refreshToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
17
src/main/java/app/mealsmadeeasy/api/auth/LoginException.java
Normal file
17
src/main/java/app/mealsmadeeasy/api/auth/LoginException.java
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package app.mealsmadeeasy.api.auth;
|
||||||
|
|
||||||
|
public final class LoginException extends Exception {
|
||||||
|
|
||||||
|
public LoginException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoginException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoginException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
21
src/main/java/app/mealsmadeeasy/api/auth/LoginView.java
Normal file
21
src/main/java/app/mealsmadeeasy/api/auth/LoginView.java
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package app.mealsmadeeasy.api.auth;
|
||||||
|
|
||||||
|
public final class LoginView {
|
||||||
|
|
||||||
|
private final String username;
|
||||||
|
private final String accessToken;
|
||||||
|
|
||||||
|
public LoginView(String username, String accessToken) {
|
||||||
|
this.username = username;
|
||||||
|
this.accessToken = accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return this.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAccessToken() {
|
||||||
|
return this.accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
21
src/main/java/app/mealsmadeeasy/api/security/AuthToken.java
Normal file
21
src/main/java/app/mealsmadeeasy/api/security/AuthToken.java
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package app.mealsmadeeasy.api.security;
|
||||||
|
|
||||||
|
public final class AuthToken {
|
||||||
|
|
||||||
|
private final String token;
|
||||||
|
private final long lifetime;
|
||||||
|
|
||||||
|
public AuthToken(String token, long lifetime) {
|
||||||
|
this.token = token;
|
||||||
|
this.lifetime = lifetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getToken() {
|
||||||
|
return this.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLifetime() {
|
||||||
|
return this.lifetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package app.mealsmadeeasy.api.security;
|
||||||
|
|
||||||
|
import app.mealsmadeeasy.api.user.User;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
|
||||||
|
public interface JpaUserDetailsService extends UserDetailsService {
|
||||||
|
User createUser(User user);
|
||||||
|
User updateUser(User user);
|
||||||
|
void deleteUser(String username);
|
||||||
|
void deleteUser(User user);
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package app.mealsmadeeasy.api.security;
|
||||||
|
|
||||||
|
import app.mealsmadeeasy.api.user.User;
|
||||||
|
import app.mealsmadeeasy.api.user.UserEntity;
|
||||||
|
import app.mealsmadeeasy.api.user.UserRepository;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public final class JpaUserDetailsServiceImpl implements JpaUserDetailsService {
|
||||||
|
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
|
public JpaUserDetailsServiceImpl(UserRepository userRepository) {
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User createUser(User user) {
|
||||||
|
return this.userRepository.save((UserEntity) user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User updateUser(User user) {
|
||||||
|
return this.userRepository.save((UserEntity) user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteUser(String username) {
|
||||||
|
final UserEntity user = this.userRepository.findByUsername(username)
|
||||||
|
.orElseThrow(() -> new UsernameNotFoundException("No such User with username: " + username));
|
||||||
|
this.userRepository.delete(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteUser(User user) {
|
||||||
|
this.userRepository.delete((UserEntity) user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||||
|
return this.userRepository.findByUsername(username)
|
||||||
|
.orElseThrow(() -> new UsernameNotFoundException("No such User with username: " + username));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package app.mealsmadeeasy.api.security;
|
package app.mealsmadeeasy.api.security;
|
||||||
|
|
||||||
public interface JwtService {
|
public interface JwtService {
|
||||||
String generateToken(String username);
|
AuthToken generateAccessToken(String username);
|
||||||
|
AuthToken generateRefreshToken(String username);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package app.mealsmadeeasy.api.security;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import io.jsonwebtoken.Jwts;
|
import io.jsonwebtoken.Jwts;
|
||||||
|
import io.jsonwebtoken.io.Serializer;
|
||||||
import io.jsonwebtoken.jackson.io.JacksonSerializer;
|
import io.jsonwebtoken.jackson.io.JacksonSerializer;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@ -9,34 +10,52 @@ import org.springframework.stereotype.Service;
|
|||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public final class JwtServiceImpl implements JwtService {
|
public final class JwtServiceImpl implements JwtService {
|
||||||
|
|
||||||
private final ObjectMapper objectMapper;
|
private final Serializer<Map<String, ?>> serializer;
|
||||||
private final long tokenLifetime;
|
private final long accessTokenLifetime;
|
||||||
|
private final long refreshTokenLifetime;
|
||||||
private final SecretKey secretKey;
|
private final SecretKey secretKey;
|
||||||
|
|
||||||
public JwtServiceImpl(
|
public JwtServiceImpl(
|
||||||
ObjectMapper objectMapper,
|
ObjectMapper objectMapper,
|
||||||
@Value("${app.mealsmadeeasy.api.security.token-lifetime}") Long tokenLifetime,
|
@Value("${app.mealsmadeeasy.api.security.access-token-lifetime}") Long accessTokenLifetime,
|
||||||
|
@Value("${app.mealsmadeeasy.api.security.refresh-token-lifetime}") Long refreshTokenLifetime,
|
||||||
SecretKey secretKey
|
SecretKey secretKey
|
||||||
) {
|
) {
|
||||||
this.objectMapper = objectMapper;
|
this.serializer = new JacksonSerializer<>();
|
||||||
this.tokenLifetime = tokenLifetime;
|
this.accessTokenLifetime = accessTokenLifetime;
|
||||||
|
this.refreshTokenLifetime = refreshTokenLifetime;
|
||||||
this.secretKey = secretKey;
|
this.secretKey = secretKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String generateToken(String username) {
|
public AuthToken generateAccessToken(String username) {
|
||||||
final Instant now = Instant.now();
|
final Instant now = Instant.now();
|
||||||
return Jwts.builder()
|
final String token = Jwts.builder()
|
||||||
.subject(username)
|
.subject(username)
|
||||||
.issuedAt(Date.from(now))
|
.issuedAt(Date.from(now))
|
||||||
.expiration(Date.from(Instant.now().plusSeconds(this.tokenLifetime)))
|
.expiration(Date.from(now.plusSeconds(this.accessTokenLifetime)))
|
||||||
.signWith(this.secretKey)
|
.signWith(this.secretKey)
|
||||||
.json(new JacksonSerializer<>(this.objectMapper))
|
.json(this.serializer)
|
||||||
.compact();
|
.compact();
|
||||||
|
return new AuthToken(token, this.accessTokenLifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthToken generateRefreshToken(String username) {
|
||||||
|
final Instant now = Instant.now();
|
||||||
|
final String token = Jwts.builder()
|
||||||
|
.subject(username)
|
||||||
|
.issuedAt(Date.from(now))
|
||||||
|
.expiration(Date.from(now.plusSeconds(this.refreshTokenLifetime)))
|
||||||
|
.signWith(this.secretKey)
|
||||||
|
.json(this.serializer)
|
||||||
|
.compact();
|
||||||
|
return new AuthToken(token, this.refreshTokenLifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
package app.mealsmadeeasy.api.security;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class KeyConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecretKey secretKey() {
|
||||||
|
return Jwts.SIG.HS256.key().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -3,6 +3,9 @@ package app.mealsmadeeasy.api.security;
|
|||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.authentication.ProviderManager;
|
||||||
|
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||||
import org.springframework.security.config.Customizer;
|
import org.springframework.security.config.Customizer;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
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.EnableWebSecurity;
|
||||||
@ -10,6 +13,8 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
|
|||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
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.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
|
||||||
@ -20,16 +25,16 @@ import javax.crypto.SecretKey;
|
|||||||
public class SecurityConfiguration {
|
public class SecurityConfiguration {
|
||||||
|
|
||||||
private final SecretKey secretKey;
|
private final SecretKey secretKey;
|
||||||
private final UserDetailsService userDetailsService;
|
private final JpaUserDetailsService jpaUserDetailsService;
|
||||||
|
|
||||||
public SecurityConfiguration(SecretKey secretKey, UserDetailsService userDetailsService) {
|
public SecurityConfiguration(SecretKey secretKey, JpaUserDetailsService jpaUserDetailsService) {
|
||||||
this.secretKey = secretKey;
|
this.secretKey = secretKey;
|
||||||
this.userDetailsService = userDetailsService;
|
this.jpaUserDetailsService = jpaUserDetailsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public WebSecurityCustomizer webSecurityCustomizer() {
|
public WebSecurityCustomizer webSecurityCustomizer() {
|
||||||
return web -> web.ignoring().requestMatchers("/greeting");
|
return web -> web.ignoring().requestMatchers("/greeting", "/auth/login");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ -46,10 +51,33 @@ public class SecurityConfiguration {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
httpSecurity.addFilterBefore(
|
httpSecurity.addFilterBefore(
|
||||||
new JwtFilter(this.secretKey, this.userDetailsService),
|
new JwtFilter(this.secretKey, this.jpaUserDetailsService),
|
||||||
UsernamePasswordAuthenticationFilter.class
|
UsernamePasswordAuthenticationFilter.class
|
||||||
);
|
);
|
||||||
return httpSecurity.build();
|
return httpSecurity.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public UserDetailsService userDetailsService() {
|
||||||
|
return this.jpaUserDetailsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public DaoAuthenticationProvider daoAuthenticationProvider() {
|
||||||
|
final var provider = new DaoAuthenticationProvider();
|
||||||
|
provider.setUserDetailsService(this.userDetailsService());
|
||||||
|
provider.setPasswordEncoder(this.passwordEncoder());
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AuthenticationManager authenticationManager() {
|
||||||
|
return new ProviderManager(this.daoAuthenticationProvider());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
18
src/main/java/app/mealsmadeeasy/api/user/User.java
Normal file
18
src/main/java/app/mealsmadeeasy/api/user/User.java
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package app.mealsmadeeasy.api.user;
|
||||||
|
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public sealed interface User extends UserDetails permits UserEntity {
|
||||||
|
|
||||||
|
Long getId();
|
||||||
|
|
||||||
|
String getEmail();
|
||||||
|
void setEmail(String email);
|
||||||
|
|
||||||
|
void addAuthority(UserGrantedAuthority userGrantedAuthority);
|
||||||
|
void addAuthorities(Set<? extends UserGrantedAuthority> userGrantedAuthorities);
|
||||||
|
void removeAuthority(UserGrantedAuthority userGrantedAuthority);
|
||||||
|
|
||||||
|
}
|
144
src/main/java/app/mealsmadeeasy/api/user/UserEntity.java
Normal file
144
src/main/java/app/mealsmadeeasy/api/user/UserEntity.java
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package app.mealsmadeeasy.api.user;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Entity(name = "User")
|
||||||
|
public final class UserEntity implements User {
|
||||||
|
|
||||||
|
public static UserEntity getDefaultDraft() {
|
||||||
|
final var user = new UserEntity();
|
||||||
|
user.setEnabled(true);
|
||||||
|
user.setExpired(false);
|
||||||
|
user.setLocked(false);
|
||||||
|
user.setCredentialsExpired(false);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(unique = true, nullable = false)
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@Column(unique = true, nullable = false)
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@OneToMany(fetch = FetchType.EAGER, mappedBy = "userEntity")
|
||||||
|
private final Set<UserGrantedAuthorityEntity> authorities = new HashSet<>();
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Boolean enabled;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Boolean expired;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Boolean locked;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Boolean credentialsExpired;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getId() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsername() {
|
||||||
|
return this.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEmail() {
|
||||||
|
return this.email;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPassword() {
|
||||||
|
return this.password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
|
return this.authorities;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addAuthority(UserGrantedAuthority userGrantedAuthority) {
|
||||||
|
this.authorities.add((UserGrantedAuthorityEntity) userGrantedAuthority);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addAuthorities(Set<? extends UserGrantedAuthority> userGrantedAuthorities) {
|
||||||
|
userGrantedAuthorities.forEach(this::addAuthority);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAuthority(UserGrantedAuthority userGrantedAuthority) {
|
||||||
|
this.authorities.remove((UserGrantedAuthorityEntity) userGrantedAuthority);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonExpired() {
|
||||||
|
return !this.expired;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExpired(Boolean expired) {
|
||||||
|
this.expired = expired;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonLocked() {
|
||||||
|
return !this.locked;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocked(Boolean locked) {
|
||||||
|
this.locked = locked;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCredentialsNonExpired() {
|
||||||
|
return !this.credentialsExpired;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCredentialsExpired(Boolean credentialsExpired) {
|
||||||
|
this.credentialsExpired = credentialsExpired;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return this.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(Boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package app.mealsmadeeasy.api.user;
|
||||||
|
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
|
||||||
|
public interface UserGrantedAuthority extends GrantedAuthority {}
|
@ -0,0 +1,24 @@
|
|||||||
|
package app.mealsmadeeasy.api.user;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
public final class UserGrantedAuthorityEntity implements UserGrantedAuthority {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String authority;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "user_entity_id")
|
||||||
|
private UserEntity userEntity;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAuthority() {
|
||||||
|
return this.authority;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package app.mealsmadeeasy.api.user;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
public interface UserGrantedAuthorityRepository extends JpaRepository<UserGrantedAuthorityEntity, Long> {}
|
@ -0,0 +1,9 @@
|
|||||||
|
package app.mealsmadeeasy.api.user;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface UserRepository extends JpaRepository<UserEntity, Long> {
|
||||||
|
Optional<UserEntity> findByUsername(String username);
|
||||||
|
}
|
10
src/main/java/app/mealsmadeeasy/api/user/UserService.java
Normal file
10
src/main/java/app/mealsmadeeasy/api/user/UserService.java
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package app.mealsmadeeasy.api.user;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public interface UserService {
|
||||||
|
User createUser(String username, String email, String rawPassword, Set<UserGrantedAuthority> authorities);
|
||||||
|
User updateUser(User user);
|
||||||
|
void deleteUser(User user);
|
||||||
|
void deleteUser(String username);
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package app.mealsmadeeasy.api.user;
|
||||||
|
|
||||||
|
import app.mealsmadeeasy.api.security.JpaUserDetailsService;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public final class UserServiceImpl implements UserService {
|
||||||
|
|
||||||
|
private final JpaUserDetailsService jpaUserDetailsService;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
public UserServiceImpl(JpaUserDetailsService jpaUserDetailsService, PasswordEncoder passwordEncoder) {
|
||||||
|
this.jpaUserDetailsService = jpaUserDetailsService;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User createUser(
|
||||||
|
String username,
|
||||||
|
String email,
|
||||||
|
String rawPassword,
|
||||||
|
Set<UserGrantedAuthority> authorities
|
||||||
|
) {
|
||||||
|
final UserEntity draft = UserEntity.getDefaultDraft();
|
||||||
|
draft.setUsername(username);
|
||||||
|
draft.setEmail(email);
|
||||||
|
draft.setPassword(this.passwordEncoder.encode(rawPassword));
|
||||||
|
draft.addAuthorities(authorities);
|
||||||
|
return this.jpaUserDetailsService.createUser(draft);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User updateUser(User user) {
|
||||||
|
return this.jpaUserDetailsService.updateUser(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteUser(User user) {
|
||||||
|
this.jpaUserDetailsService.deleteUser(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteUser(String username) {
|
||||||
|
this.jpaUserDetailsService.deleteUser(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
spring.application.name=meals-made-easy-api
|
spring.application.name=meals-made-easy-api
|
||||||
spring.jpa.hibernate.ddl-auto=update
|
spring.jpa.hibernate.ddl-auto=create-drop
|
||||||
spring.datasource.url=jdbc:mysql://localhost:55001/meals_made_easy_api
|
spring.datasource.url=jdbc:mysql://localhost:55001/meals_made_easy_api
|
||||||
spring.datasource.username=meals-made-easy-api-user
|
spring.datasource.username=meals-made-easy-api-user
|
||||||
spring.datasource.password=devpass
|
spring.datasource.password=devpass
|
||||||
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||||
app.mealsmadeeasy.api.security.token-lifetime=60
|
app.mealsmadeeasy.api.security.access-token-lifetime=60
|
||||||
|
app.mealsmadeeasy.api.security.refresh-token-lifetime=120
|
||||||
|
Loading…
Reference in New Issue
Block a user