Commit 34f22b25 authored by Khalid Ali's avatar Khalid Ali
Browse files

Use generic spring security over Kakawait

parent 21a083b6
......@@ -18,8 +18,10 @@ plugins {
id "org.sonarqube" version "2.6"
}
plugins {
id 'jacoco'
apply plugin: 'jacoco'
jacoco {
toolVersion = "0.8.2"
}
flyway {
......@@ -48,7 +50,6 @@ dependencies {
implementation('com.fasterxml.jackson.dataformat:jackson-dataformat-xml')
implementation('com.fasterxml.jackson.datatype:jackson-datatype-joda')
implementation('org.springframework.security:spring-security-config')
implementation('com.kakawait:cas-security-spring-boot-starter:1.0.0-beta-2')
implementation('org.modelmapper:modelmapper:2.3.1')
compile('org.springframework.security:spring-security-cas')
compile('org.springframework.security:spring-security-test')
......
package com.gmu.bookshare;
import com.kakawait.spring.boot.security.cas.autoconfigure.CasSecurityProperties;
import com.kakawait.spring.security.cas.client.CasAuthorizationInterceptor;
import com.kakawait.spring.security.cas.client.ticket.ProxyTicketProvider;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.session.SingleSignOutHttpSessionListener;
import org.jasig.cas.client.validation.Cas30ServiceTicketValidator;
......@@ -18,13 +15,11 @@ import org.springframework.core.Ordered;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.filter.ForwardedHeaderFilter;
import javax.servlet.http.HttpSessionEvent;
......@@ -49,91 +44,76 @@ public class BookshareApplication {
return filterRegistrationBean;
}
@Bean
RestTemplate casRestTemplate(ServiceProperties serviceProperties, ProxyTicketProvider proxyTicketProvider) {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add(new CasAuthorizationInterceptor(serviceProperties, proxyTicketProvider));
return restTemplate;
}
@Bean
public ServiceProperties serviceProperties() {
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setService("http://localhost:9090/login/cas");
serviceProperties.setService("https://localhost:9090/login/cas");
serviceProperties.setSendRenew(false);
return serviceProperties;
}
// @Bean
// public ServiceProperties serviceProperties() {
// ServiceProperties serviceProperties = new ServiceProperties();
// serviceProperties.setService("http://localhost:9009/login/cas");
// serviceProperties.setSendRenew(false);
// return serviceProperties;
// }
//
// @Bean
// @Primary
// public AuthenticationEntryPoint authenticationEntryPoint(
// ServiceProperties sP) {
//
// // URL where user will be redirected to for authentication
// CasAuthenticationEntryPoint entryPoint
// = new CasAuthenticationEntryPoint();
// entryPoint.setLoginUrl("https://localhost:6443/cas/login");
// entryPoint.setServiceProperties(sP);
// return entryPoint;
// }
//
// /**
// * Validates service ticket given to user upon authentication.
// *
// * @return TicketValidator object
// */
// @Bean
// public TicketValidator ticketValidator() {
// return new Cas30ServiceTicketValidator(
// "https://localhost:6443/cas");
// }
//
// @Bean
// public CasAuthenticationProvider casAuthenticationProvider() {
//
// CasAuthenticationProvider provider = new CasAuthenticationProvider();
// provider.setServiceProperties(serviceProperties());
// provider.setTicketValidator(ticketValidator());
// provider.setUserDetailsService(
// s -> new User("casuser", "Mellon", true, true, true, true,
// AuthorityUtils.createAuthorityList("ROLE_ADMIN")));
// provider.setKey("CAS_PROVIDER_LOCALHOST_9000");
// return provider;
// }
//
// @Bean
// public SecurityContextLogoutHandler securityContextLogoutHandler() {
// return new SecurityContextLogoutHandler();
// }
//
// @Bean
// public LogoutFilter logoutFilter() {
// LogoutFilter logoutFilter = new LogoutFilter(
// "https://localhost:6443/cas/logout",
// securityContextLogoutHandler());
// logoutFilter.setFilterProcessesUrl("/logout/cas");
// return logoutFilter;
// }
//
// @Bean
// public SingleSignOutFilter singleSignOutFilter() {
// SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
// singleSignOutFilter.setCasServerUrlPrefix("https://localhost:6443/cas");
// singleSignOutFilter.setIgnoreInitConfiguration(true);
// return singleSignOutFilter;
// }
//
// @EventListener
// public SingleSignOutHttpSessionListener singleSignOutHttpSessionListener(
// HttpSessionEvent event) {
// return new SingleSignOutHttpSessionListener();
// }
@Bean
@Primary
public AuthenticationEntryPoint authenticationEntryPoint(
ServiceProperties sP) {
// URL where user will be redirected to for authentication
CasAuthenticationEntryPoint entryPoint
= new CasAuthenticationEntryPoint();
entryPoint.setLoginUrl("https://localhost:9443/login");
entryPoint.setServiceProperties(sP);
return entryPoint;
}
/**
* Validates service ticket given to user upon authentication.
*
* @return TicketValidator object
*/
@Bean
public TicketValidator ticketValidator() {
return new Cas30ServiceTicketValidator(
"https://localhost:9443/");
}
@Bean
public CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider provider = new CasAuthenticationProvider();
provider.setServiceProperties(serviceProperties());
provider.setTicketValidator(ticketValidator());
provider.setUserDetailsService(
s -> new User("casuser", "Mellon", true, true, true, true,
AuthorityUtils.createAuthorityList("ROLE_ADMIN")));
provider.setKey("CAS_PROVIDER_LOCALHOST_9000");
return provider;
}
@Bean
public SecurityContextLogoutHandler securityContextLogoutHandler() {
return new SecurityContextLogoutHandler();
}
@Bean
public LogoutFilter logoutFilter() {
LogoutFilter logoutFilter = new LogoutFilter(
"https://localhost:9443/logout",
securityContextLogoutHandler());
logoutFilter.setFilterProcessesUrl("/logout/cas");
return logoutFilter;
}
@Bean
public SingleSignOutFilter singleSignOutFilter() {
SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
singleSignOutFilter.setCasServerUrlPrefix("https://localhost:9443/");
singleSignOutFilter.setIgnoreInitConfiguration(true);
return singleSignOutFilter;
}
@EventListener
public SingleSignOutHttpSessionListener singleSignOutHttpSessionListener(
HttpSessionEvent event) {
return new SingleSignOutHttpSessionListener();
}
}
package com.gmu.bookshare.config;
import com.kakawait.spring.boot.security.cas.autoconfigure.CasHttpSecurityConfigurer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@Configuration
@EnableWebSecurity
public class ApiSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**").authorizeRequests().anyRequest().authenticated();
// Applying CAS security on current HttpSecurity (FilterChain)
// I'm not using .apply() from HttpSecurity due to following issue
// https://github.com/spring-projects/spring-security/issues/4422
CasHttpSecurityConfigurer.cas().configure(http);
http.exceptionHandling().authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
}
}
\ No newline at end of file
package com.gmu.bookshare.config;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
import org.springframework.security.cas.web.CasAuthenticationFilter;
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.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import java.util.Collections;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private AuthenticationProvider authenticationProvider;
private AuthenticationEntryPoint authenticationEntryPoint;
private SingleSignOutFilter singleSignOutFilter;
private LogoutFilter logoutFilter;
@Autowired
public SecurityConfig(CasAuthenticationProvider casAuthenticationProvider, AuthenticationEntryPoint eP,
LogoutFilter lF, SingleSignOutFilter ssF) {
this.authenticationProvider = casAuthenticationProvider;
this.authenticationEntryPoint = eP;
this.logoutFilter = lF;
this.singleSignOutFilter = ssF;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.regexMatchers("/secured.*", "/login", "/bs/api/.*")
.authenticated()
.and()
.authorizeRequests()
.regexMatchers("/")
.permitAll()
.and()
.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.logout().logoutSuccessUrl("/logout")
.and()
.addFilterBefore(singleSignOutFilter, CasAuthenticationFilter.class)
.addFilterBefore(logoutFilter, LogoutFilter.class);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return new ProviderManager(Collections.singletonList(authenticationProvider));
}
@Bean
public CasAuthenticationFilter casAuthenticationFilter(ServiceProperties sP) throws Exception {
CasAuthenticationFilter filter = new CasAuthenticationFilter();
filter.setServiceProperties(sP);
filter.setAuthenticationManager(authenticationManager());
return filter;
}
}
\ No newline at end of file
......@@ -7,8 +7,6 @@ import com.gmu.bookshare.model.ListingDto;
import com.gmu.bookshare.service.BidService;
import com.gmu.bookshare.service.ListingService;
import com.gmu.bookshare.service.ShareUserService;
import com.kakawait.spring.security.cas.client.ticket.ProxyTicketProvider;
import com.kakawait.spring.security.cas.client.validation.AssertionProvider;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
......@@ -20,9 +18,7 @@ import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
......@@ -38,30 +34,15 @@ public class BookshareApiController {
private final BidService bidService;
private final ShareUserService shareUserService;
private final ProxyTicketProvider proxyTicketProvider;
private final AssertionProvider assertionProvider;
@Autowired
private ModelMapper modelMapper;
@Autowired
public BookshareApiController(ListingService listingService, BidService bidService,
ShareUserService shareUserService, ProxyTicketProvider proxyTicketProvider,
AssertionProvider assertionProvider) {
ShareUserService shareUserService) {
this.listingService = listingService;
this.bidService = bidService;
this.shareUserService = shareUserService;
this.proxyTicketProvider = proxyTicketProvider;
this.assertionProvider = assertionProvider;
}
@RequestMapping
public String hello(Authentication authentication, Model model) {
if (authentication != null && StringUtils.hasText(authentication.getName())) {
model.addAttribute("username", authentication.getName());
model.addAttribute("principal", authentication.getPrincipal());
}
return "index";
}
@GetMapping(value = "/listing/", produces = MediaType.APPLICATION_JSON_VALUE)
......@@ -118,31 +99,31 @@ public class BookshareApiController {
return convertBidToDto(bidCreated);
}
// @GetMapping(name = "/login")
// public String index(ModelMap modelMap) {
// Authentication auth = SecurityContextHolder.getContext()
// .getAuthentication();
// if(auth != null
// && auth.getPrincipal() != null
// && auth.getPrincipal() instanceof UserDetails) {
// modelMap.put("username", ((UserDetails) auth.getPrincipal()).getUsername());
// }
// return "secure/index";
// }
//
// @GetMapping("/logout")
// public String logout(
// HttpServletRequest request,
// HttpServletResponse response,
// SecurityContextLogoutHandler logoutHandler) {
// Authentication auth = SecurityContextHolder
// .getContext().getAuthentication();
// logoutHandler.logout(request, response, auth );
// new CookieClearingLogoutHandler(
// AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY)
// .logout(request, response, auth);
// return "auth/logout";
// }
@GetMapping(name = "/login")
public String index(ModelMap modelMap) {
Authentication auth = SecurityContextHolder.getContext()
.getAuthentication();
if (auth != null
&& auth.getPrincipal() != null
&& auth.getPrincipal() instanceof UserDetails) {
modelMap.put("username", ((UserDetails) auth.getPrincipal()).getUsername());
}
return "secured/index";
}
@GetMapping("/logout")
public String logout(
HttpServletRequest request,
HttpServletResponse response,
SecurityContextLogoutHandler logoutHandler) {
Authentication auth = SecurityContextHolder
.getContext().getAuthentication();
logoutHandler.logout(request, response, auth);
new CookieClearingLogoutHandler(
AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY)
.logout(request, response, auth);
return "auth/logout";
}
private ListingDto convertToDto(ListingEntity listingEntity) {
return modelMapper.map(listingEntity, ListingDto.class);
......
package com.gmu.bookshare;
import com.gmu.bookshare.config.ApiSecurityConfiguration;
import com.gmu.bookshare.entity.ListingEntity;
import com.gmu.bookshare.model.ListingDto;
import com.gmu.bookshare.service.BidService;
......@@ -8,33 +7,16 @@ import com.gmu.bookshare.service.ListingService;
import com.gmu.bookshare.service.ShareUserService;
import com.gmu.bookshare.utils.JsonUtil;
import com.gmu.bookshare.web.BookshareApiController;
import com.kakawait.spring.boot.security.cas.autoconfigure.CasSecurityProperties;
import com.kakawait.spring.security.cas.client.ticket.ProxyTicketProvider;
import com.kakawait.spring.security.cas.client.validation.AssertionProvider;
import org.jasig.cas.client.validation.TicketValidator;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.modelmapper.ModelMapper;
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.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
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.csrf;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
import java.util.Collections;
import java.util.Date;
......@@ -44,6 +26,7 @@ import static org.hamcrest.Matchers.is;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
......@@ -67,38 +50,7 @@ public class BookshareRestControllerIntegrationTest {
@MockBean
private ShareUserService shareUserService;
@MockBean
private ProxyTicketProvider proxyTicketProvider;
@MockBean
private AssertionProvider assertionProvider;
@MockBean
private CasSecurityProperties casSecurityProperties;
@MockBean
private CasAuthenticationEntryPoint casAuthenticationEntryPoint;
@MockBean
private TicketValidator ticketValidator;
@MockBean
private ServiceProperties serviceProperties;
// @Autowired
// private WebApplicationContext context;
//
// private MockMvc mvc;
//
// @Before
// public void setup() {
// mvc = MockMvcBuilders
// .webAppContextSetup(context)
// .apply(springSecurity())
// .build();
// }
@WithMockUser(value = "spring")
@WithMockUser(value = "casuser")
@Test
public void givenListing_whenGetListings_thenReturnJsonArray()
throws Exception {
......@@ -118,7 +70,7 @@ public class BookshareRestControllerIntegrationTest {
.andExpect(jsonPath("$[0].isbn", is(alex.getIsbn())));
}
@WithMockUser(value = "spring")
@WithMockUser(value = "casuser")
@Test
public void givenListing_whenPutListing_thenReturnHttpOk()
throws Exception {
......@@ -142,7 +94,7 @@ public class BookshareRestControllerIntegrationTest {
.andExpect(status().isOk());
}
@WithMockUser(value = "spring")
@WithMockUser(value = "casuser")
@Test
public void givenListing_whenPutInvalidListing_thenReturnHttpNotFound() throws Exception {
ListingEntity listingNotInDB = new ListingEntity(123456, 3, 14.99,
......@@ -160,7 +112,7 @@ public class BookshareRestControllerIntegrationTest {
.andExpect(status().isNotFound());
}
@WithMockUser(value = "spring")
@WithMockUser(value = "casuser")
@Test
public void givenListing_whenDeleteListing_thenReturnHttpOk() throws Exception {
ListingEntity listingInDB = new ListingEntity(123456, 3, 14.99,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment