Initial implementation of OAuth2 server with OIDC
This commit is contained in:
6
pom.xml
6
pom.xml
@ -12,7 +12,7 @@
|
||||
<artifactId>anyame-backend</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>anyame-backend</name>
|
||||
<description>User service for anyame backend</description>
|
||||
<description>User service for anyame</description>
|
||||
<url/>
|
||||
<licenses>
|
||||
<license/>
|
||||
@ -50,6 +50,10 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-oauth2-authorization-server</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
|
@ -1,104 +0,0 @@
|
||||
package com.backend.user.service.anyame.component;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "authorization")
|
||||
public class AuthorizationProperties {
|
||||
|
||||
private List<RoleConfig> roles;
|
||||
private String hierarchy;
|
||||
private String defaultRole;
|
||||
|
||||
public List<RoleConfig> getRoles() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
public String getHierarchy() {
|
||||
return hierarchy;
|
||||
}
|
||||
|
||||
public String getDefaultRole() {
|
||||
return defaultRole;
|
||||
}
|
||||
|
||||
public AuthorizationProperties setRoles(List<RoleConfig> roles) {
|
||||
this.roles = roles;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AuthorizationProperties setHierarchy(String hierarchy) {
|
||||
this.hierarchy = hierarchy;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AuthorizationProperties setDefaultRole(String defaultRole) {
|
||||
this.defaultRole = defaultRole;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Optional<Duration> getAccessExpiry(String roleName) {
|
||||
return roles.stream()
|
||||
.filter(role -> role.getName().equals(roleName))
|
||||
.map(RoleConfig::getAccessExpiry)
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
public Optional<Duration> getRefreshExpiry(String roleName) {
|
||||
return roles.stream()
|
||||
.filter(role -> role.getName().equals(roleName))
|
||||
.map(RoleConfig::getRefreshExpiry)
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
public static class RoleConfig {
|
||||
|
||||
private String name;
|
||||
private Duration accessExpiry;
|
||||
private Duration refreshExpiry;
|
||||
private List<String> privileges;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Duration getAccessExpiry() {
|
||||
return accessExpiry;
|
||||
}
|
||||
|
||||
public Duration getRefreshExpiry() {
|
||||
return refreshExpiry;
|
||||
}
|
||||
|
||||
public List<String> getPrivileges() {
|
||||
return privileges;
|
||||
}
|
||||
|
||||
public RoleConfig setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RoleConfig setAccessExpiry(Duration accessExpiry) {
|
||||
this.accessExpiry = accessExpiry;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RoleConfig setRefreshExpiry(Duration refreshExpiry) {
|
||||
this.refreshExpiry = refreshExpiry;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RoleConfig setPrivileges(List<String> privileges) {
|
||||
this.privileges = privileges;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package com.backend.user.service.anyame.component;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "spring.security.oauth2.authorizationserver")
|
||||
public class AuthorizationServerProperties {
|
||||
|
||||
private String issuerUrl;
|
||||
private String introspectionEndpoint;
|
||||
|
||||
public String getIssuerUrl() {
|
||||
return issuerUrl;
|
||||
}
|
||||
|
||||
public AuthorizationServerProperties setIssuerUrl(String issuerUrl) {
|
||||
this.issuerUrl = issuerUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getIntrospectionEndpoint() {
|
||||
return introspectionEndpoint;
|
||||
}
|
||||
|
||||
public AuthorizationServerProperties setIntrospectionEndpoint(String introspectionEndpoint) {
|
||||
this.introspectionEndpoint = introspectionEndpoint;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
package com.backend.user.service.anyame.component;
|
||||
|
||||
import com.backend.user.service.anyame.entity.Role;
|
||||
import com.backend.user.service.anyame.repository.RoleRepository;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class DefaultRoleProvider {
|
||||
private final AuthorizationProperties authorizationProperties;
|
||||
private final RoleRepository repository;
|
||||
private Role defaultRole;
|
||||
|
||||
public DefaultRoleProvider(AuthorizationProperties authorizationProperties, RoleRepository repository) {
|
||||
this.authorizationProperties = authorizationProperties;
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
defaultRole = repository.findByName(authorizationProperties.getDefaultRole()).orElseThrow();
|
||||
}
|
||||
|
||||
public Role getDefaultRole() {
|
||||
return defaultRole;
|
||||
}
|
||||
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
package com.backend.user.service.anyame.component;
|
||||
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import javax.crypto.SecretKey;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Component
|
||||
public class JWTSecretProvider {
|
||||
|
||||
@Value("${jwt.secret}")
|
||||
private String jwtSecret;
|
||||
private SecretKey secretKey;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
secretKey = Keys.hmacShaKeyFor(jwtSecret.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public SecretKey getSecretKey() {
|
||||
return secretKey;
|
||||
}
|
||||
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
package com.backend.user.service.anyame.component;
|
||||
|
||||
import com.backend.user.service.anyame.entity.Privilege;
|
||||
import com.backend.user.service.anyame.entity.Role;
|
||||
import com.backend.user.service.anyame.repository.PrivilegeRepository;
|
||||
import com.backend.user.service.anyame.repository.RoleRepository;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Component
|
||||
public class SetupDataLoader implements
|
||||
ApplicationListener<ContextRefreshedEvent> {
|
||||
|
||||
boolean alreadySetup = false;
|
||||
private final AuthorizationProperties authorizationProperties;
|
||||
private final RoleRepository roleRepository;
|
||||
private final PrivilegeRepository privilegeRepository;
|
||||
|
||||
public SetupDataLoader(AuthorizationProperties authorizationProperties,
|
||||
RoleRepository roleRepository,
|
||||
PrivilegeRepository privilegeRepository) {
|
||||
this.authorizationProperties = authorizationProperties;
|
||||
this.roleRepository = roleRepository;
|
||||
this.privilegeRepository = privilegeRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||
if (alreadySetup)
|
||||
return;
|
||||
authorizationProperties.getRoles().forEach(roleConfig -> {
|
||||
List<Privilege> privileges = roleConfig.getPrivileges()
|
||||
.stream()
|
||||
.map(this::createPrivilegeIfNotFound)
|
||||
.toList();
|
||||
createRoleIfNotFound(roleConfig.getName(), privileges);
|
||||
});
|
||||
}
|
||||
|
||||
Privilege createPrivilegeIfNotFound(String name) {
|
||||
Optional<Privilege> privilegeOptional = privilegeRepository.findByName(name);
|
||||
if (privilegeOptional.isEmpty()) {
|
||||
privilegeOptional = Optional.of(new Privilege(name));
|
||||
privilegeRepository.save(privilegeOptional.get());
|
||||
}
|
||||
return privilegeOptional.get();
|
||||
}
|
||||
|
||||
Role createRoleIfNotFound(String name, Collection<Privilege> privileges) {
|
||||
Optional<Role> roleOptional = roleRepository.findByName(name);
|
||||
if (roleOptional.isEmpty()) {
|
||||
roleOptional = Optional.of(new Role(name));
|
||||
roleOptional.get().setPrivileges(privileges);
|
||||
roleRepository.save(roleOptional.get());
|
||||
}
|
||||
return roleOptional.get();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
package com.backend.user.service.anyame.config;
|
||||
|
||||
import com.backend.user.service.anyame.component.AuthorizationServerProperties;
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.proc.SecurityContext;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
|
||||
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.UUID;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class AuthorizationServerConfig {
|
||||
private final AuthorizationServerProperties authorizationServerProperties;
|
||||
|
||||
public AuthorizationServerConfig(AuthorizationServerProperties authorizationServerProperties) {
|
||||
this.authorizationServerProperties = authorizationServerProperties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(1)
|
||||
public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http, @Qualifier("corsConfigurationSource") CorsConfigurationSource configurationSource) throws Exception {
|
||||
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
|
||||
OAuth2AuthorizationServerConfigurer.authorizationServer();
|
||||
return http
|
||||
.cors(c -> c.configurationSource(configurationSource))
|
||||
.securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
|
||||
.with(authorizationServerConfigurer, (authorizationServer) ->
|
||||
authorizationServer
|
||||
.oidc(Customizer.withDefaults())
|
||||
)
|
||||
.authorizeHttpRequests((authorize) ->
|
||||
authorize
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.exceptionHandling((exceptions) -> exceptions
|
||||
.defaultAuthenticationEntryPointFor(
|
||||
new LoginUrlAuthenticationEntryPoint("/login"),
|
||||
new MediaTypeRequestMatcher(MediaType.ALL)
|
||||
)
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RegisteredClientRepository registeredClientRepository() {
|
||||
RegisteredClient oidcClient = RegisteredClient.withId(UUID.randomUUID().toString())
|
||||
.clientId("oidc-client")
|
||||
.clientSecret("{noop}secret")
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
|
||||
.redirectUri("http://localhost:5173/code")
|
||||
.postLogoutRedirectUri("http://localhost:5173/")
|
||||
.scope(OidcScopes.OPENID)
|
||||
.scope(OidcScopes.PROFILE)
|
||||
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
|
||||
.tokenSettings(TokenSettings.builder()
|
||||
.accessTokenFormat(OAuth2TokenFormat.REFERENCE)
|
||||
.accessTokenTimeToLive(Duration.of(30, ChronoUnit.MINUTES))
|
||||
.refreshTokenTimeToLive(Duration.of(120, ChronoUnit.MINUTES))
|
||||
.reuseRefreshTokens(false)
|
||||
.authorizationCodeTimeToLive(Duration.of(30, ChronoUnit.SECONDS))
|
||||
.build())
|
||||
.build();
|
||||
|
||||
return new InMemoryRegisteredClientRepository(oidcClient);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
|
||||
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthorizationServerSettings authorizationServerSettings() {
|
||||
return AuthorizationServerSettings.builder()
|
||||
.issuer(authorizationServerProperties.getIssuerUrl())
|
||||
.tokenIntrospectionEndpoint(authorizationServerProperties.getIntrospectionEndpoint())
|
||||
.build();
|
||||
}
|
||||
}
|
@ -1,51 +1,42 @@
|
||||
package com.backend.user.service.anyame.config;
|
||||
|
||||
import com.backend.user.service.anyame.component.AuthorizationProperties;
|
||||
import org.passay.CharacterRule;
|
||||
import org.passay.EnglishCharacterData;
|
||||
import org.passay.LengthRule;
|
||||
import org.passay.PasswordValidator;
|
||||
import org.passay.WhitespaceRule;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
|
||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
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 org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
|
||||
import java.util.Arrays;
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
|
||||
@Configuration
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
return http.csrf(AbstractHttpConfigurer::disable).authorizeHttpRequests(c -> c.anyRequest().permitAll()).build();
|
||||
@Order(2)
|
||||
public SecurityFilterChain filterChain(HttpSecurity http, @Qualifier("corsConfigurationSource") CorsConfigurationSource configurationSource) throws Exception {
|
||||
return http
|
||||
.cors(c -> c.configurationSource(configurationSource))
|
||||
.authorizeHttpRequests(c ->
|
||||
c.anyRequest().authenticated()
|
||||
)
|
||||
.formLogin(withDefaults())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordValidator passwordValidator() {
|
||||
return new PasswordValidator(Arrays.asList(
|
||||
new LengthRule(8, 64),
|
||||
new CharacterRule(EnglishCharacterData.UpperCase, 1),
|
||||
new CharacterRule(EnglishCharacterData.LowerCase, 1),
|
||||
new CharacterRule(EnglishCharacterData.Digit, 1),
|
||||
new CharacterRule(EnglishCharacterData.Special, 1),
|
||||
new WhitespaceRule()
|
||||
));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RoleHierarchy roleHierarchy(AuthorizationProperties authorizationProperties) {
|
||||
return RoleHierarchyImpl.fromHierarchy(authorizationProperties.getHierarchy());
|
||||
public UserDetailsService users() {
|
||||
UserDetails user = User.builder()
|
||||
.username("admin")
|
||||
.password("{noop}password")
|
||||
.roles("USER")
|
||||
.build();
|
||||
return new InMemoryUserDetailsManager(user);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.backend.user.service.anyame.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/**")
|
||||
.allowedOrigins("http://localhost:5173")
|
||||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||
.allowedHeaders("*")
|
||||
.allowCredentials(true);
|
||||
}
|
||||
|
||||
@Bean(name = "corsConfigurationSource")
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.setAllowedOrigins(List.of("http://localhost:5173"));
|
||||
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
|
||||
config.setAllowedHeaders(List.of("*"));
|
||||
config.setAllowCredentials(true);
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
return source;
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
package com.backend.user.service.anyame.controller;
|
||||
|
||||
import com.backend.user.service.anyame.dto.AuthResponse;
|
||||
import com.backend.user.service.anyame.dto.LoginRequest;
|
||||
import com.backend.user.service.anyame.dto.RegisterRequest;
|
||||
import com.backend.user.service.anyame.exception.UserAlreadyExistsException;
|
||||
import com.backend.user.service.anyame.exception.InvalidCredentialsException;
|
||||
import com.backend.user.service.anyame.exception.InvalidRoleException;
|
||||
import com.backend.user.service.anyame.exception.NoExpiryDurationException;
|
||||
import com.backend.user.service.anyame.exception.UnsafePasswordException;
|
||||
import com.backend.user.service.anyame.service.AuthService;
|
||||
import org.springframework.http.HttpStatus;
|
||||
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;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
public class AuthController {
|
||||
|
||||
private final AuthService authService;
|
||||
|
||||
public AuthController(AuthService authService) {
|
||||
this.authService = authService;
|
||||
}
|
||||
|
||||
@PostMapping("/register")
|
||||
public ResponseEntity<AuthResponse> register(@RequestBody RegisterRequest request) {
|
||||
try {
|
||||
return ResponseEntity.ok(authService.register(request));
|
||||
} catch (UnsafePasswordException e) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "unsafe password, length 8-64, 1 upper case, 1 lower case, 1 digit, 1 special symbol. No whitespaces in password.");
|
||||
} catch (UserAlreadyExistsException e) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "user already registered");
|
||||
} catch (NoExpiryDurationException e) {
|
||||
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "no expiry duration found, invalid server config");
|
||||
} catch (InvalidRoleException e) {
|
||||
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "invalid role defined: " + e.getInvalidRole());
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<AuthResponse> login(@RequestBody LoginRequest request) {
|
||||
try {
|
||||
return ResponseEntity.ok(authService.login(request));
|
||||
} catch (InvalidCredentialsException e) {
|
||||
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "invalid credentials", e);
|
||||
} catch (NoExpiryDurationException e) {
|
||||
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "no expiry duration found, invalid server config");
|
||||
} catch (InvalidRoleException e) {
|
||||
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "invalid role defined: " + e.getInvalidRole());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package com.backend.user.service.anyame.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class HelloController {
|
||||
@GetMapping("/hello")
|
||||
public String hello() {
|
||||
return "Hello World";
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
package com.backend.user.service.anyame.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record AuthResponse(String accessToken, String refreshToken, long id, String email, String name, List<String> roles) {
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package com.backend.user.service.anyame.dto;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
public record LoginRequest(
|
||||
@NotBlank(message = "email must not be blank")
|
||||
@Email(message = "email must be valid")
|
||||
String email,
|
||||
@NotNull(message = "password must not be null, but can be blank")
|
||||
String password) {
|
||||
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package com.backend.user.service.anyame.dto;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
public record RegisterRequest(
|
||||
@NotBlank(message = "name must not be blank")
|
||||
@Size(min = 3, max = 16, message = "name min length is 3, max is 16")
|
||||
String name,
|
||||
@NotBlank(message = "email must not be blank")
|
||||
@Email(message = "email must be valid")
|
||||
String email,
|
||||
@NotNull(message = "password must not be null, but can be blank")
|
||||
String password) {
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
package com.backend.user.service.anyame.entity;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.ManyToMany;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@Entity
|
||||
public class Privilege {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
private String name;
|
||||
@ManyToMany(mappedBy = "privileges")
|
||||
private Collection<Role> roles;
|
||||
|
||||
protected Privilege() {
|
||||
}
|
||||
|
||||
public Privilege(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Collection<Role> getRoles() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
package com.backend.user.service.anyame.entity;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.JoinTable;
|
||||
import jakarta.persistence.ManyToMany;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@Entity
|
||||
public class Role {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
@ManyToMany(mappedBy = "roles")
|
||||
private Collection<User> users;
|
||||
|
||||
@ManyToMany
|
||||
@JoinTable(
|
||||
name = "roles_privileges",
|
||||
joinColumns = @JoinColumn(
|
||||
name = "role_id", referencedColumnName = "id"),
|
||||
inverseJoinColumns = @JoinColumn(
|
||||
name = "privilege_id", referencedColumnName = "id"))
|
||||
private Collection<Privilege> privileges;
|
||||
|
||||
protected Role() {
|
||||
}
|
||||
|
||||
public Role(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Collection<User> getUsers() {
|
||||
return users;
|
||||
}
|
||||
|
||||
public Collection<Privilege> getPrivileges() {
|
||||
return privileges;
|
||||
}
|
||||
|
||||
public Role setPrivileges(Collection<Privilege> privileges) {
|
||||
this.privileges = privileges;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
package com.backend.user.service.anyame.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.JoinTable;
|
||||
import jakarta.persistence.ManyToMany;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@Entity
|
||||
@Table(name = "users")
|
||||
public class User {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
private String name;
|
||||
@Column(unique = true, nullable = false)
|
||||
private String email;
|
||||
private String password;
|
||||
private boolean enabled = true;
|
||||
|
||||
@ManyToMany
|
||||
@JoinTable(
|
||||
name = "users_roles",
|
||||
joinColumns = @JoinColumn(
|
||||
name = "user_id", referencedColumnName = "id"),
|
||||
inverseJoinColumns = @JoinColumn(
|
||||
name = "role_id", referencedColumnName = "id"))
|
||||
private Collection<Role> roles;
|
||||
|
||||
protected User() {
|
||||
}
|
||||
|
||||
public User(String name, String email, String password) {
|
||||
this.name = name;
|
||||
this.email = email;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public User(String name, String email) {
|
||||
this.name = name;
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public User setPassword(String password) {
|
||||
this.password = password;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public User setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Collection<Role> getRoles() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
public User setRoles(Collection<Role> roles) {
|
||||
this.roles = roles;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,9 +0,0 @@
|
||||
package com.backend.user.service.anyame.exception;
|
||||
|
||||
public class InvalidCredentialsException extends Exception {
|
||||
|
||||
public InvalidCredentialsException() {
|
||||
super("invalid credentials");
|
||||
}
|
||||
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package com.backend.user.service.anyame.exception;
|
||||
|
||||
public class InvalidRoleException extends Exception {
|
||||
private final String invalidRole;
|
||||
|
||||
public InvalidRoleException(String invalidRole) {
|
||||
this.invalidRole = invalidRole;
|
||||
}
|
||||
|
||||
public InvalidRoleException(String message, String invalidRole) {
|
||||
super(message);
|
||||
this.invalidRole = invalidRole;
|
||||
}
|
||||
|
||||
public String getInvalidRole() {
|
||||
return invalidRole;
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package com.backend.user.service.anyame.exception;
|
||||
|
||||
public class NoExpiryDurationException extends Exception {
|
||||
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package com.backend.user.service.anyame.exception;
|
||||
|
||||
public class UnsafePasswordException extends Exception {
|
||||
|
||||
public UnsafePasswordException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package com.backend.user.service.anyame.exception;
|
||||
|
||||
public class UserAlreadyExistsException extends Exception {
|
||||
|
||||
public UserAlreadyExistsException() {
|
||||
super("user already exists");
|
||||
}
|
||||
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package com.backend.user.service.anyame.repository;
|
||||
|
||||
import com.backend.user.service.anyame.entity.Privilege;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface PrivilegeRepository extends JpaRepository<Privilege, Long> {
|
||||
|
||||
Optional<Privilege> findByName(String name);
|
||||
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package com.backend.user.service.anyame.repository;
|
||||
|
||||
import com.backend.user.service.anyame.entity.Role;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface RoleRepository extends JpaRepository<Role, Long> {
|
||||
|
||||
Optional<Role> findByName(String roleName);
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package com.backend.user.service.anyame.repository;
|
||||
|
||||
import com.backend.user.service.anyame.entity.User;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
|
||||
Optional<User> findByEmail(String email);
|
||||
|
||||
Optional<User> findByEmailOrName(String email, String name);
|
||||
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
package com.backend.user.service.anyame.service;
|
||||
|
||||
import com.backend.user.service.anyame.component.DefaultRoleProvider;
|
||||
import com.backend.user.service.anyame.dto.AuthResponse;
|
||||
import com.backend.user.service.anyame.dto.LoginRequest;
|
||||
import com.backend.user.service.anyame.dto.RegisterRequest;
|
||||
import com.backend.user.service.anyame.entity.Role;
|
||||
import com.backend.user.service.anyame.entity.User;
|
||||
import com.backend.user.service.anyame.exception.UserAlreadyExistsException;
|
||||
import com.backend.user.service.anyame.exception.InvalidCredentialsException;
|
||||
import com.backend.user.service.anyame.exception.InvalidRoleException;
|
||||
import com.backend.user.service.anyame.exception.NoExpiryDurationException;
|
||||
import com.backend.user.service.anyame.exception.UnsafePasswordException;
|
||||
import com.backend.user.service.anyame.repository.UserRepository;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class AuthService {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final JwtService jwtService;
|
||||
private final PasswordValidatorService passwordValidator;
|
||||
private final DefaultRoleProvider defaultRoleProvider;
|
||||
|
||||
public AuthService(UserRepository userRepository,
|
||||
PasswordEncoder passwordEncoder,
|
||||
JwtService jwtService,
|
||||
PasswordValidatorService passwordValidator,
|
||||
DefaultRoleProvider defaultRoleProvider) {
|
||||
this.userRepository = userRepository;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
this.jwtService = jwtService;
|
||||
this.passwordValidator = passwordValidator;
|
||||
this.defaultRoleProvider = defaultRoleProvider;
|
||||
}
|
||||
|
||||
public AuthResponse register(
|
||||
RegisterRequest request) throws UnsafePasswordException, UserAlreadyExistsException, NoExpiryDurationException, InvalidRoleException {
|
||||
if (userRepository.findByEmailOrName(request.email(), request.name()).isPresent()) {
|
||||
throw new UserAlreadyExistsException();
|
||||
}
|
||||
|
||||
User user = new User(request.name(), request.email());
|
||||
user.setRoles(Collections.singleton(defaultRoleProvider.getDefaultRole()));
|
||||
|
||||
if (request.password() != null && !request.password().isBlank()) {
|
||||
if (!passwordValidator.validate(request.password())) {
|
||||
throw new UnsafePasswordException("unsafe password");
|
||||
}
|
||||
user.setPassword(passwordEncoder.encode(request.password()));
|
||||
}
|
||||
user = userRepository.save(user);
|
||||
|
||||
return generateAuthResponse(user);
|
||||
}
|
||||
|
||||
public AuthResponse login(LoginRequest request) throws InvalidCredentialsException, NoExpiryDurationException, InvalidRoleException {
|
||||
User user = userRepository.findByEmail(request.email())
|
||||
.orElseThrow(() -> new RuntimeException("Invalid credentials"));
|
||||
|
||||
if (user.getPassword() == null || !passwordEncoder.matches(request.password(), user.getPassword())) {
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
|
||||
return generateAuthResponse(user);
|
||||
}
|
||||
|
||||
private AuthResponse generateAuthResponse(User user) throws NoExpiryDurationException, InvalidRoleException {
|
||||
String accessToken = jwtService.generateAccessToken(user);
|
||||
String refreshToken = jwtService.generateRefreshToken(user);
|
||||
List<String> roles = user.getRoles().stream().map(Role::getName).toList();
|
||||
return new AuthResponse(accessToken, refreshToken, user.getId(), user.getEmail(), user.getName(), roles);
|
||||
}
|
||||
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
package com.backend.user.service.anyame.service;
|
||||
|
||||
import com.backend.user.service.anyame.entity.Privilege;
|
||||
import com.backend.user.service.anyame.entity.Role;
|
||||
import com.backend.user.service.anyame.entity.User;
|
||||
import com.backend.user.service.anyame.exception.InvalidRoleException;
|
||||
import com.backend.user.service.anyame.repository.RoleRepository;
|
||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class AuthorityResolver {
|
||||
private final RoleRepository roleRepository;
|
||||
private final RoleHierarchy roleHierarchy;
|
||||
|
||||
public AuthorityResolver(RoleRepository roleRepository, RoleHierarchy roleHierarchy) {
|
||||
this.roleRepository = roleRepository;
|
||||
this.roleHierarchy = roleHierarchy;
|
||||
}
|
||||
|
||||
public List<String> getAuthorities(User user) throws InvalidRoleException {
|
||||
List<GrantedAuthority> grantedAuthorities = user.getRoles().stream()
|
||||
.map(role -> new SimpleGrantedAuthority(role.getName()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Collection<? extends GrantedAuthority> reachableAuthorities =
|
||||
roleHierarchy.getReachableGrantedAuthorities(grantedAuthorities);
|
||||
|
||||
Set<String> privileges = new HashSet<>();
|
||||
for (GrantedAuthority authority : reachableAuthorities) {
|
||||
String roleName = authority.getAuthority();
|
||||
Role role = roleRepository.findByName(roleName)
|
||||
.orElseThrow(() -> new InvalidRoleException(roleName));
|
||||
if (role != null && role.getPrivileges() != null) {
|
||||
privileges.addAll(role.getPrivileges().stream()
|
||||
.map(Privilege::getName)
|
||||
.collect(Collectors.toSet()));
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> allAuthorities = reachableAuthorities.stream()
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.collect(Collectors.toSet());
|
||||
allAuthorities.addAll(privileges);
|
||||
|
||||
return new ArrayList<>(allAuthorities);
|
||||
}
|
||||
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
package com.backend.user.service.anyame.service;
|
||||
|
||||
import com.backend.user.service.anyame.component.AuthorizationProperties;
|
||||
import com.backend.user.service.anyame.component.JWTSecretProvider;
|
||||
import com.backend.user.service.anyame.entity.Role;
|
||||
import com.backend.user.service.anyame.entity.User;
|
||||
import com.backend.user.service.anyame.exception.InvalidRoleException;
|
||||
import com.backend.user.service.anyame.exception.NoExpiryDurationException;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.JwtBuilder;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Service
|
||||
public class JwtService {
|
||||
|
||||
private final JWTSecretProvider secretProvider;
|
||||
private final AuthorizationProperties authorizationProperties;
|
||||
private final AuthorityResolver authorityResolver;
|
||||
|
||||
public JwtService(JWTSecretProvider secretProvider,
|
||||
AuthorizationProperties authorizationProperties,
|
||||
AuthorityResolver authorityResolver) {
|
||||
this.secretProvider = secretProvider;
|
||||
this.authorizationProperties = authorizationProperties;
|
||||
this.authorityResolver = authorityResolver;
|
||||
}
|
||||
|
||||
public String generateRefreshToken(User user) throws InvalidRoleException, NoExpiryDurationException {
|
||||
List<String> authorities = authorityResolver.getAuthorities(user);
|
||||
List<String> roles = user.getRoles().stream()
|
||||
.map(Role::getName)
|
||||
.toList();
|
||||
JwtBuilder builder = Jwts.builder()
|
||||
.subject(user.getEmail())
|
||||
.claim("name", user.getName())
|
||||
.claim("id", user.getId())
|
||||
.claim("roles", roles)
|
||||
.claim("authorities", authorities)
|
||||
.claim("type", "refresh");
|
||||
Duration refreshExpiry = getMaxExpiry(user,
|
||||
role -> authorizationProperties.getRefreshExpiry(role.getName()))
|
||||
.orElseThrow(NoExpiryDurationException::new);
|
||||
|
||||
Date issuedAt = new Date();
|
||||
Date expiryDate = Date.from(Instant.now().plus(refreshExpiry));
|
||||
|
||||
return builder.issuedAt(issuedAt)
|
||||
.expiration(expiryDate)
|
||||
.signWith(secretProvider.getSecretKey())
|
||||
.compact();
|
||||
}
|
||||
|
||||
public String generateAccessToken(User user) throws InvalidRoleException, NoExpiryDurationException {
|
||||
List<String> authorities = authorityResolver.getAuthorities(user);
|
||||
List<String> roles = user.getRoles().stream()
|
||||
.map(Role::getName)
|
||||
.toList();
|
||||
JwtBuilder builder = Jwts.builder()
|
||||
.subject(user.getEmail())
|
||||
.claim("name", user.getName())
|
||||
.claim("id", user.getId())
|
||||
.claim("roles", roles)
|
||||
.claim("authorities", authorities)
|
||||
.claim("type", "access");
|
||||
Duration accessExpiry = getMaxExpiry(user,
|
||||
role -> authorizationProperties.getAccessExpiry(role.getName()))
|
||||
.orElseThrow(NoExpiryDurationException::new);
|
||||
|
||||
Date issuedAt = new Date();
|
||||
Date expiryDate = Date.from(Instant.now().plus(accessExpiry));
|
||||
|
||||
return builder.issuedAt(issuedAt)
|
||||
.expiration(expiryDate)
|
||||
.signWith(secretProvider.getSecretKey())
|
||||
.compact();
|
||||
}
|
||||
|
||||
private Optional<Duration> getMaxExpiry(User user, Function<Role, Optional<Duration>> mapper) {
|
||||
return user.getRoles().stream()
|
||||
.map(mapper)
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.max(Duration::compareTo);
|
||||
}
|
||||
|
||||
public String extractEmail(String token) {
|
||||
return getClaims(token).getSubject();
|
||||
}
|
||||
|
||||
public boolean isTokenValid(String token, User user) {
|
||||
return extractEmail(token).equals(user.getEmail()) && !isTokenExpired(token);
|
||||
}
|
||||
|
||||
private boolean isTokenExpired(String token) {
|
||||
return getClaims(token).getExpiration().before(new Date());
|
||||
}
|
||||
|
||||
private Claims getClaims(String token) {
|
||||
return Jwts.parser()
|
||||
.decryptWith(secretProvider.getSecretKey())
|
||||
.build()
|
||||
.parseEncryptedClaims(token)
|
||||
.getPayload();
|
||||
}
|
||||
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package com.backend.user.service.anyame.service;
|
||||
|
||||
import org.passay.PasswordData;
|
||||
import org.passay.PasswordValidator;
|
||||
import org.passay.RuleResult;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class PasswordValidatorService {
|
||||
|
||||
private final PasswordValidator passwordValidator;
|
||||
|
||||
public PasswordValidatorService(PasswordValidator passwordValidator) {
|
||||
this.passwordValidator = passwordValidator;
|
||||
}
|
||||
|
||||
public boolean validate(String password) {
|
||||
RuleResult result = passwordValidator.validate(new PasswordData(password));
|
||||
// TODO: Add HaveBeenIPwned support?
|
||||
return result.isValid();
|
||||
}
|
||||
|
||||
}
|
@ -18,13 +18,32 @@ authorization:
|
||||
default-role: ROLE_USER
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: anyame-user-service
|
||||
security:
|
||||
oauth2:
|
||||
authorizationserver:
|
||||
issuer-url: http://localhost:8080
|
||||
introspection-endpoint: /oauth2/token-info
|
||||
datasource:
|
||||
url: ${DATABASE_URL}
|
||||
username: ${DATABASE_USERNAME}
|
||||
password: ${DATABASE_PASSWORD}
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: none
|
||||
ddl-auto: create
|
||||
|
||||
logging:
|
||||
level:
|
||||
root: DEBUG
|
||||
org.springframework.security: DEBUG
|
||||
logging.level.org.springframework.web: DEBUG
|
||||
logging.level.org.springframework.security.oauth2: TRACE
|
||||
org.apache.tomcat.util.net.NioEndpoint: ERROR
|
||||
sun.rmi: ERROR
|
||||
java.io: ERROR
|
||||
javax.management: ERROR
|
||||
|
||||
server:
|
||||
error:
|
||||
include-message: always
|
Reference in New Issue
Block a user