Commit 21a083b6 authored by Khalid Ali's avatar Khalid Ali
Browse files

Kakawait Security

parent 7c5f285b
......@@ -47,8 +47,11 @@ dependencies {
implementation('org.springframework.boot:spring-boot-starter-actuator')
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')
compile('io.springfox:springfox-swagger2:2.9.2')
compile('io.springfox:springfox-swagger-ui:2.9.2')
compile('org.postgresql:postgresql:42.2.5')
......
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;
......@@ -7,17 +10,22 @@ import org.jasig.cas.client.validation.TicketValidator;
import org.modelmapper.ModelMapper;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.context.event.EventListener;
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;
......@@ -34,75 +42,98 @@ public class BookshareApplication {
}
@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");
FilterRegistrationBean forwardedHeaderFilter() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new ForwardedHeaderFilter());
filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return filterRegistrationBean;
}
@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;
RestTemplate casRestTemplate(ServiceProperties serviceProperties, ProxyTicketProvider proxyTicketProvider) {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add(new CasAuthorizationInterceptor(serviceProperties, proxyTicketProvider));
return restTemplate;
}
@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;
public ServiceProperties serviceProperties() {
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setService("http://localhost:9090/login/cas");
serviceProperties.setSendRenew(false);
return serviceProperties;
}
@EventListener
public SingleSignOutHttpSessionListener singleSignOutHttpSessionListener(
HttpSessionEvent event) {
return new SingleSignOutHttpSessionListener();
}
// @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();
// }
}
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.utils;
package com.gmu.bookshare.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
......
......@@ -7,13 +7,26 @@ 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;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
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;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.stream.Collectors;
......@@ -25,14 +38,30 @@ 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) {
public BookshareApiController(ListingService listingService, BidService bidService,
ShareUserService shareUserService, ProxyTicketProvider proxyTicketProvider,
AssertionProvider assertionProvider) {
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)
......@@ -89,6 +118,32 @@ 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";
// }
private ListingDto convertToDto(ListingEntity listingEntity) {
return modelMapper.map(listingEntity, ListingDto.class);
}
......
......@@ -11,4 +11,17 @@ spring.datasource:
password:
server:
port: 9009
\ No newline at end of file
ssl:
enabled: true
keyStore: file:/etc/cas/tomcat.keystore
keyStorePassword: changeit
keyPassword: changeit
port: 9090
security:
cas:
server:
base-url: https://localhost:9443/
service:
resolution-mode: dynamic
require-ssl: true
\ No newline at end of file
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;
......@@ -7,15 +8,33 @@ 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;
......@@ -48,6 +67,38 @@ 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")
@Test
public void givenListing_whenGetListings_thenReturnJsonArray()
throws Exception {
......@@ -60,12 +111,14 @@ public class BookshareRestControllerIntegrationTest {
given(listingService.getAll()).willReturn(allListingEntities);
mvc.perform(get("/bs/api/listing/")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(1)))
.andExpect(jsonPath("$[0].isbn", is(alex.getIsbn())));
}
@WithMockUser(value = "spring")
@Test
public void givenListing_whenPutListing_thenReturnHttpOk()
throws Exception {
......@@ -83,11 +136,13 @@ public class BookshareRestControllerIntegrationTest {
given(listingService.updateListing(any(ListingEntity.class))).willReturn(listingInDB);
mvc.perform(put("/bs/api/listing/" + listingDtoInDB.getId())
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(JsonUtil.writeValueAsString(listingDtoInDB)))
.andExpect(status().isOk());
}
@WithMockUser(value = "spring")
@Test
public void givenListing_whenPutInvalidListing_thenReturnHttpNotFound() throws Exception {
ListingEntity listingNotInDB = new ListingEntity(123456, 3, 14.99,
......@@ -99,11 +154,13 @@ public class BookshareRestControllerIntegrationTest {
given(listingService.updateListing(any(ListingEntity.class))).willReturn(null);
mvc.perform(put("/bs/api/listing/" + listingDtoNotInDB.getId())
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(JsonUtil.writeValueAsString(listingDtoNotInDB)))
.andExpect(status().isNotFound());
}
@WithMockUser(value = "spring")
@Test
public void givenListing_whenDeleteListing_thenReturnHttpOk() throws Exception {
ListingEntity listingInDB = new ListingEntity(123456, 3, 14.99,
......@@ -115,6 +172,7 @@ public class BookshareRestControllerIntegrationTest {
given(listingService.getById(listingInDB.getId())).willReturn(listingInDB);
mvc.perform(delete("/bs/api/listing/" + listingInDB.getId())
.with(csrf())
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(JsonUtil.writeValueAsString(listingDtoInDB)))
.andExpect(status().isOk());
......
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