From 3baccc689d62175643478d63301c6638397e0ee6 Mon Sep 17 00:00:00 2001 From: annakhuseinova Date: Sat, 25 Jul 2020 18:52:53 +0300 Subject: [PATCH 1/6] Added tests for basic authentication --- .../web/controllers/BeerControllerIT.java | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 src/test/java/guru/sfg/brewery/web/controllers/BeerControllerIT.java diff --git a/src/test/java/guru/sfg/brewery/web/controllers/BeerControllerIT.java b/src/test/java/guru/sfg/brewery/web/controllers/BeerControllerIT.java new file mode 100644 index 000000000..f0ce21823 --- /dev/null +++ b/src/test/java/guru/sfg/brewery/web/controllers/BeerControllerIT.java @@ -0,0 +1,78 @@ +package guru.sfg.brewery.web.controllers; + +import guru.sfg.brewery.repositories.BeerInventoryRepository; +import guru.sfg.brewery.repositories.BeerRepository; +import guru.sfg.brewery.repositories.CustomerRepository; +import guru.sfg.brewery.services.BeerService; +import guru.sfg.brewery.services.BreweryService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Created by jt on 6/12/20. + */ +@WebMvcTest +public class BeerControllerIT { + + + // WebApplicationContext will allow us to leverage filters used by Spring Security in order to test authentication + @Autowired + WebApplicationContext wac; + + MockMvc mockMvc; + + @MockBean + BeerRepository beerRepository; + + @MockBean + BeerInventoryRepository beerInventoryRepository; + + @MockBean + BreweryService breweryService; + + @MockBean + CustomerRepository customerRepository; + + @MockBean + BeerService beerService; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders + .webAppContextSetup(wac) + // activates spring security filters + .apply(springSecurity()) + .build(); + } + // Здесь просто создаем мокового юзера с username spring. + @WithMockUser("spring") + @Test + void findBeers() throws Exception{ + mockMvc.perform(get("/beers/find")) + .andExpect(status().isOk()) + .andExpect(view().name("beers/findBeers")) + .andExpect(model().attributeExists("beer")); + } + + // Здесь с помощью метода with() теперь проверяем базовую аутентификацию. + @Test + void findBeersWithHttpBasic() throws Exception{ + mockMvc.perform(get("/beers/find").with(httpBasic("spring", "guru"))) + .andExpect(status().isOk()) + .andExpect(view().name("beers/findBeers")) + .andExpect(model().attributeExists("beer")); + } + +} From a0c1ff010e907eb34adebbce2c9bd44ba4ae428d Mon Sep 17 00:00:00 2001 From: annakhuseinova Date: Sat, 25 Jul 2020 19:44:13 +0300 Subject: [PATCH 2/6] Added basic java security config --- pom.xml | 4 ++++ .../sfg/brewery/config/SecurityConfig.java | 24 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 src/main/java/guru/sfg/brewery/config/SecurityConfig.java diff --git a/pom.xml b/pom.xml index df7a6cb36..1563420ba 100644 --- a/pom.xml +++ b/pom.xml @@ -143,6 +143,10 @@ spring-security-test test + + org.springframework.boot + spring-boot-starter-security + diff --git a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java new file mode 100644 index 000000000..a45b28521 --- /dev/null +++ b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java @@ -0,0 +1,24 @@ +package guru.sfg.brewery.config; + +import org.springframework.context.annotation.Configuration; +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.WebSecurityConfigurerAdapter; + +@Configuration +@EnableWebSecurity +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests(authorize -> { + authorize.antMatchers("/", "/webjars/**", "/login", "/resources/**").permitAll(); + } ) + .authorizeRequests() + .anyRequest().authenticated() + .and() + .formLogin().and() + .httpBasic(); + } +} From fbc31b00189038cbfa24ce1fea6e499f77ebbf7b Mon Sep 17 00:00:00 2001 From: annakhuseinova Date: Sat, 25 Jul 2020 20:00:41 +0300 Subject: [PATCH 3/6] Added mvc matchers --- .../java/guru/sfg/brewery/config/SecurityConfig.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java index a45b28521..587f91346 100644 --- a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java +++ b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java @@ -1,6 +1,7 @@ package guru.sfg.brewery.config; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; 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.WebSecurityConfigurerAdapter; @@ -11,9 +12,13 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { - http + http .authorizeRequests(authorize -> { - authorize.antMatchers("/", "/webjars/**", "/login", "/resources/**").permitAll(); + authorize + .antMatchers("/", "/webjars/**", "/login", "/resources/**").permitAll() + .antMatchers("/beers/find", "/beers*").permitAll() + .antMatchers(HttpMethod.GET, "/api/v1/beer/**").permitAll() + .mvcMatchers(HttpMethod.GET, "/api/v1/beerUpc/{upc}").permitAll(); } ) .authorizeRequests() .anyRequest().authenticated() From 6f254a470441c993f635bd901a19bae7122dead3 Mon Sep 17 00:00:00 2001 From: annakhuseinova Date: Sat, 25 Jul 2020 22:10:22 +0300 Subject: [PATCH 4/6] Added in memory authentication using 2 techniques --- .../sfg/brewery/config/SecurityConfig.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java index 587f91346..61740191f 100644 --- a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java +++ b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java @@ -1,10 +1,16 @@ package guru.sfg.brewery.config; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; +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.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +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; @Configuration @EnableWebSecurity @@ -26,4 +32,37 @@ protected void configure(HttpSecurity http) throws Exception { .formLogin().and() .httpBasic(); } + + // Configuring In Memory Authentication using Authentication Fluent API. + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.inMemoryAuthentication() + .withUser("spring") + .password("guru") + .roles() + .and() + .withUser("user") + .password("{noop}password") + .roles("USER"); + + } + + // This is kinda an old way of creating InMemory UserDetails Service. Alternatively, we can use + // In Memory Authentication Fluent API. See above +// @Override +// @Bean +// protected UserDetailsService userDetailsService() { +// UserDetails admin = User.withDefaultPasswordEncoder() +// .username("spring") +// .password("guru") +// .roles("ADMIN") +// .build(); +// UserDetails user = User.withDefaultPasswordEncoder() +// .username("user") +// .password("password") +// .roles("USER") +// .build(); +// // InMemoryUserDetailsManager implements UserDetailsService thus overriding default UserDetailsService +// return new InMemoryUserDetailsManager(admin, user); +// } } From 62a69f7f79a1b016f614157882cc0e982c220eea Mon Sep 17 00:00:00 2001 From: annakhuseinova Date: Sun, 26 Jul 2020 12:07:30 +0300 Subject: [PATCH 5/6] Added custom authentication filter --- .../sfg/brewery/config/SecurityConfig.java | 17 ++- .../brewery/filter/RestHeaderAuthFilter.java | 112 ++++++++++++++++++ .../web/controllers/BeerControllerTest.java | 2 + 3 files changed, 126 insertions(+), 5 deletions(-) create mode 100644 src/main/java/guru/sfg/brewery/filter/RestHeaderAuthFilter.java diff --git a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java index 61740191f..46398030a 100644 --- a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java +++ b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java @@ -1,23 +1,30 @@ package guru.sfg.brewery.config; -import org.springframework.context.annotation.Bean; +import guru.sfg.brewery.filter.RestHeaderAuthFilter; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; 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.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -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.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { + public RestHeaderAuthFilter restHeaderAuthFilter(AuthenticationManager authenticationManager){ + RestHeaderAuthFilter restHeaderAuthFilter = new RestHeaderAuthFilter(new AntPathRequestMatcher("/api/**")); + restHeaderAuthFilter.setAuthenticationManager(authenticationManager); + return restHeaderAuthFilter; + } + // By default, Spring Security turns on csrf protection. @Override protected void configure(HttpSecurity http) throws Exception { + http.addFilterBefore(restHeaderAuthFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class) + .csrf().disable(); http .authorizeRequests(authorize -> { authorize diff --git a/src/main/java/guru/sfg/brewery/filter/RestHeaderAuthFilter.java b/src/main/java/guru/sfg/brewery/filter/RestHeaderAuthFilter.java new file mode 100644 index 000000000..42de9088f --- /dev/null +++ b/src/main/java/guru/sfg/brewery/filter/RestHeaderAuthFilter.java @@ -0,0 +1,112 @@ +package guru.sfg.brewery.filter; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.security.authentication.InternalAuthenticationServiceException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.util.StringUtils; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * AbstractAuthenticationProcessingFilter - abstract processor of browser-based HTTP-based authentication requests. + * The filter requires that you set the authenticationManager property. An authenticationManager is required to process + * the authentication request tokens creating by implementing classes. This filter will intercept a request and attempt + * to perform authentication from that request if the request URL matches the value of the filterProcessesUrl property. + * This behaviour can be modified by overriding the method requiresAuthentication. Authentication is performed by the + * attemptAuthentication method, which must be implemented by subclasses. + * + * If authentication is successful, the resulting Authentication object will be placed into the SecurityContext + * for the current thread, which is guaranteed to have already been created by an earlier filter. + * */ +@Slf4j +public class RestHeaderAuthFilter extends AbstractAuthenticationProcessingFilter { + + public RestHeaderAuthFilter(RequestMatcher requiresAuthenticationRequestMatcher) { + super(requiresAuthenticationRequestMatcher); + } + + @Override + public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException { + + String userName = getUsername(httpServletRequest); + String password = getPassword(httpServletRequest); + + if (userName == null){ + userName = ""; + } + if (password == null){ + password = ""; + } + log.debug("Authenticating user: {}", userName); + + // An Authentication implementation that is designed for simple presentation of a username and a password. + // Needed for work with authentication manager. + UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userName, password); + // AuthenticationManager is configured as InMemory in fluent api setting + if (!StringUtils.isEmpty(userName)){ + return this.getAuthenticationManager().authenticate(token); + } + return null; + } + + private String getPassword(HttpServletRequest httpServletRequest) { + return httpServletRequest.getHeader("Api-Secret"); + } + + private String getUsername(HttpServletRequest httpServletRequest) { + return httpServletRequest.getHeader("Api-Key"); + } + + @Override + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest)req; + HttpServletResponse response = (HttpServletResponse)res; + if (!this.requiresAuthentication(request, response)) { + chain.doFilter(request, response); + } else { + if (this.logger.isDebugEnabled()) { + this.logger.debug("Request is to process authentication"); + } + Authentication authResult = attemptAuthentication(request, response); + try { + if (authResult != null){ + this.successfulAuthentication(request, response, chain, authResult); + }else { + chain.doFilter(request, response); + } + } catch (AuthenticationException e){ + log.error("Authentication Failed"); + unsuccessfulAuthentication(request, response, e); + } + } + } + + protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { + if (this.logger.isDebugEnabled()) { + this.logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult); + } + SecurityContextHolder.getContext().setAuthentication(authResult); + } + + protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { + SecurityContextHolder.clearContext(); + if (this.log.isDebugEnabled()) { + log.debug("Authentication request failed: " + failed.toString(), failed); + log.debug("Updated SecurityContextHolder to contain null Authentication"); + } + response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); + } +} diff --git a/src/test/java/guru/sfg/brewery/web/controllers/BeerControllerTest.java b/src/test/java/guru/sfg/brewery/web/controllers/BeerControllerTest.java index 0efa7484d..196bef31b 100644 --- a/src/test/java/guru/sfg/brewery/web/controllers/BeerControllerTest.java +++ b/src/test/java/guru/sfg/brewery/web/controllers/BeerControllerTest.java @@ -30,6 +30,7 @@ import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; +import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -105,6 +106,7 @@ void showBeer() throws Exception{ .andExpect(model().attribute("beer", hasProperty("id", is(uuid)))); } + AuthenticationEntryPoint @Test void initCreationForm() throws Exception { mockMvc.perform(get("/beers/new")) From b917af0aa4fa42a9ec3545b31ada4c92f37471fb Mon Sep 17 00:00:00 2001 From: annakhuseinova Date: Sun, 26 Jul 2020 16:06:58 +0300 Subject: [PATCH 6/6] Added databse authentication --- .../sfg/brewery/config/SecurityConfig.java | 4 ++ .../brewery/domain/security/Authority.java | 22 +++++++++ .../sfg/brewery/domain/security/User.java | 36 ++++++++++++++ .../brewery/filter/RestHeaderAuthFilter.java | 2 - .../security/AuthorityRepository.java | 9 ++++ .../repositories/security/UserRepository.java | 11 +++++ .../services/JpaUserDetailsService.java | 49 +++++++++++++++++++ 7 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 src/main/java/guru/sfg/brewery/domain/security/Authority.java create mode 100644 src/main/java/guru/sfg/brewery/domain/security/User.java create mode 100644 src/main/java/guru/sfg/brewery/repositories/security/AuthorityRepository.java create mode 100644 src/main/java/guru/sfg/brewery/repositories/security/UserRepository.java create mode 100644 src/main/java/guru/sfg/brewery/services/JpaUserDetailsService.java diff --git a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java index 46398030a..05fb8fc3d 100644 --- a/src/main/java/guru/sfg/brewery/config/SecurityConfig.java +++ b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java @@ -28,6 +28,7 @@ protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests(authorize -> { authorize + .antMatchers("/h2-console/**").permitAll() // do not use in production .antMatchers("/", "/webjars/**", "/login", "/resources/**").permitAll() .antMatchers("/beers/find", "/beers*").permitAll() .antMatchers(HttpMethod.GET, "/api/v1/beer/**").permitAll() @@ -38,6 +39,9 @@ protected void configure(HttpSecurity http) throws Exception { .and() .formLogin().and() .httpBasic(); + + // h2 console config + http.headers().frameOptions().sameOrigin(); } // Configuring In Memory Authentication using Authentication Fluent API. diff --git a/src/main/java/guru/sfg/brewery/domain/security/Authority.java b/src/main/java/guru/sfg/brewery/domain/security/Authority.java new file mode 100644 index 000000000..c817d49d4 --- /dev/null +++ b/src/main/java/guru/sfg/brewery/domain/security/Authority.java @@ -0,0 +1,22 @@ +package guru.sfg.brewery.domain.security; + +import lombok.*; + +import javax.persistence.*; +import java.util.Set; + +@Entity +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class Authority { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private String role; + + @ManyToMany(mappedBy = "authorities") + private Set users; +} diff --git a/src/main/java/guru/sfg/brewery/domain/security/User.java b/src/main/java/guru/sfg/brewery/domain/security/User.java new file mode 100644 index 000000000..28f31dc9a --- /dev/null +++ b/src/main/java/guru/sfg/brewery/domain/security/User.java @@ -0,0 +1,36 @@ +package guru.sfg.brewery.domain.security; + +import lombok.*; + +import javax.persistence.*; +import java.util.Set; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Builder +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Integer id; + private String username; + private String password; + @Singular + @ManyToMany(cascade = CascadeType.MERGE) + @JoinTable(name = "user_authority", + joinColumns = {@JoinColumn(name = "USER_ID", referencedColumnName = "ID")}, + inverseJoinColumns = {@JoinColumn(name = "AUTHORITY_ID", referencedColumnName = "ID")}) + private Set authorities; + // @Builder.Default - для того, чтобы в @Builder можно было задать дефолтное значение + @Builder.Default + private Boolean accountNonExpired = true; + @Builder.Default + private Boolean accountNonLocked = true; + @Builder.Default + private Boolean credentialsNonExpired = true; + @Builder.Default + private Boolean enabled = true; +} diff --git a/src/main/java/guru/sfg/brewery/filter/RestHeaderAuthFilter.java b/src/main/java/guru/sfg/brewery/filter/RestHeaderAuthFilter.java index 42de9088f..8e75ed2e3 100644 --- a/src/main/java/guru/sfg/brewery/filter/RestHeaderAuthFilter.java +++ b/src/main/java/guru/sfg/brewery/filter/RestHeaderAuthFilter.java @@ -2,9 +2,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; -import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; diff --git a/src/main/java/guru/sfg/brewery/repositories/security/AuthorityRepository.java b/src/main/java/guru/sfg/brewery/repositories/security/AuthorityRepository.java new file mode 100644 index 000000000..e1b51ac24 --- /dev/null +++ b/src/main/java/guru/sfg/brewery/repositories/security/AuthorityRepository.java @@ -0,0 +1,9 @@ +package guru.sfg.brewery.repositories.security; + +import guru.sfg.brewery.domain.security.Authority; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AuthorityRepository extends JpaRepository { + + +} diff --git a/src/main/java/guru/sfg/brewery/repositories/security/UserRepository.java b/src/main/java/guru/sfg/brewery/repositories/security/UserRepository.java new file mode 100644 index 000000000..5a27e7802 --- /dev/null +++ b/src/main/java/guru/sfg/brewery/repositories/security/UserRepository.java @@ -0,0 +1,11 @@ +package guru.sfg.brewery.repositories.security; + +import guru.sfg.brewery.domain.security.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface UserRepository extends JpaRepository { + + Optional findByUsername(String username); +} diff --git a/src/main/java/guru/sfg/brewery/services/JpaUserDetailsService.java b/src/main/java/guru/sfg/brewery/services/JpaUserDetailsService.java new file mode 100644 index 000000000..6e5ecd393 --- /dev/null +++ b/src/main/java/guru/sfg/brewery/services/JpaUserDetailsService.java @@ -0,0 +1,49 @@ +package guru.sfg.brewery.services; + +import guru.sfg.brewery.domain.security.Authority; +import guru.sfg.brewery.domain.security.User; +import guru.sfg.brewery.repositories.security.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +@Service +public class JpaUserDetailsService implements UserDetailsService { + + private final UserRepository userRepository; + + // We are using @Transactional for executing this method in one Hibernate context so that we do not need + // to configure user to load authorities eagerly (we could alternatively do that). + @Transactional + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = userRepository.findByUsername(username).orElseThrow(()-> { + throw new UsernameNotFoundException("User name: " + username + " not found"); + }); + // User из пакета org.springframework.security.core.userdetails.User implements UserDetails interface and is + // immutable. + return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), + user.getEnabled(), user.getAccountNonExpired(), user.getCredentialsNonExpired(), user.getAccountNonLocked(), + convertToSpringAuthorities(user.getAuthorities())); + } + + private Collection convertToSpringAuthorities(Set authorities) { + if (authorities != null && authorities.size() > 0){ + return authorities.stream().map(Authority::getRole).map(SimpleGrantedAuthority::new) + .collect(Collectors.toSet()); + }else { + return new HashSet<>(); + } + } +}