diff --git a/airline/client/spring-ws/pom.xml b/airline/client/spring-ws/pom.xml index e24f23b..3585a20 100644 --- a/airline/client/spring-ws/pom.xml +++ b/airline/client/spring-ws/pom.xml @@ -29,6 +29,11 @@ spring-ws-core + + org.springframework.ws + spring-ws-security + + org.springframework.ws spring-ws-test diff --git a/airline/client/spring-ws/src/main/java/org/springframework/ws/samples/airline/client/sws/GetFlights.java b/airline/client/spring-ws/src/main/java/org/springframework/ws/samples/airline/client/sws/GetFlights.java index 75229a0..4ca07af 100644 --- a/airline/client/spring-ws/src/main/java/org/springframework/ws/samples/airline/client/sws/GetFlights.java +++ b/airline/client/spring-ws/src/main/java/org/springframework/ws/samples/airline/client/sws/GetFlights.java @@ -49,8 +49,13 @@ public class GetFlights extends WebServiceGatewaySupport { getFlightsRequest.setDepartureDate(departureDate); System.out.println("Requesting flights on " + departureDate); - GetFlightsResponse response = (GetFlightsResponse) getWebServiceTemplate().marshalSendAndReceive(getFlightsRequest); - System.out.println("Got " + response.getFlight().size() + " results"); + GetFlightsResponse response = null; + try { + response = (GetFlightsResponse) getWebServiceTemplate().marshalSendAndReceive(getFlightsRequest); + } catch (Exception e) { + throw new RuntimeException(e); + } + System.out.println("Got " + response.getFlight().size() + " results"); if (response.getFlight().size() > 0) { // Book the first flight using John Doe as a frequent flyer BookFlightRequest bookFlightRequest = new BookFlightRequest(); diff --git a/airline/client/spring-ws/src/main/java/org/springframework/ws/samples/airline/client/sws/GetFrequentFlyerMileage.java b/airline/client/spring-ws/src/main/java/org/springframework/ws/samples/airline/client/sws/GetFrequentFlyerMileage.java index be0aafe..1f93fce 100644 --- a/airline/client/spring-ws/src/main/java/org/springframework/ws/samples/airline/client/sws/GetFrequentFlyerMileage.java +++ b/airline/client/spring-ws/src/main/java/org/springframework/ws/samples/airline/client/sws/GetFrequentFlyerMileage.java @@ -35,7 +35,5 @@ public class GetFrequentFlyerMileage extends WebServiceGatewaySupport { ""); getWebServiceTemplate().sendSourceAndReceiveToResult(source, new StreamResult(System.out)); - } - } diff --git a/airline/client/spring-ws/src/main/java/org/springframework/ws/samples/airline/client/sws/SpringWsMain.java b/airline/client/spring-ws/src/main/java/org/springframework/ws/samples/airline/client/sws/SpringWsMain.java index 62c0e48..6e91a54 100644 --- a/airline/client/spring-ws/src/main/java/org/springframework/ws/samples/airline/client/sws/SpringWsMain.java +++ b/airline/client/spring-ws/src/main/java/org/springframework/ws/samples/airline/client/sws/SpringWsMain.java @@ -29,5 +29,8 @@ public class SpringWsMain { GetFlights getFlights = ctx.getBean(GetFlights.class); getFlights.getFlights(); + + GetFrequentFlyerMileage getFrequentFlyerMileage = ctx.getBean(GetFrequentFlyerMileage.class); + getFrequentFlyerMileage.getFrequentFlyerMileage(); } } diff --git a/airline/client/spring-ws/src/main/java/org/springframework/ws/samples/airline/client/sws/WsConfiguration.java b/airline/client/spring-ws/src/main/java/org/springframework/ws/samples/airline/client/sws/WsConfiguration.java index 4860a70..6241a5c 100644 --- a/airline/client/spring-ws/src/main/java/org/springframework/ws/samples/airline/client/sws/WsConfiguration.java +++ b/airline/client/spring-ws/src/main/java/org/springframework/ws/samples/airline/client/sws/WsConfiguration.java @@ -3,8 +3,10 @@ package org.springframework.ws.samples.airline.client.sws; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.oxm.jaxb.Jaxb2Marshaller; +import org.springframework.ws.client.support.interceptor.ClientInterceptor; import org.springframework.ws.soap.SoapMessageFactory; import org.springframework.ws.soap.saaj.SaajSoapMessageFactory; +import org.springframework.ws.soap.security.wss4j2.Wss4jSecurityInterceptor; @Configuration(proxyBeanMethods = false) public class WsConfiguration { @@ -23,7 +25,8 @@ public class WsConfiguration { } @Bean - GetFlights getFlights(SoapMessageFactory messageFactory, Jaxb2Marshaller marshaller) { + GetFlights getFlights(SoapMessageFactory messageFactory, Jaxb2Marshaller marshaller, + Wss4jSecurityInterceptor securityInterceptor) { GetFlights getFlights = new GetFlights(messageFactory); getFlights.setDefaultUri("http://localhost:8080/airline-server/services"); @@ -33,10 +36,24 @@ public class WsConfiguration { } @Bean - GetFrequentFlyerMileage getFrequentFlyerMileage(SoapMessageFactory messageFactory) { + GetFrequentFlyerMileage getFrequentFlyerMileage(SoapMessageFactory messageFactory, Jaxb2Marshaller marshaller, + Wss4jSecurityInterceptor securityInterceptor) { GetFrequentFlyerMileage getFrequentFlyerMileage = new GetFrequentFlyerMileage(messageFactory); getFrequentFlyerMileage.setDefaultUri("http://localhost:8080/airline-server/services"); + getFrequentFlyerMileage.setMarshaller(marshaller); + getFrequentFlyerMileage.setUnmarshaller(marshaller); + getFrequentFlyerMileage.setInterceptors(new ClientInterceptor[] { securityInterceptor }); return getFrequentFlyerMileage; } + + @Bean + Wss4jSecurityInterceptor securityInterceptor() { + + Wss4jSecurityInterceptor securityInterceptor = new Wss4jSecurityInterceptor(); + securityInterceptor.setSecurementActions("UsernameToken"); + securityInterceptor.setSecurementUsername("john"); + securityInterceptor.setSecurementPassword("changeme"); + return securityInterceptor; + } } diff --git a/airline/server/pom.xml b/airline/server/pom.xml index a5d13b3..ea884bd 100644 --- a/airline/server/pom.xml +++ b/airline/server/pom.xml @@ -36,16 +36,15 @@ spring-boot-starter-web-services - - - - - - - - - - + + org.springframework.ws + spring-ws-security + + + + org.springframework.boot + spring-boot-starter-security + org.springframework.ws diff --git a/airline/server/src/main/java/org/springframework/ws/samples/airline/AirlineServerApplication.java b/airline/server/src/main/java/org/springframework/ws/samples/airline/AirlineServerApplication.java index c49df5d..1d52b3e 100644 --- a/airline/server/src/main/java/org/springframework/ws/samples/airline/AirlineServerApplication.java +++ b/airline/server/src/main/java/org/springframework/ws/samples/airline/AirlineServerApplication.java @@ -2,8 +2,9 @@ package org.springframework.ws.samples.airline; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; -@SpringBootApplication +@SpringBootApplication(exclude = SecurityAutoConfiguration.class) public class AirlineServerApplication { public static void main(String[] args) { diff --git a/airline/server/src/main/java/org/springframework/ws/samples/airline/dao/FlightDao.java b/airline/server/src/main/java/org/springframework/ws/samples/airline/dao/FlightDao.java index 6e3c2b0..453e4e1 100644 --- a/airline/server/src/main/java/org/springframework/ws/samples/airline/dao/FlightDao.java +++ b/airline/server/src/main/java/org/springframework/ws/samples/airline/dao/FlightDao.java @@ -35,12 +35,4 @@ public interface FlightDao extends CrudRepository { @Param("class") ServiceClass serviceClass); Flight findFlightByNumberAndDepartureTime(String flightNumber, ZonedDateTime departureTime); - - /** - * @deprecated Migrate to {@link #save(Object)}. - */ - @Deprecated - default Flight update(Flight flight) { - return save(flight); - } } diff --git a/airline/server/src/main/java/org/springframework/ws/samples/airline/dao/FrequentFlyerDao.java b/airline/server/src/main/java/org/springframework/ws/samples/airline/dao/FrequentFlyerDao.java index a2e9c7e..3c63fe4 100644 --- a/airline/server/src/main/java/org/springframework/ws/samples/airline/dao/FrequentFlyerDao.java +++ b/airline/server/src/main/java/org/springframework/ws/samples/airline/dao/FrequentFlyerDao.java @@ -16,20 +16,13 @@ package org.springframework.ws.samples.airline.dao; -import org.springframework.dao.DataAccessException; +import java.util.Optional; + import org.springframework.data.repository.CrudRepository; import org.springframework.ws.samples.airline.domain.FrequentFlyer; public interface FrequentFlyerDao extends CrudRepository { - /** - * @deprecated Migrate to {@link #findByUsername(String)}. - */ - @Deprecated - default FrequentFlyer get(String username) throws DataAccessException { - return findByUsername(username); - } - - FrequentFlyer findByUsername(String username); + Optional findByUsername(String username); } diff --git a/airline/server/src/main/java/org/springframework/ws/samples/airline/domain/FrequentFlyer.java b/airline/server/src/main/java/org/springframework/ws/samples/airline/domain/FrequentFlyer.java index 42d541f..a73c5ac 100644 --- a/airline/server/src/main/java/org/springframework/ws/samples/airline/domain/FrequentFlyer.java +++ b/airline/server/src/main/java/org/springframework/ws/samples/airline/domain/FrequentFlyer.java @@ -85,8 +85,10 @@ public class FrequentFlyer extends Passenger { return username.hashCode(); } + @Override public String toString() { - return username; + return "FrequentFlyer{" + "username='" + username + '\'' + ", password='" + password + '\'' + ", miles=" + miles + + '}'; } public void addMiles(int miles) { diff --git a/airline/server/src/main/java/org/springframework/ws/samples/airline/security/FrequentFlyerDetails.java b/airline/server/src/main/java/org/springframework/ws/samples/airline/security/FrequentFlyerDetails.java new file mode 100644 index 0000000..d3b2124 --- /dev/null +++ b/airline/server/src/main/java/org/springframework/ws/samples/airline/security/FrequentFlyerDetails.java @@ -0,0 +1,65 @@ +package org.springframework.ws.samples.airline.security; + +import java.util.Collection; +import java.util.List; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.ws.samples.airline.domain.FrequentFlyer; + +public class FrequentFlyerDetails implements UserDetails { + + private static final List GRANTED_AUTHORITIES = List + .of(new SimpleGrantedAuthority("ROLE_FREQUENT_FLYER")); + + private FrequentFlyer frequentFlyer; + + public FrequentFlyerDetails(FrequentFlyer frequentFlyer) { + this.frequentFlyer = frequentFlyer; + } + + public FrequentFlyer getFrequentFlyer() { + return frequentFlyer; + } + + @Override + public Collection getAuthorities() { + return GRANTED_AUTHORITIES; + } + + @Override + public String getUsername() { + return frequentFlyer.getUsername(); + } + + @Override + public String getPassword() { + return frequentFlyer.getPassword(); + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public String toString() { + return "FrequentFlyerDetails{" + "frequentFlyer=" + frequentFlyer + '}'; + } +} diff --git a/airline/server/src/main/java/org/springframework/ws/samples/airline/security/FrequentFlyerSecurityService.java b/airline/server/src/main/java/org/springframework/ws/samples/airline/security/FrequentFlyerSecurityService.java new file mode 100644 index 0000000..4873b11 --- /dev/null +++ b/airline/server/src/main/java/org/springframework/ws/samples/airline/security/FrequentFlyerSecurityService.java @@ -0,0 +1,29 @@ +package org.springframework.ws.samples.airline.security; + +import org.springframework.ws.samples.airline.domain.FrequentFlyer; +import org.springframework.ws.samples.airline.service.NoSuchFrequentFlyerException; + +/** + * Defines the business logic for handling frequent flyers. + * + * @author Arjen Poutsma + */ +public interface FrequentFlyerSecurityService { + + /** + * Returns the FrequentFlyer with the given username. + * + * @param username the username + * @return the frequent flyer with the given username, or null if not found + * @throws NoSuchFrequentFlyerException when the frequent flyer cannot be found + */ + FrequentFlyer getFrequentFlyer(String username) throws NoSuchFrequentFlyerException; + + /** + * Returns the FrequentFlyer that is currently logged in. + * + * @return the frequent flyer that is currently logged in, or null if not found + */ + FrequentFlyer getCurrentlyAuthenticatedFrequentFlyer(); + +} diff --git a/airline/server/src/main/java/org/springframework/ws/samples/airline/security/SecurityConfiguration.java b/airline/server/src/main/java/org/springframework/ws/samples/airline/security/SecurityConfiguration.java new file mode 100644 index 0000000..2f68f7d --- /dev/null +++ b/airline/server/src/main/java/org/springframework/ws/samples/airline/security/SecurityConfiguration.java @@ -0,0 +1,37 @@ +package org.springframework.ws.samples.airline.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.crypto.password.NoOpPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.ws.samples.airline.dao.FrequentFlyerDao; + +@Configuration(proxyBeanMethods = false) +@EnableMethodSecurity +public class SecurityConfiguration { + + @Bean + SpringSecurityFrequentFlyerService userDetailsService(FrequentFlyerDao frequentFlyerDao) { + return new SpringSecurityFrequentFlyerService(frequentFlyerDao); + } + + @Bean + PasswordEncoder passwordEncoder() { + return NoOpPasswordEncoder.getInstance(); + } + +// @Bean +// SecurityFilterChain securityFilterChain(HttpSecurity security) throws Exception { +// +// security.authorizeHttpRequests() // +// .requestMatchers("/login", "/error", "/logout").permitAll() // +// .anyRequest().authenticated() // +// .and() // +// .csrf().disable(); +// +// return security.build(); +// } +} diff --git a/airline/server/src/main/java/org/springframework/ws/samples/airline/security/SpringSecurityFrequentFlyerService.java b/airline/server/src/main/java/org/springframework/ws/samples/airline/security/SpringSecurityFrequentFlyerService.java new file mode 100644 index 0000000..b5518ae --- /dev/null +++ b/airline/server/src/main/java/org/springframework/ws/samples/airline/security/SpringSecurityFrequentFlyerService.java @@ -0,0 +1,65 @@ +package org.springframework.ws.samples.airline.security; + +import org.apache.wss4j.common.principal.UsernameTokenPrincipal; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.ws.samples.airline.dao.FrequentFlyerDao; +import org.springframework.ws.samples.airline.domain.FrequentFlyer; +import org.springframework.ws.samples.airline.service.NoSuchFrequentFlyerException; + +/** + * Implementation of the {@link FrequentFlyerSecurityService} that uses Spring Security. + * + * @author Arjen Poutsma + */ +public class SpringSecurityFrequentFlyerService implements FrequentFlyerSecurityService, UserDetailsService { + + private static final Logger log = LoggerFactory.getLogger(SpringSecurityFrequentFlyerService.class); + + private final FrequentFlyerDao frequentFlyerDao; + + public SpringSecurityFrequentFlyerService(FrequentFlyerDao frequentFlyerDao) { + this.frequentFlyerDao = frequentFlyerDao; + } + + @Override + @Transactional + public FrequentFlyer getFrequentFlyer(String username) throws NoSuchFrequentFlyerException { + return frequentFlyerDao.findByUsername(username) // + .orElseThrow(() -> new NoSuchFrequentFlyerException(username)); + } + + @Override + @Transactional + public FrequentFlyer getCurrentlyAuthenticatedFrequentFlyer() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if (authentication != null) { + UsernameTokenPrincipal principal = (UsernameTokenPrincipal) authentication.getPrincipal(); + FrequentFlyerDetails frequentFlyerDetails = (FrequentFlyerDetails) loadUserByUsername(principal.getName()); + return frequentFlyerDetails.getFrequentFlyer(); + } + + return null; + } + + @Override + @Transactional + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + log.debug("Looking up " + username); + + FrequentFlyerDetails details = frequentFlyerDao.findByUsername(username) // + .map(FrequentFlyerDetails::new) // + .orElseThrow(() -> new UsernameNotFoundException("Frequent flyer '" + username + "' not found")); + + log.debug("Found " + details); + + return details; + } +} diff --git a/airline/server/src/main/java/org/springframework/ws/samples/airline/security/StubFrequentFlyerSecurityService.java b/airline/server/src/main/java/org/springframework/ws/samples/airline/security/StubFrequentFlyerSecurityService.java new file mode 100644 index 0000000..11dc4da --- /dev/null +++ b/airline/server/src/main/java/org/springframework/ws/samples/airline/security/StubFrequentFlyerSecurityService.java @@ -0,0 +1,35 @@ +package org.springframework.ws.samples.airline.security; + +import org.springframework.ws.samples.airline.domain.FrequentFlyer; +import org.springframework.ws.samples.airline.service.NoSuchFrequentFlyerException; + +/** + * Stub implementation of FrequentFlyerSecurityService. This implementation is used by default by + * {@link org.springframework.ws.samples.airline.service.impl.AirlineServiceImpl}, to allow it to run without depending + * on Spring Security. + * + * @author Arjen Poutsma + */ +public class StubFrequentFlyerSecurityService implements FrequentFlyerSecurityService { + + private FrequentFlyer john; + + public StubFrequentFlyerSecurityService() { + this.john = new FrequentFlyer("John", "Doe", "john", "changeme"); + john.setMiles(10); + } + + @Override + public FrequentFlyer getFrequentFlyer(String username) throws NoSuchFrequentFlyerException { + if (john.getUsername().equals(username)) { + return john; + } else { + throw new NoSuchFrequentFlyerException(username); + } + } + + @Override + public FrequentFlyer getCurrentlyAuthenticatedFrequentFlyer() { + return john; + } +} diff --git a/airline/server/src/main/java/org/springframework/ws/samples/airline/service/AirlineService.java b/airline/server/src/main/java/org/springframework/ws/samples/airline/service/AirlineService.java index ee047ae..3932307 100644 --- a/airline/server/src/main/java/org/springframework/ws/samples/airline/service/AirlineService.java +++ b/airline/server/src/main/java/org/springframework/ws/samples/airline/service/AirlineService.java @@ -66,4 +66,11 @@ public interface AirlineService { Ticket bookFlight(String flightNumber, ZonedDateTime departureTime, List passengers) throws NoSuchFlightException, NoSeatAvailableException, NoSuchFrequentFlyerException; + /** + * Returns the amount of frequent flyer award miles for the currently logged in frequent flyer. + * + * @return the amount of frequent flyer miles + */ + int getFrequentFlyerMileage(); } + diff --git a/airline/server/src/main/java/org/springframework/ws/samples/airline/service/impl/AirlineServiceImpl.java b/airline/server/src/main/java/org/springframework/ws/samples/airline/service/impl/AirlineServiceImpl.java index e889195..f0189fc 100644 --- a/airline/server/src/main/java/org/springframework/ws/samples/airline/service/impl/AirlineServiceImpl.java +++ b/airline/server/src/main/java/org/springframework/ws/samples/airline/service/impl/AirlineServiceImpl.java @@ -18,19 +18,20 @@ package org.springframework.ws.samples.airline.service.impl; import java.time.LocalDate; import java.time.ZonedDateTime; import java.util.List; +import java.util.Optional; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import org.springframework.ws.samples.airline.dao.FlightDao; import org.springframework.ws.samples.airline.dao.TicketDao; -import org.springframework.ws.samples.airline.domain.Flight; -import org.springframework.ws.samples.airline.domain.Passenger; -import org.springframework.ws.samples.airline.domain.ServiceClass; -import org.springframework.ws.samples.airline.domain.Ticket; +import org.springframework.ws.samples.airline.domain.*; +import org.springframework.ws.samples.airline.security.FrequentFlyerSecurityService; +import org.springframework.ws.samples.airline.security.StubFrequentFlyerSecurityService; import org.springframework.ws.samples.airline.service.AirlineService; import org.springframework.ws.samples.airline.service.NoSeatAvailableException; import org.springframework.ws.samples.airline.service.NoSuchFlightException; @@ -51,40 +52,66 @@ public class AirlineServiceImpl implements AirlineService { private TicketDao ticketDao; - @Autowired + private FrequentFlyerSecurityService frequentFlyerSecurityService = new StubFrequentFlyerSecurityService(); + public AirlineServiceImpl(FlightDao flightDao, TicketDao ticketDao) { this.flightDao = flightDao; this.ticketDao = ticketDao; } + @Autowired(required = false) + public void setFrequentFlyerSecurityService(FrequentFlyerSecurityService frequentFlyerSecurityService) { + this.frequentFlyerSecurityService = frequentFlyerSecurityService; + } + @Transactional(readOnly = false, rollbackFor = { NoSuchFlightException.class, NoSeatAvailableException.class, NoSuchFrequentFlyerException.class }) public Ticket bookFlight(String flightNumber, ZonedDateTime departureTime, List passengers) throws NoSuchFlightException, NoSeatAvailableException, NoSuchFrequentFlyerException { Assert.notEmpty(passengers, "No passengers given"); + if (logger.isDebugEnabled()) { logger.debug("Booking flight '" + flightNumber + "' on '" + departureTime + "' for " + passengers); } + Flight flight = flightDao.findFlightByNumberAndDepartureTime(flightNumber, departureTime); + if (flight == null) { throw new NoSuchFlightException(flightNumber, departureTime); } else if (flight.getSeatsAvailable() < passengers.size()) { throw new NoSeatAvailableException(flight); } + Ticket ticket = new Ticket(); ticket.setIssueDate(LocalDate.now()); ticket.setFlight(flight); - passengers.forEach(ticket::addPassenger); + + for (Passenger passenger : passengers) { + if (passenger instanceof FrequentFlyer && frequentFlyerSecurityService != null) { + String username = ((FrequentFlyer) passenger).getUsername(); + Assert.hasLength(username, "No username specified"); + FrequentFlyer frequentFlyer = frequentFlyerSecurityService.getFrequentFlyer(username); + frequentFlyer.addMiles(flight.getMiles()); + ticket.addPassenger(frequentFlyer); + } else { + ticket.addPassenger(passenger); + } + } + flight.substractSeats(passengers.size()); + flightDao.save(flight); ticketDao.save(ticket); + return ticket; } public Flight getFlight(Long id) throws NoSuchFlightException { - return flightDao.findById(id).orElseThrow(() -> new NoSuchFlightException(id)); + + return flightDao.findById(id) // + .orElseThrow(() -> new NoSuchFlightException(id)); } public List getFlights(String fromAirportCode, String toAirportCode, ZonedDateTime departureDate, @@ -93,13 +120,30 @@ public class AirlineServiceImpl implements AirlineService { if (serviceClass == null) { serviceClass = ServiceClass.ECONOMY; } + if (logger.isDebugEnabled()) { logger.debug("Getting flights from '" + fromAirportCode + "' to '" + toAirportCode + "' on " + departureDate); } + List flights = flightDao.findFlights(fromAirportCode, toAirportCode, departureDate, serviceClass); + if (logger.isDebugEnabled()) { logger.debug("Returning " + flights.size() + " flights"); } + return flights; } + + @Override + @PreAuthorize("hasRole('FREQUENT_FLYER')") + public int getFrequentFlyerMileage() { + + if (logger.isDebugEnabled()) { + logger.debug("Using " + frequentFlyerSecurityService + " for security"); + } + + return Optional.ofNullable(frequentFlyerSecurityService.getCurrentlyAuthenticatedFrequentFlyer()) + .map(FrequentFlyer::getMiles) // + .orElse(0); + } } diff --git a/airline/server/src/main/java/org/springframework/ws/samples/airline/web/FlightsController.java b/airline/server/src/main/java/org/springframework/ws/samples/airline/web/FlightsController.java index 3ed09a1..09cc56c 100644 --- a/airline/server/src/main/java/org/springframework/ws/samples/airline/web/FlightsController.java +++ b/airline/server/src/main/java/org/springframework/ws/samples/airline/web/FlightsController.java @@ -20,7 +20,7 @@ import java.time.LocalDate; import java.time.ZonedDateTime; import org.springframework.stereotype.Controller; -import org.springframework.ui.ModelMap; +import org.springframework.ui.Model; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -49,7 +49,7 @@ public class FlightsController { public String flightList(@RequestParam(value = "from", required = false) String fromAirportCode, @RequestParam(value = "to", required = false) String toAirportCode, @RequestParam(value = "departureDate", required = false) String departureDateString, - @RequestParam(value = "serviceClass", required = false) String serviceClassString, ModelMap model) { + @RequestParam(value = "serviceClass", required = false) String serviceClassString, Model model) { if (ObjectUtils.isEmpty(departureDateString)) { departureDateString = LocalDate.now().toString(); @@ -58,7 +58,7 @@ public class FlightsController { serviceClassString = "ECONOMY"; } ServiceClass serviceClass = ServiceClass.valueOf(serviceClassString); - ZonedDateTime departureDate = ZonedDateTime.parse(departureDateString); + ZonedDateTime departureDate = ZonedDateTime.parse(departureDateString); if (StringUtils.hasLength(fromAirportCode) && StringUtils.hasLength(toAirportCode)) { model.addAttribute("from", fromAirportCode); @@ -72,7 +72,7 @@ public class FlightsController { } @GetMapping(value = "{id}") - public String singleFlight(@PathVariable("id") long id, ModelMap model) throws NoSuchFlightException { + public String singleFlight(@PathVariable("id") long id, Model model) throws NoSuchFlightException { model.addAttribute(airlineService.getFlight(id)); return "flight"; diff --git a/airline/server/src/main/java/org/springframework/ws/samples/airline/ws/AirlineEndpoint.java b/airline/server/src/main/java/org/springframework/ws/samples/airline/ws/AirlineEndpoint.java index 5f78e98..bc7ea4d 100644 --- a/airline/server/src/main/java/org/springframework/ws/samples/airline/ws/AirlineEndpoint.java +++ b/airline/server/src/main/java/org/springframework/ws/samples/airline/ws/AirlineEndpoint.java @@ -24,15 +24,16 @@ import java.time.LocalDate; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.XMLGregorianCalendar; +import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.StringUtils; import org.springframework.ws.samples.airline.domain.FrequentFlyer; @@ -45,6 +46,8 @@ import org.springframework.ws.samples.airline.service.NoSeatAvailableException; import org.springframework.ws.samples.airline.service.NoSuchFlightException; import org.springframework.ws.samples.airline.service.NoSuchFrequentFlyerException; import org.springframework.ws.server.endpoint.annotation.*; +import org.w3c.dom.Document; +import org.w3c.dom.Element; /** * Endpoint that handles the Airline Web Service messages using a combination of JAXB2 marshalling and XPath @@ -55,7 +58,7 @@ import org.springframework.ws.server.endpoint.annotation.*; @Endpoint public class AirlineEndpoint { - private static final Log logger = LogFactory.getLog(AirlineEndpoint.class); + private static final Logger logger = LoggerFactory.getLogger(AirlineEndpoint.class); private final ObjectFactory objectFactory = new ObjectFactory(); @@ -136,23 +139,38 @@ public class AirlineEndpoint { NoSuchFrequentFlyerException, DatatypeConfigurationException { ZonedDateTime departureTime = SchemaConversionUtils.toDateTime(xmlDepartureTime); - List passengers = new ArrayList(passengerOrUsernameList.size()); + List passengers = new ArrayList<>(passengerOrUsernameList.size()); - for (Iterator iterator = passengerOrUsernameList.iterator(); iterator.hasNext();) { - Object passengerOrUsername = iterator.next(); - if (passengerOrUsername instanceof Name) { - Name passengerName = (Name) passengerOrUsername; + for (Object passengerOrUsername : passengerOrUsernameList) { + if (passengerOrUsername instanceof Name passengerName) { Passenger passenger = new Passenger(passengerName.getFirst(), passengerName.getLast()); passengers.add(passenger); - } else if (passengerOrUsername instanceof String) { - String frequentFlyerUsername = (String) passengerOrUsername; + } else if (passengerOrUsername instanceof String frequentFlyerUsername) { FrequentFlyer frequentFlyer = new FrequentFlyer(frequentFlyerUsername); passengers.add(frequentFlyer); } } + org.springframework.ws.samples.airline.domain.Ticket domainTicket = airlineService.bookFlight(flightNumber, departureTime, passengers); return SchemaConversionUtils.toSchemaType(domainTicket); } + + @PayloadRoot(localPart = GET_FREQUENT_FLYER_MILEAGE_REQUEST, namespace = MESSAGES_NAMESPACE) + @ResponsePayload + public Element getFrequentFlyerMileage() throws ParserConfigurationException { + + if (logger.isDebugEnabled()) { + logger.debug("Received GetFrequentFlyerMileageRequest"); + } + + int mileage = airlineService.getFrequentFlyerMileage(); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document document = documentBuilder.newDocument(); + Element response = document.createElementNS(MESSAGES_NAMESPACE, GET_FREQUENT_FLYER_MILEAGE_RESPONSE); + response.setTextContent(Integer.toString(mileage)); + + return response; + } } diff --git a/airline/server/src/main/java/org/springframework/ws/samples/airline/ws/WebServicesConfiguration.java b/airline/server/src/main/java/org/springframework/ws/samples/airline/ws/WebServicesConfiguration.java index 4b5866f..82963ee 100644 --- a/airline/server/src/main/java/org/springframework/ws/samples/airline/ws/WebServicesConfiguration.java +++ b/airline/server/src/main/java/org/springframework/ws/samples/airline/ws/WebServicesConfiguration.java @@ -1,5 +1,7 @@ package org.springframework.ws.samples.airline.ws; +import javax.security.auth.callback.CallbackHandler; + import org.springframework.boot.web.server.MimeMappings; import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.boot.web.servlet.ServletRegistrationBean; @@ -8,10 +10,13 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; +import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.util.MimeTypeUtils; -import org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor; import org.springframework.ws.soap.saaj.SaajSoapMessageFactory; +import org.springframework.ws.soap.security.wss4j2.Wss4jSecurityInterceptor; +import org.springframework.ws.soap.security.wss4j2.callback.SpringSecurityPasswordValidationCallbackHandler; import org.springframework.ws.soap.server.SoapMessageDispatcher; +import org.springframework.ws.soap.server.endpoint.interceptor.PayloadRootSmartSoapEndpointInterceptor; import org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor; import org.springframework.ws.transport.http.MessageDispatcherServlet; import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition; @@ -40,11 +45,6 @@ public class WebServicesConfiguration { return new ServletRegistrationBean<>(messageDispatcherServlet, "/airline-server/*", "*.wsdl"); } - @Bean - PayloadLoggingInterceptor payloadLoggingInterceptor() { - return new PayloadLoggingInterceptor(); - } - @Bean PayloadValidatingInterceptor payloadValidatingInterceptor(CommonsXsdSchemaCollection xsdSchemaCollection) { @@ -56,33 +56,34 @@ public class WebServicesConfiguration { return payloadValidatingInterceptor; } -// @Bean -// XwsSecurityInterceptor securityInterceptor(UserDetailsService securityService) { -// -// XwsSecurityInterceptor securityInterceptor = new XwsSecurityInterceptor(); -// securityInterceptor.setSecureResponse(false); -// securityInterceptor.setPolicyConfiguration( -// new ClassPathResource("org/springframework/ws/samples/airline/security/securityPolicy.xml")); -// securityInterceptor.setCallbackHandler(springDigestPasswordValidationCallbackHandler(securityService)); -// -// return securityInterceptor; -// } + @Bean + Wss4jSecurityInterceptor securityInterceptor(SpringSecurityPasswordValidationCallbackHandler handler) { -// @Bean -// PayloadRootSmartSoapEndpointInterceptor smartSoapEndpointInterceptor(XwsSecurityInterceptor securityInterceptor) { -// -// return new PayloadRootSmartSoapEndpointInterceptor(securityInterceptor, -// "http://www.springframework.org/spring-ws/samples/airline/schemas/messages", "GetFrequentFlyerMileageRequest"); -// } + /** + * + * + */ + Wss4jSecurityInterceptor securityInterceptor = new Wss4jSecurityInterceptor(); + securityInterceptor.setValidationActions("UsernameToken"); + securityInterceptor.setValidationCallbackHandlers(new CallbackHandler[] { handler }); + securityInterceptor.setSecureResponse(false); + return securityInterceptor; + } -// @Bean -// SpringDigestPasswordValidationCallbackHandler springDigestPasswordValidationCallbackHandler( -// UserDetailsService securityService) { -// -// SpringDigestPasswordValidationCallbackHandler handler = new SpringDigestPasswordValidationCallbackHandler(); -// handler.setUserDetailsService(securityService); -// return handler; -// } + @Bean + PayloadRootSmartSoapEndpointInterceptor smartSoapEndpointInterceptor(Wss4jSecurityInterceptor securityInterceptor) { + return new PayloadRootSmartSoapEndpointInterceptor(securityInterceptor, + "http://www.springframework.org/spring-ws/samples/airline/schemas/messages", "GetFrequentFlyerMileageRequest"); + } + + @Bean + SpringSecurityPasswordValidationCallbackHandler springSecurityPasswordValidationCallbackHandler( + UserDetailsService userDetailsService) { + + SpringSecurityPasswordValidationCallbackHandler handler = new SpringSecurityPasswordValidationCallbackHandler(); + handler.setUserDetailsService(userDetailsService); + return handler; + } @Bean SaajSoapMessageFactory messageFactory() { diff --git a/airline/server/src/main/resources/application.properties b/airline/server/src/main/resources/application.properties index 9fe8aba..7022d42 100644 --- a/airline/server/src/main/resources/application.properties +++ b/airline/server/src/main/resources/application.properties @@ -1,5 +1,11 @@ logging.level.org.springframework.data=DEBUG -logging.level.org.springframework.web=DEBUG +logging.level.org.springframework.web=TRACE logging.level.org.springframework.jms=DEBUG logging.level.org.springframework.ws=DEBUG +logging.level.org.springframework.security=TRACE #logging.level.org.apache.activemq=DEBUG +logging.level.org.springframework.ws.client.MessageTracing.sent=DEBUG +logging.level.org.springframework.ws.server.MessageTracing.sent=DEBUG +logging.level.org.springframework.ws.client.MessageTracing.received=TRACE +logging.level.org.springframework.ws.server.MessageTracing.received=TRACE +logging.level.org.springframework.ws.samples=DEBUG