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..46398030a
--- /dev/null
+++ b/src/main/java/guru/sfg/brewery/config/SecurityConfig.java
@@ -0,0 +1,75 @@
+package guru.sfg.brewery.config;
+
+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.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
+ .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()
+ .and()
+ .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);
+// }
+}
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/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"));
+ }
+
+}
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"))