From cfa4525c21e714971453b2688e6e2e14fd173ff4 Mon Sep 17 00:00:00 2001 From: anhefti Date: Thu, 22 Nov 2018 10:39:18 +0100 Subject: [PATCH] Added OAuth2 AuthServer and two ResServer for admin and monitoring SEBSERV-4 --- .../java/ch/ethz/seb/sebserver/SEBServer.java | 1 + .../ethz/seb/sebserver/WebSecurityConfig.java | 14 +- .../ch/ethz/seb/sebserver/gbl/JSONMapper.java | 34 ++++ .../ClientSessionWebSecurityConfig.java | 147 ++++++++-------- .../weblayer/SebClientTestController.java | 29 ++++ .../weblayer/WebServiceUserDetails.java | 48 ++++++ .../webservice/weblayer/WsTestController.java | 10 +- .../oauth/AdminResourceServerConfig.java | 89 ++++++++++ .../oauth/AuthorizationServerConfig.java | 85 ++++++++++ .../weblayer/oauth/GuiClientDetails.java | 160 ++++++++++++++++++ .../oauth/SebClientResourceServerConfig.java | 75 ++++++++ .../oauth/WebServiceClientDetails.java | 80 +++++++++ .../config/application-dev-ws.properties | 4 +- src/main/resources/logback.xml | 1 + 14 files changed, 681 insertions(+), 96 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/gbl/JSONMapper.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/SebClientTestController.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/WebServiceUserDetails.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/AdminResourceServerConfig.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/AuthorizationServerConfig.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/GuiClientDetails.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/SebClientResourceServerConfig.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/WebServiceClientDetails.java diff --git a/src/main/java/ch/ethz/seb/sebserver/SEBServer.java b/src/main/java/ch/ethz/seb/sebserver/SEBServer.java index 0c23f5b2..225c13d9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/SEBServer.java +++ b/src/main/java/ch/ethz/seb/sebserver/SEBServer.java @@ -16,6 +16,7 @@ import org.springframework.context.annotation.Configuration; @Configuration @SpringBootApplication(exclude = { + // OAuth2ResourceServerAutoConfiguration.class, UserDetailsServiceAutoConfiguration.class, DataSourceAutoConfiguration.class }) diff --git a/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java b/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java index 9f7e8e50..d85e7114 100644 --- a/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/WebSecurityConfig.java @@ -10,10 +10,6 @@ package ch.ethz.seb.sebserver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.annotation.Order; -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.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @@ -25,21 +21,13 @@ import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; @Configuration @WebServiceProfile @GuiProfile -@EnableWebSecurity -@Order(0) -public class WebSecurityConfig extends WebSecurityConfigurerAdapter { +public class WebSecurityConfig { /** Spring bean name of user password encoder */ public static final String USER_PASSWORD_ENCODER_BEAN_NAME = "userPasswordEncoder"; /** Spring bean name of client (application) password encoder */ public static final String CLIENT_PASSWORD_ENCODER_BEAN_NAME = "clientPasswordEncoder"; - @Override - protected void configure(final HttpSecurity http) throws Exception { - System.out.println("**************** Overall WebConfig: "); - - } - /** Password encoder used for user passwords (stronger protection) */ @Bean(USER_PASSWORD_ENCODER_BEAN_NAME) public PasswordEncoder userPasswordEncoder() { diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/JSONMapper.java b/src/main/java/ch/ethz/seb/sebserver/gbl/JSONMapper.java new file mode 100644 index 00000000..959d9ffc --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/JSONMapper.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.gbl; + +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.joda.JodaModule; + +@Lazy +@Component +public class JSONMapper extends ObjectMapper { + + private static final long serialVersionUID = 2883304481547670626L; + + public JSONMapper() { + super(); + super.registerModule(new JodaModule()); + super.configure( + com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, + false); + super.configure( + com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_WITH_ZONE_ID, + false); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/ClientSessionWebSecurityConfig.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/ClientSessionWebSecurityConfig.java index 6429bc29..2f37d421 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/ClientSessionWebSecurityConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/ClientSessionWebSecurityConfig.java @@ -9,97 +9,88 @@ package ch.ethz.seb.sebserver.webservice.weblayer; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.annotation.Order; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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.http.SessionCreationPolicy; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.security.web.util.matcher.OrRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfiguration; +import org.springframework.security.oauth2.provider.token.AccessTokenConverter; +import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter; +import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.UserAuthenticationConverter; +import ch.ethz.seb.sebserver.WebSecurityConfig; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.webservice.weblayer.oauth.AdminResourceServerConfig; +import ch.ethz.seb.sebserver.webservice.weblayer.oauth.SebClientResourceServerConfig; +import ch.ethz.seb.sebserver.webservice.weblayer.oauth.WebServiceClientDetails; -/** Spring web security configuration for all endpoints needed for SEB-Client session management. - * This are: - * - *
- *      - /sebauth/sebhandshake/ the SEB-Client handshake and authentication endpoint
- *      - /sebauth/lmshandshake/ the LMS-Client handshake and authentication endpoint
- *      - /ws/ the root of all web-socket endpoints on HTTP level
- * 
- * - * This configuration secures the above endpoints by using custom client authentication filter */ -@Configuration @WebServiceProfile +@Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) -@Order(2) +@EnableWebSecurity public class ClientSessionWebSecurityConfig extends WebSecurityConfigurerAdapter { - public static final AntPathRequestMatcher SEB_HANDSHAKE_ENDPOINT = - new AntPathRequestMatcher("/sebauth/sebhandshake/**"); - public static final AntPathRequestMatcher SEB_WEB_SOCKET_ENDPOINT = - new AntPathRequestMatcher("/ws/**"); - public static final AntPathRequestMatcher LMS_HANDSHAKE_ENDPOINT = - new AntPathRequestMatcher("/sebauth/lmshandshake/**"); - - public static final RequestMatcher SEB_CLIENT_ENDPOINTS = new OrRequestMatcher( - SEB_HANDSHAKE_ENDPOINT, - SEB_WEB_SOCKET_ENDPOINT); - - public static final RequestMatcher SEB_CONNECTION_PROTECTED_URLS = new OrRequestMatcher( - SEB_CLIENT_ENDPOINTS, - LMS_HANDSHAKE_ENDPOINT); + /** Spring bean name of user password encoder */ + public static final String AUTHENTICATION_MANAGER = "AUTHENTICATION_MANAGER"; @Autowired - private CustomAuthenticationError customAuthenticationError; + private WebServiceUserDetails webServiceUserDetails; + @Autowired + @Qualifier(WebSecurityConfig.USER_PASSWORD_ENCODER_BEAN_NAME) + private PasswordEncoder userPasswordEncoder; + @Autowired + private TokenStore tokenStore; + @Autowired + private WebServiceClientDetails webServiceClientDetails; + + @Bean + public AccessTokenConverter accessTokenConverter() { + final DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter(); + accessTokenConverter.setUserTokenConverter(userAuthenticationConverter()); + return accessTokenConverter; + } + + @Bean + public UserAuthenticationConverter userAuthenticationConverter() { + final DefaultUserAuthenticationConverter userAuthenticationConverter = + new DefaultUserAuthenticationConverter(); + userAuthenticationConverter.setUserDetailsService(this.webServiceUserDetails); + return userAuthenticationConverter; + } @Override - protected void configure(final HttpSecurity http) throws Exception { - System.out.println("**************** WebServiceWebConfig: "); - //@formatter:off - http - // The Web-Service is designed as a stateless Rest API - // for SEB session management only endpoints for handshake and web-socket is used what is stateless on HTTP - .sessionManagement() - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) - -// TODO -// .and() -// .requestMatcher(SEB_CONNECTION_PROTECTED_URLS) -// .addFilterBefore( -// this.sebClientAuthenticationFilter, -// BasicAuthenticationFilter.class) -// .addFilterBefore( -// this.lmsClientAuthenticationFilter, -// SEBClientAuthenticationFilter.class) -// .authorizeRequests() -// .requestMatchers(SEB_CONNECTION_PROTECTED_URLS) -// .authenticated() -// instead of: - - .and() - .antMatcher("/webservice/**") - .authorizeRequests() - .anyRequest() - .fullyAuthenticated() -// end TODO - - .and() - .exceptionHandling() - .defaultAuthenticationEntryPointFor( - this.customAuthenticationError, - SEB_CONNECTION_PROTECTED_URLS) - - .and() - // disable session based security and functionality - .formLogin().disable() - .httpBasic().disable() - .logout().disable() - .headers().frameOptions().disable() - .and() - .csrf().disable(); - //@formatter:on + @Bean(AUTHENTICATION_MANAGER) + public AuthenticationManager authenticationManagerBean() throws Exception { + final AuthenticationManager authenticationManagerBean = super.authenticationManagerBean(); + return authenticationManagerBean; } + + @Override + protected void configure(final AuthenticationManagerBuilder auth) throws Exception { + auth + .userDetailsService(this.webServiceUserDetails) + .passwordEncoder(this.userPasswordEncoder); + } + + @Bean + protected ResourceServerConfiguration sebServerAdminAPIResources() { + return new AdminResourceServerConfig(accessTokenConverter()); + } + + @Bean + protected ResourceServerConfiguration sebServerSebClientAPIResources() throws Exception { + return new SebClientResourceServerConfig( + accessTokenConverter(), + this.tokenStore, + this.webServiceClientDetails, + authenticationManagerBean()); + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/SebClientTestController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/SebClientTestController.java new file mode 100644 index 00000000..6225f8f1 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/SebClientTestController.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.webservice.weblayer; + +import java.security.Principal; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; + +@RestController +@RequestMapping("/sebclient") +@WebServiceProfile +public class SebClientTestController { + + @RequestMapping(value = "/hello", method = RequestMethod.GET) + public String helloFromWebService(final Principal principal) { + return "Hello From Seb-Cleint-Web-Service"; + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/WebServiceUserDetails.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/WebServiceUserDetails.java new file mode 100644 index 00000000..0922d9c0 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/WebServiceUserDetails.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.webservice.weblayer; + +import java.util.Collections; + +import org.springframework.context.annotation.Lazy; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Component; + +@Lazy +@Component +public class WebServiceUserDetails implements UserDetailsService { + +// private final UserDao userDao; +// +// public InternalUserDetailsService(final UserDao userDao) { +// this.userDao = userDao; +// } + + @Override + public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException { + return new User( + username, + "$2a$04$btj5PkII8IIHLE7zbQOd3u7YghHeClG7k1ZzYbtybRnd5h1YqwTf.", + Collections.emptyList()); + +// try { +// final org.eth.demo.sebserver.domain.rest.admin.User byUserName = this.userDao.byUserName(username); +// if (byUserName == null) { +// throw new UsernameNotFoundException("No User with name: " + username + " found"); +// } +// return byUserName; +// } catch (final Exception e) { +// throw new UsernameNotFoundException("No User with name: " + username + " found"); +// } + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/WsTestController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/WsTestController.java index 6743fc27..6517c54e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/WsTestController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/WsTestController.java @@ -8,6 +8,8 @@ package ch.ethz.seb.sebserver.webservice.weblayer; +import java.security.Principal; + import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @@ -15,7 +17,7 @@ import org.springframework.web.bind.annotation.RestController; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; @RestController -@RequestMapping("/webservice") +@RequestMapping("/admin") @WebServiceProfile public class WsTestController { @@ -23,9 +25,9 @@ public class WsTestController { System.out.println("************** TestController webservice"); } - @RequestMapping(value = "/", method = RequestMethod.GET) - public String helloFromWebService() { - return "Hello From Web-Service"; + @RequestMapping(value = "/hello", method = RequestMethod.GET) + public String helloFromWebService(final Principal principal) { + return "Hello From Admin-Web-Service"; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/AdminResourceServerConfig.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/AdminResourceServerConfig.java new file mode 100644 index 00000000..5656dfd7 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/AdminResourceServerConfig.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.webservice.weblayer.oauth; + +import java.util.Arrays; +import java.util.List; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfiguration; +import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.token.AccessTokenConverter; +import org.springframework.security.oauth2.provider.token.RemoteTokenServices; +import org.springframework.web.util.UriComponentsBuilder; + +public class AdminResourceServerConfig extends ResourceServerConfiguration { + + @Value("${server.address}") + private String webServerAdress; + @Value("${server.port}") + private String webServerPort; + @Value("${sebserver.webservice.protocol}") + private String webProtocol; + @Value("${sebserver.oauth.clients.guiClient.id}") + private String guiClientId; + // TODO secret should not be referenced here (should go to stack and disappear after use) + @Value("${sebserver.oauth.clients.guiClient.secret}") + private String guiClientSecret; + + public AdminResourceServerConfig(final AccessTokenConverter accessTokenConverter) { + setConfigurers(Arrays. asList(new ResourceServerConfigurerAdapter() { + + @Override + public void configure(final ResourceServerSecurityConfigurer resources) throws Exception { + resources.resourceId(GuiClientDetails.RESOURCE_ID); + // TODO try to use DefualtTokenServices like in SebClientResourceServerConfig + final RemoteTokenServices tokenService = new RemoteTokenServices(); + tokenService.setCheckTokenEndpointUrl( + UriComponentsBuilder + .fromHttpUrl(AdminResourceServerConfig.this.webProtocol + "://" + + AdminResourceServerConfig.this.webServerAdress) + .port(AdminResourceServerConfig.this.webServerPort) + .path("oauth/check_token") + .toUriString()); + tokenService.setClientId(AdminResourceServerConfig.this.guiClientId); + tokenService.setClientSecret(AdminResourceServerConfig.this.guiClientSecret); + tokenService.setAccessTokenConverter(accessTokenConverter); + resources.tokenServices(tokenService); + } + + @Override + public void configure(final HttpSecurity http) throws Exception { + http + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .antMatcher("/admin/**") + .authorizeRequests() + .anyRequest() + .authenticated() + .and() + .formLogin().disable() + .httpBasic().disable() + .logout().disable() + .headers().frameOptions().disable() + .and() + .csrf().disable(); + } + + })); + setOrder(1); + } + + // Switch off the Spring Boot auto configuration + @Override + public void setConfigurers(final List configurers) { + super.setConfigurers(configurers); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/AuthorizationServerConfig.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/AuthorizationServerConfig.java new file mode 100644 index 00000000..e9765cc6 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/AuthorizationServerConfig.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.webservice.weblayer.oauth; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.token.AccessTokenConverter; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; + +import ch.ethz.seb.sebserver.WebSecurityConfig; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.webservice.weblayer.ClientSessionWebSecurityConfig; +import ch.ethz.seb.sebserver.webservice.weblayer.WebServiceUserDetails; + +@WebServiceProfile +@Configuration +@EnableAuthorizationServer +@Order(100) +public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { + + @Autowired + private AccessTokenConverter accessTokenConverter; + @Autowired + private DataSource dataSource; + @Autowired + private WebServiceUserDetails webServiceUserDetails; + @Autowired + private WebServiceClientDetails webServiceClientDetails; + @Autowired + @Qualifier(WebSecurityConfig.CLIENT_PASSWORD_ENCODER_BEAN_NAME) + private PasswordEncoder clientPasswordEncoder; + @Autowired + @Qualifier(ClientSessionWebSecurityConfig.AUTHENTICATION_MANAGER) + private AuthenticationManager authenticationManager; + + @Override + public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception { + oauthServer + .tokenKeyAccess("permitAll()") + .checkTokenAccess("isAuthenticated()") + .passwordEncoder(this.clientPasswordEncoder); + } + + @Override + public void configure(final ClientDetailsServiceConfigurer clients) throws Exception { + clients.withClientDetails(this.webServiceClientDetails); + } + + @Bean + public TokenStore tokenStore() { + return new JdbcTokenStore(this.dataSource); + } + + @Override + public void configure(final AuthorizationServerEndpointsConfigurer endpoints) { + final JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter(); + jwtAccessTokenConverter.setAccessTokenConverter(this.accessTokenConverter); + endpoints + .tokenStore(tokenStore()) + .authenticationManager(this.authenticationManager) + .userDetailsService(this.webServiceUserDetails) + .accessTokenConverter(jwtAccessTokenConverter); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/GuiClientDetails.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/GuiClientDetails.java new file mode 100644 index 00000000..5056ebce --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/GuiClientDetails.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.webservice.weblayer.oauth; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.WebSecurityConfig; +import ch.ethz.seb.sebserver.gbl.util.Utils; + +@Lazy +@Component +public final class GuiClientDetails implements ClientDetails { + + private static final long serialVersionUID = 4505193832353978832L; + + public static final String RESOURCE_ID = "seb-server-administration-api"; + + private static final Set GRANT_TYPES = Utils.immutableSetOf("password", "refresh_token"); + private static final Set RESOURCE_IDS = Utils.immutableSetOf(RESOURCE_ID); + private static final Set SCOPES = Utils.immutableSetOf("read", "write"); + + private final String guiClientId; + private final String guiClientSecret; + private final Integer guiClientAccessTokenValiditySeconds; + private final Integer guiClientRefreshTokenValiditySeconds; + + public GuiClientDetails( + @Qualifier(WebSecurityConfig.CLIENT_PASSWORD_ENCODER_BEAN_NAME) final PasswordEncoder clientPasswordEncoder, + @Value("${sebserver.oauth.clients.guiClient.id}") final String guiClientId, + @Value("${sebserver.oauth.clients.guiClient.secret}") final String guiClientSecret, + @Value("${sebserver.oauth.clients.guiClient.accessTokenValiditySeconds}") final Integer guiClientAccessTokenValiditySeconds, + @Value("${sebserver.oauth.clients.guiClient.refreshTokenValiditySeconds}") final Integer guiClientRefreshTokenValiditySeconds) { + + this.guiClientId = guiClientId; + this.guiClientSecret = clientPasswordEncoder.encode(guiClientSecret); + this.guiClientAccessTokenValiditySeconds = guiClientAccessTokenValiditySeconds; + this.guiClientRefreshTokenValiditySeconds = guiClientRefreshTokenValiditySeconds; + } + + @Autowired + @Qualifier(WebSecurityConfig.CLIENT_PASSWORD_ENCODER_BEAN_NAME) + private PasswordEncoder clientPasswordEncoder; + + @Override + public String getClientId() { + return this.guiClientId; + } + + @Override + public Set getResourceIds() { + return RESOURCE_IDS; + } + + @Override + public boolean isSecretRequired() { + return true; + } + + @Override + public String getClientSecret() { + return this.guiClientSecret; + } + + @Override + public boolean isScoped() { + return true; + } + + @Override + public Set getScope() { + return SCOPES; + } + + @Override + public Set getAuthorizedGrantTypes() { + return GRANT_TYPES; + } + + @Override + public Set getRegisteredRedirectUri() { + return Collections.emptySet(); + } + + @Override + public Collection getAuthorities() { + return Collections.emptyList(); + } + + @Override + public Integer getAccessTokenValiditySeconds() { + return this.guiClientAccessTokenValiditySeconds; + } + + @Override + public Integer getRefreshTokenValiditySeconds() { + return this.guiClientRefreshTokenValiditySeconds; + } + + @Override + public boolean isAutoApprove(final String scope) { + return true; + } + + @Override + public Map getAdditionalInformation() { + return Collections.emptyMap(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.guiClientId == null) ? 0 : this.guiClientId.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final GuiClientDetails other = (GuiClientDetails) obj; + if (this.guiClientId == null) { + if (other.guiClientId != null) + return false; + } else if (!this.guiClientId.equals(other.guiClientId)) + return false; + return true; + } + + @Override + public String toString() { + return "GuiClientDetails [guiClientId=" + this.guiClientId + ", guiClientSecret=" + this.guiClientSecret + + ", guiClientAccessTokenValiditySeconds=" + this.guiClientAccessTokenValiditySeconds + + ", guiClientRefreshTokenValiditySeconds=" + this.guiClientRefreshTokenValiditySeconds + + ", clientPasswordEncoder=" + this.clientPasswordEncoder + "]"; + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/SebClientResourceServerConfig.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/SebClientResourceServerConfig.java new file mode 100644 index 00000000..d808358d --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/SebClientResourceServerConfig.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.webservice.weblayer.oauth; + +import java.util.Arrays; +import java.util.List; + +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfiguration; +import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.token.AccessTokenConverter; +import org.springframework.security.oauth2.provider.token.DefaultTokenServices; +import org.springframework.security.oauth2.provider.token.TokenStore; + +public class SebClientResourceServerConfig extends ResourceServerConfiguration { + + public SebClientResourceServerConfig( + final AccessTokenConverter accessTokenConverter, + final TokenStore tokenStore, + final WebServiceClientDetails webServiceClientDetails, + final AuthenticationManager authenticationManager) { + + setConfigurers(Arrays. asList(new ResourceServerConfigurerAdapter() { + + @Override + public void configure(final ResourceServerSecurityConfigurer resources) throws Exception { + resources.resourceId(WebServiceClientDetails.RESOURCE_ID); + final DefaultTokenServices tokenService = new DefaultTokenServices(); + tokenService.setTokenStore(tokenStore); + tokenService.setClientDetailsService(webServiceClientDetails); + tokenService.setSupportRefreshToken(false); + tokenService.setAuthenticationManager(authenticationManager); + resources.tokenServices(tokenService); + } + + @Override + public void configure(final HttpSecurity http) throws Exception { + http + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .antMatcher("/sebclient/**") + .authorizeRequests() + .anyRequest() + .authenticated() + .and() + .formLogin().disable() + .httpBasic().disable() + .logout().disable() + .headers().frameOptions().disable() + .and() + .csrf().disable(); + } + + })); + setOrder(2); + } + + // Switch off the Spring Boot auto configuration + @Override + public void setConfigurers(final List configurers) { + super.setConfigurers(configurers); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/WebServiceClientDetails.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/WebServiceClientDetails.java new file mode 100644 index 00000000..c4b91569 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/WebServiceClientDetails.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.webservice.weblayer.oauth; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.ClientRegistrationException; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.WebSecurityConfig; + +@Lazy +@Component +public class WebServiceClientDetails implements ClientDetailsService { + + private static final Logger log = LoggerFactory.getLogger(WebServiceClientDetails.class); + + public static final String[] SEB_CLIENT_GRANT_TYPES = new String[] { "client_credentials", "refresh_token" }; + public static final String[] SEB_CLIENT_SCOPES = new String[] { "web-service-api-read", "web-service-api-write" }; + public static final String RESOURCE_ID = "seb-server-seb-client-api"; + + @Value("${sebserver.oauth.clients.guiClient.accessTokenValiditySeconds}") + private Integer guiClientAccessTokenValiditySeconds; + @Value("${sebserver.oauth.clients.guiClient.refreshTokenValiditySeconds}") + private Integer guiClientRefreshTokenValiditySeconds; + + private final GuiClientDetails guiClientDetails; + @Autowired + @Qualifier(WebSecurityConfig.CLIENT_PASSWORD_ENCODER_BEAN_NAME) + private PasswordEncoder clientPasswordEncoder; + + public WebServiceClientDetails(final GuiClientDetails guiClientDetails) { + this.guiClientDetails = guiClientDetails; + } + + @Override + public ClientDetails loadClientByClientId(final String clientId) throws ClientRegistrationException { + if (clientId == null) { + throw new ClientRegistrationException("clientId is null"); + } + + if (clientId.equals(this.guiClientDetails.getClientId())) { + return this.guiClientDetails; + } + + final ClientDetails forSEBClientAPI = getForSEBClientAPI(clientId); + if (forSEBClientAPI != null) { + return forSEBClientAPI; + } + + log.warn("ClientDetails for clientId: {} not found", clientId); + throw new ClientRegistrationException("clientId not found"); + } + + private ClientDetails getForSEBClientAPI(final String clientId) { + // TODO create ClientDetails from matching LMSSetup + final BaseClientDetails baseClientDetails = new BaseClientDetails( + clientId, + RESOURCE_ID, + "read,write", + "client_credentials,refresh_token", ""); + baseClientDetails.setClientSecret(this.clientPasswordEncoder.encode("test")); + return baseClientDetails; + } + +} diff --git a/src/main/resources/config/application-dev-ws.properties b/src/main/resources/config/application-dev-ws.properties index de055c2c..c260d96e 100644 --- a/src/main/resources/config/application-dev-ws.properties +++ b/src/main/resources/config/application-dev-ws.properties @@ -1,6 +1,6 @@ server.address=localhost server.port=8090 -server.servlet.context-path=/api/ +server.servlet.context-path=/ spring.datasource.initialize=true spring.datasource.initialization-mode=always @@ -10,6 +10,8 @@ spring.datasource.platform=dev sebserver.oauth.clients.guiClient.accessTokenValiditySeconds=1800 sebserver.oauth.clients.guiClient.refreshTokenValiditySeconds=-1 +sebserver.oauth.clients.sebClient.accessTokenValiditySeconds=1800 +sebserver.oauth.clients.sebClient.refreshTokenValiditySeconds=-1 sebserver.webservice.protocol=http sebserver.client.connection-strategy=HTTP diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 1be5cd9a..af31e7ad 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -13,6 +13,7 @@ +