├── .gitignore ├── src ├── main │ ├── resources │ │ ├── application.properties │ │ └── logback.xml │ ├── webapp │ │ └── index.html │ └── java │ │ └── com │ │ └── github │ │ └── fromi │ │ └── openidconnect │ │ ├── SampleSecuredController.java │ │ ├── Application.java │ │ └── security │ │ ├── UserInfo.java │ │ ├── OpenIDConnectAuthenticationFilter.java │ │ ├── SecurityConfiguration.java │ │ └── OAuth2Client.java └── test │ └── java │ └── com │ └── github │ └── fromi │ └── openidconnect │ └── SecurityIT.java ├── README.md ├── LICENSE └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .gitignore support plugin (hsz.mobi) 2 | .idea 3 | *.iml -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | google.oauth2.clientId= 2 | google.oauth2.clientSecret= -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a sample application of how to connect Spring Security with Google OpenIdConnect. 2 | Application is build using Spring Boot and Java 8. 3 | 4 | To test, please configure file application.properties with your client id and secret obtained with [Google Developers Console](https://console.developers.google.com/). -------------------------------------------------------------------------------- /src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | OpenIdConnect with Spring 6 | 7 | 8 |

This is a sample application of how to connect Spring Security with Google OpenIdConnect.

9 | Go to some secured resource 10 | 11 | -------------------------------------------------------------------------------- /src/main/java/com/github/fromi/openidconnect/SampleSecuredController.java: -------------------------------------------------------------------------------- 1 | package com.github.fromi.openidconnect; 2 | 3 | import org.springframework.security.core.context.SecurityContextHolder; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | import com.github.fromi.openidconnect.security.UserInfo; 8 | 9 | @RestController 10 | public class SampleSecuredController { 11 | @RequestMapping("/test") 12 | public String test() { 13 | UserInfo userInfo = (UserInfo) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 14 | return "Welcome, " + userInfo.getName(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/github/fromi/openidconnect/Application.java: -------------------------------------------------------------------------------- 1 | package com.github.fromi.openidconnect; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 5 | import org.springframework.boot.builder.SpringApplicationBuilder; 6 | import org.springframework.boot.context.web.SpringBootServletInitializer; 7 | import org.springframework.context.annotation.ComponentScan; 8 | import org.springframework.context.annotation.ScopedProxyMode; 9 | 10 | @ComponentScan(scopedProxy = ScopedProxyMode.INTERFACES) 11 | @EnableAutoConfiguration 12 | public class Application extends SpringBootServletInitializer { 13 | 14 | public static void main(String[] args) { 15 | SpringApplication.run(Application.class); 16 | } 17 | 18 | @Override 19 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 20 | return application.sources(Application.class); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Romain Fromi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/main/java/com/github/fromi/openidconnect/security/UserInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.fromi.openidconnect.security; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | public class UserInfo { 7 | private final String id; 8 | private final String name; 9 | private final String givenName; 10 | private final String familyName; 11 | private final String gender; 12 | private final String picture; 13 | private final String link; 14 | 15 | @JsonCreator 16 | public UserInfo(@JsonProperty("id") String id, 17 | @JsonProperty("name") String name, 18 | @JsonProperty("given_name") String givenName, 19 | @JsonProperty("family_name") String familyName, 20 | @JsonProperty("gender") String gender, 21 | @JsonProperty("picture") String picture, 22 | @JsonProperty("link") String link) { 23 | this.id = id; 24 | this.name = name; 25 | this.givenName = givenName; 26 | this.familyName = familyName; 27 | this.gender = gender; 28 | this.picture = picture; 29 | this.link = link; 30 | } 31 | 32 | public String getId() { 33 | return id; 34 | } 35 | 36 | public String getName() { 37 | return name; 38 | } 39 | 40 | public String getGivenName() { 41 | return givenName; 42 | } 43 | 44 | public String getFamilyName() { 45 | return familyName; 46 | } 47 | 48 | public String getGender() { 49 | return gender; 50 | } 51 | 52 | public String getPicture() { 53 | return picture; 54 | } 55 | 56 | public String getLink() { 57 | return link; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/github/fromi/openidconnect/security/OpenIDConnectAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package com.github.fromi.openidconnect.security; 2 | 3 | import static java.util.Optional.empty; 4 | import static org.springframework.security.core.authority.AuthorityUtils.NO_AUTHORITIES; 5 | 6 | import java.io.IOException; 7 | 8 | import javax.annotation.Resource; 9 | import javax.servlet.ServletException; 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | 13 | import org.springframework.http.ResponseEntity; 14 | import org.springframework.security.core.Authentication; 15 | import org.springframework.security.core.AuthenticationException; 16 | import org.springframework.security.oauth2.client.OAuth2RestOperations; 17 | import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; 18 | import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; 19 | 20 | public class OpenIDConnectAuthenticationFilter extends AbstractAuthenticationProcessingFilter { 21 | 22 | @Resource 23 | private OAuth2RestOperations restTemplate; 24 | 25 | protected OpenIDConnectAuthenticationFilter(String defaultFilterProcessesUrl) { 26 | super(defaultFilterProcessesUrl); 27 | setAuthenticationManager(authentication -> authentication); // AbstractAuthenticationProcessingFilter requires an authentication manager. 28 | } 29 | 30 | @Override 31 | public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) 32 | throws AuthenticationException, IOException, ServletException { 33 | final ResponseEntity userInfoResponseEntity = restTemplate.getForEntity("https://www.googleapis.com/oauth2/v2/userinfo", UserInfo.class); 34 | return new PreAuthenticatedAuthenticationToken(userInfoResponseEntity.getBody(), empty(), NO_AUTHORITIES); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/com/github/fromi/openidconnect/SecurityIT.java: -------------------------------------------------------------------------------- 1 | package com.github.fromi.openidconnect; 2 | 3 | import static com.jayway.restassured.RestAssured.given; 4 | import static org.hamcrest.CoreMatchers.endsWith; 5 | import static org.hamcrest.CoreMatchers.startsWith; 6 | 7 | import org.apache.http.HttpStatus; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.boot.test.IntegrationTest; 13 | import org.springframework.boot.test.SpringApplicationConfiguration; 14 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 15 | import org.springframework.test.context.web.WebAppConfiguration; 16 | 17 | import com.jayway.restassured.RestAssured; 18 | 19 | @RunWith(SpringJUnit4ClassRunner.class) 20 | @SpringApplicationConfiguration(classes = Application.class) 21 | @WebAppConfiguration 22 | @IntegrationTest("server.port:0") 23 | public class SecurityIT { 24 | 25 | @Value("${local.server.port}") 26 | int port; 27 | 28 | @Before 29 | public void setUp() { 30 | RestAssured.port = port; 31 | } 32 | 33 | @Test 34 | public void welcomePageNotRedirected() { 35 | given().redirects().follow(false).when().get("/").then().statusCode(HttpStatus.SC_OK); 36 | } 37 | 38 | @Test 39 | public void securedPageRedirectsToLoginPage() { 40 | given().redirects().follow(false).when().get("/test").then() 41 | .statusCode(HttpStatus.SC_MOVED_TEMPORARILY) 42 | .header("Location", endsWith("/login")); 43 | } 44 | 45 | @Test 46 | public void loginPageRedirectsToGoogle() { 47 | given().redirects().follow(false).when().get("/login").then() 48 | .statusCode(HttpStatus.SC_MOVED_TEMPORARILY) 49 | .header("Location", startsWith("https://accounts.google.com/o/oauth2/auth")); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/github/fromi/openidconnect/security/SecurityConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.fromi.openidconnect.security; 2 | 3 | import static org.springframework.http.HttpMethod.GET; 4 | 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 8 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 9 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 10 | import org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter; 11 | import org.springframework.security.web.AuthenticationEntryPoint; 12 | import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; 13 | import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; 14 | 15 | @Configuration 16 | @EnableWebSecurity 17 | public class SecurityConfiguration extends WebSecurityConfigurerAdapter { 18 | 19 | private final String LOGIN_URL = "/login"; 20 | 21 | @Bean 22 | public AuthenticationEntryPoint authenticationEntryPoint() { 23 | return new LoginUrlAuthenticationEntryPoint(LOGIN_URL); 24 | } 25 | 26 | @Bean 27 | public OpenIDConnectAuthenticationFilter openIdConnectAuthenticationFilter() { 28 | return new OpenIDConnectAuthenticationFilter(LOGIN_URL); 29 | } 30 | 31 | @Bean 32 | public OAuth2ClientContextFilter oAuth2ClientContextFilter() { 33 | return new OAuth2ClientContextFilter(); 34 | } 35 | @Override 36 | protected void configure(HttpSecurity http) throws Exception { 37 | http.addFilterAfter(oAuth2ClientContextFilter(), AbstractPreAuthenticatedProcessingFilter.class) 38 | .addFilterAfter(openIdConnectAuthenticationFilter(), OAuth2ClientContextFilter.class) 39 | .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint()) 40 | .and().authorizeRequests() 41 | .antMatchers(GET, "/").permitAll() 42 | .antMatchers(GET, "/test").authenticated(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/github/fromi/openidconnect/security/OAuth2Client.java: -------------------------------------------------------------------------------- 1 | package com.github.fromi.openidconnect.security; 2 | 3 | import static java.util.Arrays.asList; 4 | import static org.springframework.security.oauth2.common.AuthenticationScheme.form; 5 | 6 | import javax.annotation.Resource; 7 | 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.context.annotation.Scope; 12 | import org.springframework.context.annotation.ScopedProxyMode; 13 | import org.springframework.security.oauth2.client.OAuth2ClientContext; 14 | import org.springframework.security.oauth2.client.OAuth2RestOperations; 15 | import org.springframework.security.oauth2.client.OAuth2RestTemplate; 16 | import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; 17 | import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; 18 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client; 19 | 20 | @Configuration 21 | @EnableOAuth2Client 22 | public class OAuth2Client { 23 | @Value("${google.oauth2.clientId}") 24 | private String clientId; 25 | 26 | @Value("${google.oauth2.clientSecret}") 27 | private String clientSecret; 28 | 29 | @Bean 30 | // TODO retrieve from https://accounts.google.com/.well-known/openid-configuration ? 31 | public OAuth2ProtectedResourceDetails googleOAuth2Details() { 32 | AuthorizationCodeResourceDetails googleOAuth2Details = new AuthorizationCodeResourceDetails(); 33 | googleOAuth2Details.setAuthenticationScheme(form); 34 | googleOAuth2Details.setClientAuthenticationScheme(form); 35 | googleOAuth2Details.setClientId(clientId); 36 | googleOAuth2Details.setClientSecret(clientSecret); 37 | googleOAuth2Details.setUserAuthorizationUri("https://accounts.google.com/o/oauth2/auth"); 38 | googleOAuth2Details.setAccessTokenUri("https://www.googleapis.com/oauth2/v3/token"); 39 | googleOAuth2Details.setScope(asList("openid")); 40 | return googleOAuth2Details; 41 | } 42 | 43 | @SuppressWarnings("SpringJavaAutowiringInspection") // Provided by Spring Boot 44 | @Resource 45 | private OAuth2ClientContext oAuth2ClientContext; 46 | 47 | @Bean 48 | @Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES) 49 | public OAuth2RestOperations googleOAuth2RestTemplate() { 50 | return new OAuth2RestTemplate(googleOAuth2Details(), oAuth2ClientContext); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.github.fromi 8 | spring-google-openidconnect 9 | 1.0-SNAPSHOT 10 | war 11 | 12 | 13 | org.springframework.boot 14 | spring-boot-starter-parent 15 | 1.1.9.RELEASE 16 | 17 | 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-starter-web 22 | 23 | 24 | org.springframework.security.oauth 25 | spring-security-oauth2 26 | 2.0.4.RELEASE 27 | 28 | 29 | com.google.guava 30 | guava 31 | 18.0 32 | 33 | 34 | 35 | junit 36 | junit 37 | test 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-test 42 | test 43 | 44 | 45 | com.jayway.restassured 46 | rest-assured 47 | 2.4.0 48 | test 49 | 50 | 51 | 52 | 53 | 54 | 55 | org.apache.maven.plugins 56 | maven-compiler-plugin 57 | 3.1 58 | 59 | 1.8 60 | 1.8 61 | 62 | 63 | 64 | org.springframework.boot 65 | spring-boot-maven-plugin 66 | 67 | -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -Dhttp.proxyHost=localhost -Dhttp.proxyPort=3128 -Dhttps.proxyHost=localhost -Dhttps.proxyPort=3128 68 | 69 | 70 | 71 | 72 | 73 | --------------------------------------------------------------------------------