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.ResponseBody;
|
||||
|
||||
@Controller("/")
|
||||
public class GreetingController {
|
||||
@Controller
|
||||
public final class GreetingController {
|
||||
|
||||
@GetMapping("/greeting")
|
||||
@ResponseBody
|
||||
|
@ -1,15 +1,12 @@
|
||||
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.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;
|
||||
import java.util.Set;
|
||||
|
||||
@SpringBootApplication
|
||||
public class MealsMadeEasyApiApplication {
|
||||
@ -18,21 +15,17 @@ public class MealsMadeEasyApiApplication {
|
||||
SpringApplication.run(MealsMadeEasyApiApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecretKey secretKey() {
|
||||
return Jwts.SIG.HS256.key().build();
|
||||
private final UserService userService;
|
||||
|
||||
public MealsMadeEasyApiApplication(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public UserDetailsService userDetailsService() {
|
||||
@SuppressWarnings("deprecation")
|
||||
UserDetails testUser = User
|
||||
.withDefaultPasswordEncoder()
|
||||
.username("test")
|
||||
.password("test")
|
||||
.roles("USER")
|
||||
.build();
|
||||
return new InMemoryUserDetailsManager(testUser);
|
||||
public CommandLineRunner addTestUser() {
|
||||
return args -> {
|
||||
this.userService.createUser("test", "test@test.com", "test", Set.of());
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
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;
|
||||
|
||||
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 io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.io.Serializer;
|
||||
import io.jsonwebtoken.jackson.io.JacksonSerializer;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
@ -9,34 +10,52 @@ import org.springframework.stereotype.Service;
|
||||
import javax.crypto.SecretKey;
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public final class JwtServiceImpl implements JwtService {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
private final long tokenLifetime;
|
||||
private final Serializer<Map<String, ?>> serializer;
|
||||
private final long accessTokenLifetime;
|
||||
private final long refreshTokenLifetime;
|
||||
private final SecretKey secretKey;
|
||||
|
||||
public JwtServiceImpl(
|
||||
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
|
||||
) {
|
||||
this.objectMapper = objectMapper;
|
||||
this.tokenLifetime = tokenLifetime;
|
||||
this.serializer = new JacksonSerializer<>();
|
||||
this.accessTokenLifetime = accessTokenLifetime;
|
||||
this.refreshTokenLifetime = refreshTokenLifetime;
|
||||
this.secretKey = secretKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateToken(String username) {
|
||||
public AuthToken generateAccessToken(String username) {
|
||||
final Instant now = Instant.now();
|
||||
return Jwts.builder()
|
||||
final String token = Jwts.builder()
|
||||
.subject(username)
|
||||
.issuedAt(Date.from(now))
|
||||
.expiration(Date.from(Instant.now().plusSeconds(this.tokenLifetime)))
|
||||
.expiration(Date.from(now.plusSeconds(this.accessTokenLifetime)))
|
||||
.signWith(this.secretKey)
|
||||
.json(new JacksonSerializer<>(this.objectMapper))
|
||||
.json(this.serializer)
|
||||
.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 org.springframework.context.annotation.Bean;
|
||||
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.annotation.web.builders.HttpSecurity;
|
||||
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.http.SessionCreationPolicy;
|
||||
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.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
@ -20,16 +25,16 @@ import javax.crypto.SecretKey;
|
||||
public class SecurityConfiguration {
|
||||
|
||||
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.userDetailsService = userDetailsService;
|
||||
this.jpaUserDetailsService = jpaUserDetailsService;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WebSecurityCustomizer webSecurityCustomizer() {
|
||||
return web -> web.ignoring().requestMatchers("/greeting");
|
||||
return web -> web.ignoring().requestMatchers("/greeting", "/auth/login");
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ -46,10 +51,33 @@ public class SecurityConfiguration {
|
||||
});
|
||||
});
|
||||
httpSecurity.addFilterBefore(
|
||||
new JwtFilter(this.secretKey, this.userDetailsService),
|
||||
new JwtFilter(this.secretKey, this.jpaUserDetailsService),
|
||||
UsernamePasswordAuthenticationFilter.class
|
||||
);
|
||||
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.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.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
|
||||
app.mealsmadeeasy.api.security.access-token-lifetime=60
|
||||
app.mealsmadeeasy.api.security.refresh-token-lifetime=120
|
||||
|
Loading…
Reference in New Issue
Block a user