From 25b0b34b8df292cae4213817198d26bc314f796b Mon Sep 17 00:00:00 2001 From: djmil Date: Fri, 25 Aug 2023 23:45:14 +0200 Subject: [PATCH] SpringBoot: ApiUser ShortHashService implemented in form of CutomUserDetails, which is injected into endpint if user was sucessfully authorized --- .../djmil/cordacheckers/ApiController.java | 6 +- .../djmil/cordacheckers/ApiUserDetails.java | 19 ++++++ .../cordacheckers/ApiUserDetailsService.java | 45 ++++++++++++++ .../ApiUserShortHashService.java | 51 ++++++++++++++++ .../CustomAuthenticationProvider.java | 46 +++++++++++++++ .../djmil/cordacheckers/SecurityConfig.java | 59 +++++++++++++------ 6 files changed, 204 insertions(+), 22 deletions(-) create mode 100644 backend/src/main/java/djmil/cordacheckers/ApiUserDetails.java create mode 100644 backend/src/main/java/djmil/cordacheckers/ApiUserDetailsService.java create mode 100644 backend/src/main/java/djmil/cordacheckers/ApiUserShortHashService.java create mode 100644 backend/src/main/java/djmil/cordacheckers/CustomAuthenticationProvider.java diff --git a/backend/src/main/java/djmil/cordacheckers/ApiController.java b/backend/src/main/java/djmil/cordacheckers/ApiController.java index 3970d61..6945ac5 100644 --- a/backend/src/main/java/djmil/cordacheckers/ApiController.java +++ b/backend/src/main/java/djmil/cordacheckers/ApiController.java @@ -1,10 +1,10 @@ package djmil.cordacheckers; -import java.security.Principal; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @@ -32,9 +32,9 @@ public class ApiController { * @return a Json list of active games */ @GetMapping("/api/activegames") - public ResponseEntity dashboard(Principal principal) { + public ResponseEntity dashboard(@AuthenticationPrincipal ApiUserDetails user) { - return ResponseEntity.ok("{ \"ActiveGames\" : [\"game\", \"GAME\", \""+principal.getName()+ "\" ] }" ); + return ResponseEntity.ok("{ \"ActiveGames\" : [\"game\", \"GAME\", \""+user.getShortHash()+ "\" ] }" ); } } \ No newline at end of file diff --git a/backend/src/main/java/djmil/cordacheckers/ApiUserDetails.java b/backend/src/main/java/djmil/cordacheckers/ApiUserDetails.java new file mode 100644 index 0000000..752f708 --- /dev/null +++ b/backend/src/main/java/djmil/cordacheckers/ApiUserDetails.java @@ -0,0 +1,19 @@ +package djmil.cordacheckers; + +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; + +public class ApiUserDetails extends User { + private final String shortHash; + + public ApiUserDetails(UserDetails user, String shortHash) { + super(user.getUsername(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(), user.isCredentialsNonExpired(), user.isAccountNonLocked(), user.getAuthorities()); + + this.shortHash = shortHash; + } + + public String getShortHash() { + return this.shortHash; + } + +} \ No newline at end of file diff --git a/backend/src/main/java/djmil/cordacheckers/ApiUserDetailsService.java b/backend/src/main/java/djmil/cordacheckers/ApiUserDetailsService.java new file mode 100644 index 0000000..fcb4455 --- /dev/null +++ b/backend/src/main/java/djmil/cordacheckers/ApiUserDetailsService.java @@ -0,0 +1,45 @@ +package djmil.cordacheckers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +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.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +@Service +public class ApiUserDetailsService implements UserDetailsService { + + @Autowired + @Lazy + private PasswordEncoder encoder; + + @Autowired + @Lazy + ApiUserShortHashService apiUserShortHash; + + @Override + public ApiUserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + // Load user from database + // User user = userRepository.findByUsername(username); + + // if (user == null) { + // throw new UsernameNotFoundException("User not found"); + // } + + System.out.println("Load user "+username); + + User.UserBuilder users = User.builder(); + + UserDetails alice = users + .username(username) + .password(encoder.encode("qaz123")) + .build(); + + ApiUserDetails apiUser = new ApiUserDetails(alice, apiUserShortHash.getShortHashBy(username)); + + return apiUser; + } +} diff --git a/backend/src/main/java/djmil/cordacheckers/ApiUserShortHashService.java b/backend/src/main/java/djmil/cordacheckers/ApiUserShortHashService.java new file mode 100644 index 0000000..ae0dd93 --- /dev/null +++ b/backend/src/main/java/djmil/cordacheckers/ApiUserShortHashService.java @@ -0,0 +1,51 @@ +package djmil.cordacheckers; + +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import javax.naming.InvalidNameException; + +import org.springframework.stereotype.Service; + +import djmil.cordacheckers.cordaclient.CordaClient; +import djmil.cordacheckers.cordaclient.pojo.virtualNodes; + +@Service +public class ApiUserShortHashService { + static final Locale locale = Locale.getDefault(); + + Map apiUserShortHashMap; + + ApiUserShortHashService(CordaClient client) { + this.apiUserShortHashMap = setApiUserShortHashMap(client); + } + + private static Map setApiUserShortHashMap(CordaClient client) { + Map map = new HashMap<>(); + + List vNodesList = client.getVirtualnode(); + + try { + for (virtualNodes vNode : vNodesList) { + var identity = vNode.holdingIdentity(); + + if (identity.isPlayer()) { + map.put(identity.getName().toLowerCase(locale), identity.shortHash()); + } + } + } catch (InvalidNameException e) { + // TODO: logs + System.out.println("Unable to get ShorHash map for vNode: "+e.getExplanation()); + e.printStackTrace(); + } + + System.out.println("ApiUserShortHashMap " + map); + return map; + } + + String getShortHashBy(String apiUserName) { + return this.apiUserShortHashMap.get(apiUserName.toLowerCase(locale)); + } +} diff --git a/backend/src/main/java/djmil/cordacheckers/CustomAuthenticationProvider.java b/backend/src/main/java/djmil/cordacheckers/CustomAuthenticationProvider.java new file mode 100644 index 0000000..58d4275 --- /dev/null +++ b/backend/src/main/java/djmil/cordacheckers/CustomAuthenticationProvider.java @@ -0,0 +1,46 @@ +package djmil.cordacheckers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +@Service +public class CustomAuthenticationProvider implements AuthenticationProvider { + + @Autowired + @Lazy + private PasswordEncoder encoder; + + @Autowired + private ApiUserDetailsService userDetailsService; + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + String username = authentication.getName(); + String password = authentication.getCredentials().toString(); + + ApiUserDetails user = userDetailsService.loadUserByUsername(username); + + return checkPassword(user, password); + } + + @Override + public boolean supports(Class aClass) { + return UsernamePasswordAuthenticationToken.class.isAssignableFrom(aClass); + } + + private Authentication checkPassword(ApiUserDetails user, String rawPassword) { + if (encoder.matches(rawPassword, user.getPassword())) { + return new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities()); + } else { + throw new BadCredentialsException("Bad credentials"); + } + } + +} diff --git a/backend/src/main/java/djmil/cordacheckers/SecurityConfig.java b/backend/src/main/java/djmil/cordacheckers/SecurityConfig.java index 9ea149d..0fcf2c6 100644 --- a/backend/src/main/java/djmil/cordacheckers/SecurityConfig.java +++ b/backend/src/main/java/djmil/cordacheckers/SecurityConfig.java @@ -1,38 +1,59 @@ package djmil.cordacheckers; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; - -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; + +import static org.springframework.security.config.Customizer.withDefaults; @Configuration +@EnableWebSecurity public class SecurityConfig { @Bean PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); + return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } - @Bean - UserDetailsService hardcodedCordaUsers(PasswordEncoder passwordEncoder) { - User.UserBuilder users = User.builder(); + @Autowired + CustomAuthenticationProvider authenticationProvider; + + @Autowired + public void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.authenticationProvider(authenticationProvider); + } + + // @Bean + // SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { + // http + // .authorizeRequests(authorizeRequests -> + // authorizeRequests.anyRequest().authenticated()); + // .formLogin(withDefaults()); + // return http.build(); + // } + + // @Bean + // UserDetailsService hardcodedCordaUsers(PasswordEncoder passwordEncoder) { + // User.UserBuilder users = User.builder(); - UserDetails alice = users - .username("alice") - .password(passwordEncoder.encode("qaz123")) - .build(); + // UserDetails alice = users + // .username("alice") + // .password(passwordEncoder.encode("qaz123")) + // .build(); - UserDetails bob = users - .username("bob") - .password(passwordEncoder.encode("qaz123")) - .build(); + // UserDetails bob = users + // .username("bob") + // .password(passwordEncoder.encode("qaz123")) + // .build(); - return new InMemoryUserDetailsManager(alice, bob); - } + // return new InMemoryUserDetailsManager(alice, bob); + // } }