From 3baccc689d62175643478d63301c6638397e0ee6 Mon Sep 17 00:00:00 2001 From: annakhuseinova Date: Sat, 25 Jul 2020 18:52:53 +0300 Subject: [PATCH 1/5] 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/5] 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/5] 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/5] 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/5] 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"))