diff --git a/server/src/main/java/de/helfenkannjeder/come2help/server/configuration/security/OAuth2ClientConfigurer.java b/server/src/main/java/de/helfenkannjeder/come2help/server/configuration/security/OAuth2ClientConfigurer.java index 1a2b3cb..2c3980e 100644 --- a/server/src/main/java/de/helfenkannjeder/come2help/server/configuration/security/OAuth2ClientConfigurer.java +++ b/server/src/main/java/de/helfenkannjeder/come2help/server/configuration/security/OAuth2ClientConfigurer.java @@ -1,10 +1,14 @@ package de.helfenkannjeder.come2help.server.configuration.security; +import com.fasterxml.jackson.databind.ObjectMapper; import de.helfenkannjeder.come2help.server.security.jwt.FacebookSuccessHandler; import de.helfenkannjeder.come2help.server.security.jwt.GoogleSuccessHandler; +import de.helfenkannjeder.come2help.server.security.jwt.HelfenkannjederSuccessHandler; import de.helfenkannjeder.come2help.server.security.jwt.StatelessJwtAuthenticationFilter; -import java.util.Arrays; -import javax.servlet.Filter; +import de.helfenkannjeder.come2help.server.security.oauth2.CustomOAuthAuthenticationProcessingFilter; +import de.helfenkannjeder.come2help.server.security.oauth2.token.AccessTokenService; +import de.helfenkannjeder.come2help.server.security.oauth2.token.AuthorizationCodeAccessTokenService; +import de.helfenkannjeder.come2help.server.security.oauth2.token.PasswordAccessTokenService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -13,6 +17,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint; @@ -20,12 +25,19 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.web.filter.CompositeFilter; +import javax.servlet.Filter; +import java.util.Arrays; + @Configuration +@EnableOAuth2Client public class OAuth2ClientConfigurer extends WebSecurityConfigurerAdapter { @Autowired private MappingJackson2HttpMessageConverter jsonMessageConverter; + @Autowired + private ObjectMapper objectMapper; + /** * Configure HttpSecurity. This includes:
* - resources requiring authorized
@@ -38,17 +50,25 @@ public class OAuth2ClientConfigurer extends WebSecurityConfigurerAdapter { */ @Override protected void configure(HttpSecurity httpSecurity) throws Exception { + // @formatter:off httpSecurity - .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() - .csrf().disable().headers().frameOptions().disable().and() - .antMatcher("/**").authorizeRequests() - .antMatchers("/login/**").permitAll() - .antMatchers("/abilities/**").permitAll() - .antMatchers("/jsondoc/**").permitAll() - .antMatchers("/jsondoc-ui.html").permitAll() - .antMatchers("/webjars/jsondoc-ui-webjar/**").permitAll() - .anyRequest().authenticated().and() - .exceptionHandling().authenticationEntryPoint(new Http403ForbiddenEntryPoint()).and(); + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .csrf() .disable() + .headers().frameOptions().disable() + .and() + .antMatcher("/**").authorizeRequests() + .antMatchers("/login/**").permitAll() + .antMatchers("/abilities/**").permitAll() + .antMatchers("/jsondoc/**").permitAll() + .antMatchers("/jsondoc-ui.html").permitAll() + .antMatchers("/webjars/jsondoc-ui-webjar/**").permitAll() + .anyRequest().authenticated() + .and() + .exceptionHandling() + .authenticationEntryPoint(new Http403ForbiddenEntryPoint()); + // @formatter:on httpSecurity.addFilterBefore(statelessJwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); httpSecurity.addFilterBefore(createOAuth2Filter(), BasicAuthenticationFilter.class); @@ -57,18 +77,26 @@ protected void configure(HttpSecurity httpSecurity) throws Exception { private Filter createOAuth2Filter() { CompositeFilter filter = new CompositeFilter(); filter.setFilters(Arrays.asList( - createOAuth2Filter(facebook(), facebookSuccessHandler(), "/login/facebook"), - createOAuth2Filter(google(), googleSuccessHandler(), "/login/google")) + createOAuth2Filter(facebook(), facebookSuccessHandler(), "/login/facebook", getAuthorizationCodeAccessTokenService()), + createOAuth2Filter(google(), googleSuccessHandler(), "/login/google", getAuthorizationCodeAccessTokenService()), + createOAuth2Filter(helfenkannjeder(), helfenkannjederSuccessHandler(), "/login/helfenkannjeder", new PasswordAccessTokenService(objectMapper)) + ) ); return filter; } - private AbstractAuthenticationProcessingFilter createOAuth2Filter(ClientResourceDetails clientDetails, AuthenticationSuccessHandler successHandler, String path) { - CustomOAuthAuthenticationProcessingFilter oauthFilter = new CustomOAuthAuthenticationProcessingFilter(path, clientDetails, jsonMessageConverter); + private AbstractAuthenticationProcessingFilter createOAuth2Filter(ClientResourceDetails clientDetails, AuthenticationSuccessHandler successHandler, String path, AccessTokenService accessTokenService) { + CustomOAuthAuthenticationProcessingFilter oauthFilter = new CustomOAuthAuthenticationProcessingFilter(path, clientDetails, + accessTokenService); + accessTokenService.setClientResourceDetails(clientDetails); oauthFilter.setAuthenticationSuccessHandler(successHandler); return oauthFilter; } + private AccessTokenService getAuthorizationCodeAccessTokenService() { + return new AuthorizationCodeAccessTokenService(jsonMessageConverter); + } + @Bean protected StatelessJwtAuthenticationFilter statelessJwtAuthenticationFilter() { return new StatelessJwtAuthenticationFilter(); @@ -86,6 +114,12 @@ protected ClientResourceDetails google() { return new ClientResourceDetails(); } + @Bean + @ConfigurationProperties("helfenkannjeder") + protected ClientResourceDetails helfenkannjeder() { + return new ClientResourceDetails(); + } + @Bean protected AuthenticationSuccessHandler facebookSuccessHandler() { return new FacebookSuccessHandler(); @@ -95,4 +129,9 @@ protected AuthenticationSuccessHandler facebookSuccessHandler() { protected AuthenticationSuccessHandler googleSuccessHandler() { return new GoogleSuccessHandler(); } + + @Bean + protected AuthenticationSuccessHandler helfenkannjederSuccessHandler() { + return new HelfenkannjederSuccessHandler(); + } } diff --git a/server/src/main/java/de/helfenkannjeder/come2help/server/security/jwt/HelfenkannjederSuccessHandler.java b/server/src/main/java/de/helfenkannjeder/come2help/server/security/jwt/HelfenkannjederSuccessHandler.java new file mode 100644 index 0000000..845e570 --- /dev/null +++ b/server/src/main/java/de/helfenkannjeder/come2help/server/security/jwt/HelfenkannjederSuccessHandler.java @@ -0,0 +1,32 @@ +package de.helfenkannjeder.come2help.server.security.jwt; + +import org.springframework.stereotype.Component; + +@Component +public class HelfenkannjederSuccessHandler extends JwtCreatingAuthenticationSuccessHandler { + + @Override + protected String providerIdentifier() { + return "google"; + } + + @Override + protected String surnameField() { + return "family_name"; + } + + @Override + protected String givenNameField() { + return "given_name"; + } + + @Override + protected String emailField() { + return "email"; + } + + @Override + protected String idField() { + return "id"; + } +} diff --git a/server/src/main/java/de/helfenkannjeder/come2help/server/security/oauth2/CustomOAuthAuthenticationProcessingFilter.java b/server/src/main/java/de/helfenkannjeder/come2help/server/security/oauth2/CustomOAuthAuthenticationProcessingFilter.java new file mode 100644 index 0000000..ef70aff --- /dev/null +++ b/server/src/main/java/de/helfenkannjeder/come2help/server/security/oauth2/CustomOAuthAuthenticationProcessingFilter.java @@ -0,0 +1,44 @@ +package de.helfenkannjeder.come2help.server.security.oauth2; + +import de.helfenkannjeder.come2help.server.configuration.security.ClientResourceDetails; +import de.helfenkannjeder.come2help.server.security.oauth2.token.AccessTokenService; +import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; +import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class CustomOAuthAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter { + + private final UserInfoTokenServices tokenService; + private final AccessTokenService accessTokenService; + + public CustomOAuthAuthenticationProcessingFilter(String path, ClientResourceDetails clientResourceDetails, AccessTokenService accessTokenService) { + super(path); + this.tokenService = new UserInfoTokenServices(clientResourceDetails.getResource().getUserInfoUri(), clientResourceDetails.getClient().getClientId()); + this.accessTokenService = accessTokenService; + } + + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { + if (!"POST".equals(request.getMethod())) { + throw new BadCredentialsException("Only POST allowed."); + } + + OAuth2AccessToken accessToken = accessTokenService.getAccessToken(request); + + try { + return tokenService.loadAuthentication(accessToken.getValue()); + } catch (InvalidTokenException e) { + throw new BadCredentialsException("Could not obtain user details from token", e); + } + } + +} diff --git a/server/src/main/java/de/helfenkannjeder/come2help/server/security/oauth2/OAuth2Client.java b/server/src/main/java/de/helfenkannjeder/come2help/server/security/oauth2/OAuth2Client.java new file mode 100644 index 0000000..fd35ce7 --- /dev/null +++ b/server/src/main/java/de/helfenkannjeder/come2help/server/security/oauth2/OAuth2Client.java @@ -0,0 +1,44 @@ +package de.helfenkannjeder.come2help.server.security.oauth2; + +import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext; +import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.security.oauth2.client.token.AccessTokenRequest; +import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest; +import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails; +import org.springframework.security.oauth2.common.OAuth2AccessToken; + +import java.util.List; + +/** + * @author Valentin Zickner + */ +public class OAuth2Client { + + private static final String GRANT_TYPE = "password"; + + private final String tokenUrl; + private final String clientId; + private final String secret; + + public OAuth2Client(String tokenUrl, String clientId, String secret) { + this.tokenUrl = tokenUrl; + this.clientId = clientId; + this.secret = secret; + } + + public OAuth2AccessToken getAccessToken(String username, String password, List scopes) { + ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails(); + + resource.setAccessTokenUri(tokenUrl); + resource.setClientId(clientId); + resource.setClientSecret(secret); + resource.setGrantType(GRANT_TYPE); + resource.setScope(scopes); + + resource.setUsername(username); + resource.setPassword(password); + + AccessTokenRequest atr = new DefaultAccessTokenRequest(); + return new OAuth2RestTemplate(resource, new DefaultOAuth2ClientContext(atr)).getAccessToken(); + } +} diff --git a/server/src/main/java/de/helfenkannjeder/come2help/server/security/oauth2/token/AccessTokenService.java b/server/src/main/java/de/helfenkannjeder/come2help/server/security/oauth2/token/AccessTokenService.java new file mode 100644 index 0000000..6fa5ecb --- /dev/null +++ b/server/src/main/java/de/helfenkannjeder/come2help/server/security/oauth2/token/AccessTokenService.java @@ -0,0 +1,15 @@ +package de.helfenkannjeder.come2help.server.security.oauth2.token; + +import de.helfenkannjeder.come2help.server.configuration.security.ClientResourceDetails; +import org.springframework.security.oauth2.common.OAuth2AccessToken; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +/** + * @author Valentin Zickner + */ +public interface AccessTokenService { + void setClientResourceDetails(ClientResourceDetails clientResourceDetails); + OAuth2AccessToken getAccessToken(HttpServletRequest httpServletRequest) throws IOException; +} diff --git a/server/src/main/java/de/helfenkannjeder/come2help/server/configuration/security/CustomOAuthAuthenticationProcessingFilter.java b/server/src/main/java/de/helfenkannjeder/come2help/server/security/oauth2/token/AuthorizationCodeAccessTokenService.java similarity index 61% rename from server/src/main/java/de/helfenkannjeder/come2help/server/configuration/security/CustomOAuthAuthenticationProcessingFilter.java rename to server/src/main/java/de/helfenkannjeder/come2help/server/security/oauth2/token/AuthorizationCodeAccessTokenService.java index b38dd76..39a620f 100644 --- a/server/src/main/java/de/helfenkannjeder/come2help/server/configuration/security/CustomOAuthAuthenticationProcessingFilter.java +++ b/server/src/main/java/de/helfenkannjeder/come2help/server/security/oauth2/token/AuthorizationCodeAccessTokenService.java @@ -1,45 +1,40 @@ -package de.helfenkannjeder.come2help.server.configuration.security; +package de.helfenkannjeder.come2help.server.security.oauth2.token; -import java.io.IOException; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices; +import de.helfenkannjeder.come2help.server.configuration.security.ClientResourceDetails; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException; import org.springframework.security.oauth2.client.token.AccessTokenRequest; import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest; import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider; import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; -import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; -public class CustomOAuthAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter { +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; - private final ClientResourceDetails clientResourceDetails; - private final UserInfoTokenServices tokenService; - private final AuthorizationCodeAccessTokenProvider accessTokenProvider = new AuthorizationCodeAccessTokenProvider(); +/** + * @author Valentin Zickner + */ +public class AuthorizationCodeAccessTokenService implements AccessTokenService { + + private ClientResourceDetails clientResourceDetails; private final MappingJackson2HttpMessageConverter jsonMessageConverter; + private final AuthorizationCodeAccessTokenProvider accessTokenProvider = new AuthorizationCodeAccessTokenProvider(); - public CustomOAuthAuthenticationProcessingFilter(String path, ClientResourceDetails clientResourceDetails, MappingJackson2HttpMessageConverter jsonMessageConverter) { - super(path); - this.clientResourceDetails = clientResourceDetails; - this.tokenService = new UserInfoTokenServices(clientResourceDetails.getResource().getUserInfoUri(), clientResourceDetails.getClient().getClientId()); - this.accessTokenProvider.setStateMandatory(false); + public AuthorizationCodeAccessTokenService(MappingJackson2HttpMessageConverter jsonMessageConverter) { this.jsonMessageConverter = jsonMessageConverter; + this.accessTokenProvider.setStateMandatory(false); } @Override - public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { - if (!"POST".equals(request.getMethod())) { - throw new BadCredentialsException("Only POST allowed."); - } + public void setClientResourceDetails(ClientResourceDetails clientResourceDetails) { + this.clientResourceDetails = clientResourceDetails; + } + @Override + public OAuth2AccessToken getAccessToken(HttpServletRequest request) throws IOException { OAuth2AccessToken accessToken; try { AccessTokenRequest accessTokenRequest = createAccessTokenRequest(request); @@ -48,11 +43,7 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ throw new BadCredentialsException("Could not obtain access token", e); } - try { - return tokenService.loadAuthentication(accessToken.getValue()); - } catch (InvalidTokenException e) { - throw new BadCredentialsException("Could not obtain user details from token", e); - } + return accessToken; } private AccessTokenRequest createAccessTokenRequest(HttpServletRequest request) throws IOException { @@ -81,4 +72,6 @@ public String getCode() { return code; } } + + } diff --git a/server/src/main/java/de/helfenkannjeder/come2help/server/security/oauth2/token/PasswordAccessTokenService.java b/server/src/main/java/de/helfenkannjeder/come2help/server/security/oauth2/token/PasswordAccessTokenService.java new file mode 100644 index 0000000..20c7f2a --- /dev/null +++ b/server/src/main/java/de/helfenkannjeder/come2help/server/security/oauth2/token/PasswordAccessTokenService.java @@ -0,0 +1,59 @@ +package de.helfenkannjeder.come2help.server.security.oauth2.token; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.helfenkannjeder.come2help.server.configuration.security.ClientResourceDetails; +import de.helfenkannjeder.come2help.server.security.oauth2.OAuth2Client; +import org.springframework.security.oauth2.common.OAuth2AccessToken; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.Collections; + +/** + * @author Valentin Zickner + */ +public class PasswordAccessTokenService implements AccessTokenService { + + private final ObjectMapper objectMapper; + private ClientResourceDetails clientResourceDetails; + + public PasswordAccessTokenService(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + @Override + public void setClientResourceDetails(ClientResourceDetails clientResourceDetails) { + this.clientResourceDetails = clientResourceDetails; + } + + @Override + public OAuth2AccessToken getAccessToken(HttpServletRequest request) throws IOException { + LoginInformation loginInformation = objectMapper.readValue(request.getReader(), LoginInformation.class); + OAuth2Client client = new OAuth2Client(clientResourceDetails.getClient().getAccessTokenUri(), + clientResourceDetails.getClient().getClientId(), + clientResourceDetails.getClient().getClientSecret()); + return client.getAccessToken(loginInformation.getEmail(), + loginInformation.getPassword(), Collections.singletonList("default")); + } + + private static class LoginInformation { + private String email; + private String password; + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + } +} diff --git a/server/src/main/resources/application.yml b/server/src/main/resources/application.yml index 1df4362..bf4aed0 100644 --- a/server/src/main/resources/application.yml +++ b/server/src/main/resources/application.yml @@ -65,8 +65,15 @@ google: userInfoUri: https://www.googleapis.com/oauth2/v2/userinfo preferTokenInfo: false - - + +helfenkannjeder: + client: + clientId: come2help-web + clientSecret: secret + accessTokenUri: http://localhost:8081/oauth/token + resource: + userInfoUri: http://localhost:8081/user/information + api: jwt: diff --git a/web-frontend/index.html b/web-frontend/index.html index 587b865..e719a01 100644 --- a/web-frontend/index.html +++ b/web-frontend/index.html @@ -49,9 +49,24 @@ - + diff --git a/web-frontend/js/authProvider.js b/web-frontend/js/authProvider.js index 3957731..e9e70db 100644 --- a/web-frontend/js/authProvider.js +++ b/web-frontend/js/authProvider.js @@ -1,7 +1,7 @@ angular.module('Come2HelpApp').config(['$authProvider', function ($authProvider) { $authProvider.signupUrl = '/api/volunteers'; - $authProvider.loginUrl = '/api/login/volunteer'; + $authProvider.loginUrl = '/api/login/helfenkannjeder'; $authProvider.facebook({ clientId: '417693555105063', diff --git a/web-frontend/js/controllers/NavigationController.js b/web-frontend/js/controllers/NavigationController.js index b046e78..8f451c9 100644 --- a/web-frontend/js/controllers/NavigationController.js +++ b/web-frontend/js/controllers/NavigationController.js @@ -1,17 +1,37 @@ angular.module('Come2HelpApp') -.controller('NavigationController', ['jwtService', '$location', function(jwtService, $location) { - var vm = this; + .controller('NavigationController', ['jwtService', '$location', '$auth', function (jwtService, $location, $auth) { + var vm = this; - vm.authenticated = jwtService.isAuthenticated.bind(jwtService); - vm.isGuest = jwtService.isGuest.bind(jwtService); - vm.isOrganisation = jwtService.isOrganisation.bind(jwtService); - vm.logout = jwtService.logout; + vm.user = {}; + vm.authenticated = jwtService.isAuthenticated.bind(jwtService); + vm.isGuest = jwtService.isGuest.bind(jwtService); + vm.isOrganisation = jwtService.isOrganisation.bind(jwtService); + vm.logout = jwtService.logout; - vm.getClass = function (path) { - if ($location.path().substr(0, path.length) === path) { - return 'active'; - } else { - return ''; - } - }; -}]); \ No newline at end of file + vm.credentials = {}; + + vm.login = function() { + // user transport object + var transUser = { + email: vm.user.email || '', + password: vm.user.password || '' + }; + console.log(transUser); + + $auth.login(transUser) + .then(function () { + // Do nothing, global handling do everything + }) + .catch(function () { + alert('login error'); + }); + }; + + vm.getClass = function (path) { + if ($location.path().substr(0, path.length) === path) { + return 'active'; + } else { + return ''; + } + }; + }]); \ No newline at end of file diff --git a/web-frontend/test/behaviour/register.js b/web-frontend/test/behaviour/register.js index 4f2b96c..1567860 100644 --- a/web-frontend/test/behaviour/register.js +++ b/web-frontend/test/behaviour/register.js @@ -45,7 +45,7 @@ function signupWith(data) { } // Submit - $('button[type="submit"]').click(); + $('.panel-body button[type="submit"]').click(); } /**