General purpose :
Provides a spring security configuration for any Oauth2 providers in our projects. For that I found many good starting points on the web and I merged them into a simple GWTP project forked from an existing repository (https://github.com/imrabti/gwtp-spring-security) which helped me to achieve this goal with minor changes.
This a solution for GWT developers who want to provide a session management using usual (Google, Facebook, …) or custom Oauth2 provider in their applications.
Dependency management :
<dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> <version>${oauth.version}</version> </dependency> ... <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>${spring-security.version}</version> </dependency> <!-- Spring Social --> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-security</artifactId> <version>${spring-social.version}</version> </dependency> <!-- Persistance dependencies --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-dbcp2</artifactId> <version>2.1</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.186</version> </dependency>
2 – Custom provider definition :
I added some classes here as other embedded Social provider to define my own provider
– Provider definition
public interface Corporate { CorporatePofile getUserProfile(); void updateStatus(String message); }
– Oauth2 provider service
public class CorporateServiceProvider extends AbstractOAuth2ServiceProvider<Corporate> { public CorporateServiceProvider(OAuth2Operations oauth2Operations) { super(oauth2Operations); } @Override public Corporate getApi(String s) { return new CorporateServiceTemplate(s); } }
– Provider service template
public class CorporateServiceTemplate extends AbstractOAuth2ApiBinding implements Corporate { private final static String corporateProfileURL = "http://localhost:8080/oauthprotoserver/profile/"; private String accessToken; CorporateServiceTemplate(String accessToken) { super(accessToken); this.accessToken = accessToken; } @Override public CorporatePofile getUserProfile() { try { ResponseEntity<String> content = getRestTemplate().exchange(URI.create(corporateProfileURL + accessToken), HttpMethod.GET, null, String.class); // TODO Retrieve a Corporate JSON user build a serialized object return new CorporatePofile(content.getBody(),"test@test.com","client1","employee","foo","bar"); } catch (HttpClientErrorException e2) { throw new OAuth2Exception(e2.getMessage()); } } @Override public void updateStatus(String message) { return; } }
And to perform the implicit SignUp and build a valid session with SocialUserDetails
– A SimpleConnectionSignUp which requires a JDBC user connection repository
public final class SimpleConnectionSignUp implements ConnectionSignUp { private final AtomicLong userIdSequence = new AtomicLong(); public String execute(Connection<?> connection) { return Long.toString(userIdSequence.incrementAndGet()); } }
– A SimpleSignInAdapter which will help to store UserDetails into Security Context
public class SimpleSignInAdapter implements SignInAdapter { private static final Logger log = LoggerFactory.getLogger(SimpleSignInAdapter.class); private final RequestCache requestCache; private final NuvolaCasDetailsService userService; @Autowired public SimpleSignInAdapter(RequestCache requestCache, NuvolaCasDetailsService userService) { this.requestCache = requestCache; this.userService = userService; } @Override public String signIn(String localUserId, Connection<?> connection, NativeWebRequest request) { SocialUserDetails user = userService.loadUserByUserId(connection.getDisplayName()); SignInUtils.signin(user); // Return Application home page return "/"; }
This will help us into the final step add a simple login page to post a SignIn request catched by the ProviderSignInController (Framework controller)
... <div align="center"> <form id="casForm" name="casForm" action="/api/signin/corporate" method="POST" > <p><input name="login" value="Connexion via Portail" type="submit"></p> </form> </div>
The “corporate” at the end of the uri (/api/signin/corporate) is our providerId which is a Path parameter helping the controller to build a valid OAuth2ConnectionFactory.
3 – Spring security configuration :
... @Bean @Override public UserDetailsService userDetailsServiceBean() throws Exception { return super.userDetailsServiceBean(); } @Bean OAuth2Template restTemplate() { return new OAuth2Template(corporateProvider().getClientId(), corporateProvider().getClientSecret(), corporateProvider().getUserAuthorizationUri(), corporateProvider().getAccessTokenUri()); } @Bean @Scope(value="singleton", proxyMode= ScopedProxyMode.INTERFACES) public SocialAuthenticationServiceLocator socialAuthenticationServiceLocator() { SocialAuthenticationServiceRegistry registry = new SocialAuthenticationServiceRegistry(); registry.addConnectionFactory(new CorporateConnectionFactory(restTemplate())); return registry; } @Bean @Scope(value="singleton", proxyMode=ScopedProxyMode.INTERFACES) public UsersConnectionRepository usersConnectionRepository() { JdbcUsersConnectionRepository connectionRepository = new JdbcUsersConnectionRepository(dataSource, socialAuthenticationServiceLocator(), Encryptors.noOpText()); connectionRepository.setConnectionSignUp(new SimpleConnectionSignUp()); return connectionRepository; } @Bean public ProviderSignInController providerSignInController() { ProviderSignInController controller = new ProviderSignInController(socialAuthenticationServiceLocator(), usersConnectionRepository(), new SimpleSignInAdapter(new HttpSessionRequestCache(), userDetailsService)); return controller; } @Bean AuthorizationCodeResourceDetails corporateProvider() { AuthorizationCodeResourceDetails codeResourceDetails = new AuthorizationCodeResourceDetails(); codeResourceDetails.setClientId(appClientId); codeResourceDetails.setClientSecret("appClientSecret"); codeResourceDetails.setGrantType("authorization_code"); codeResourceDetails.setTokenName("bearer"); codeResourceDetails.setAuthenticationScheme(AuthenticationScheme.header); codeResourceDetails.setClientAuthenticationScheme(AuthenticationScheme.header); codeResourceDetails.setAccessTokenUri("accessTokenUri"); codeResourceDetails.setUserAuthorizationUri("userAuthorizationUri"); codeResourceDetails.setUseCurrentUri(true); codeResourceDetails.setPreEstablishedRedirectUri("http://localhost:8090/"); return codeResourceDetails; } @Bean AuthorizationCodeResourceDetails googleProvider() { return null; } @Bean AuthorizationCodeResourceDetails facebookProvider() { return null; } @Override protected AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .formLogin().loginPage("/access/login") .permitAll() .successHandler(authSuccessHandler) .failureHandler(authFailureHandler) .and() .logout().logoutUrl("/access/logout") .invalidateHttpSession(true) .logoutSuccessHandler(logoutSuccessHandler) .and() .apply(new SpringSocialConfigurer() .postLoginUrl("/home") .alwaysUsePostLoginUrl(true)); http.authorizeRequests() .antMatchers(LOGIN_PATH).permitAll() .anyRequest().authenticated(); } ...
The complete source code is available here : https://github.com/gael-jurin/gwtp-spring-security I hope this will help many developers to customize Oauth2 authorization and authentication process via providers. This implementation supports the Oauth2 authorization code flow. Thanks to Spring community for this huge work