Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>

<build>
Expand Down
75 changes: 75 additions & 0 deletions src/main/java/guru/sfg/brewery/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -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);
// }
}
112 changes: 112 additions & 0 deletions src/main/java/guru/sfg/brewery/filter/RestHeaderAuthFilter.java
Original file line number Diff line number Diff line change
@@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -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"));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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"))
Expand Down