├── .gitignore ├── README.md ├── pom.xml └── src ├── main ├── java │ ├── oauth2 │ │ └── google │ │ │ ├── GoogleUser.java │ │ │ └── GoogleUserInfoTokenServices.java │ └── web │ │ └── sample │ │ ├── OAuth2ClientSecurityConfig.java │ │ ├── SecurityWebAppInitializer.java │ │ ├── WebAppInitializer.java │ │ └── WebMvcConfig.java ├── resources │ ├── google-oauth2.properties │ └── logback.xml └── webapp │ └── WEB-INF │ └── views │ ├── secured.jsp │ └── welcome.jsp └── test └── resources └── logback-test.xml /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .project 3 | .classpath 4 | .settings 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring Security OAuth2 with Google 2 | 3 | This is a sample Spring MVC web application which secured by Spring Security. Instead of using form-based security, it is secured by Spring Security OAuth2 and the OAuth2 provider is Google. 4 | 5 | The [google-oauth2.properties](src/main/resources/google-oauth2.properties) (in `src/main/resources`) contains the details of the Google application which it uses to authenticate details. Change the values of the following attributes to the values for your application: 6 | 7 | - `oauth2.clientId` 8 | - `oauth2.clientSecret` 9 | 10 | While the `pom.xml` file includes Sprint Boot dependencies, they're only used for dependency management (i.e. using the versions indicated in other POM files). But the web application itself does not use any of the Spring Boot auto-configuration features. 11 | 12 | ## To register a Google App perform the following steps 13 | 14 | * Go to https://console.developers.google.com and login with your Google account (this will be the developer account and the email of this account will be published when someone tries to authenticate with the Google application) 15 | * If you don't have a project create a new one and then click into the project. 16 | * In the menu on the left side select "APIs & auth" --> "Credentials" --> "Create a new client ID" 17 | * In the popup select the following 18 | - Application Type = Web Application 19 | - Authorized Javascript Origins = , 20 | - Authorized Redirect URI = , the URI for our application is `/oauth2/callback` so for local testing you should enter `http://localhost:8080/`*context-path*`/oauth2/callback` 21 | - Copy the client ID and client Secret and update the properties file 22 | - Make sure you update the mandatory values on the "APIs & auth" --> "Consent screen" page as the application will not work without it. 23 | 24 | When you have a the Google App configured, the Spring application is running, and you navigate to `http://localhost:8080/`*context-path*. It will show a welcome page with a link to a secured page. When you click on this link, you'll be asked to authenticate (if not yet authenticated). This will redirect you to a Google login page. Upon login it will ask you to authorize your application for access to your account to get email and profile data. On successful login it will render the secured HTML page which means the authentication was successful. It will also show some user information (e.g. ID, user name, and email). 25 | 26 | ## Technical Details 27 | 28 | Please refer to [my blog post](http://lorenzo-dee.blogspot.com/2016/08/spring-security-oauth2-with-google.html). [OAuth2ClientSecurityConfig.java](src/main/java/web/sample/OAuth2ClientSecurityConfig.java) is the configuration class that contains the OAuth2 stuff. 29 | 30 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.orangeandbronze.training.springframework 6 | spring-security-oauth2-google 7 | 1.0-SNAPSHOT 8 | war 9 | 10 | oauth2-google 11 | 12 | 13 | 1.3.5.RELEASE 14 | 15 | 1.8 16 | 17 | false 18 | ${java.version} 19 | ${java.version} 20 | 21 | 22 | 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-dependencies 28 | ${spring.boot.version} 29 | pom 30 | import 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter 40 | 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-web 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-starter-tomcat 49 | 50 | 51 | 52 | 53 | 54 | org.springframework.boot 55 | spring-boot-starter-security 56 | 57 | 58 | org.springframework.security 59 | spring-security-taglibs 60 | 61 | 62 | org.springframework.security.oauth 63 | spring-security-oauth2 64 | 65 | 66 | 67 | javax.servlet 68 | javax.servlet-api 69 | provided 70 | 71 | 72 | javax.servlet 73 | jstl 74 | 75 | 76 | 77 | org.springframework.boot 78 | spring-boot-starter-test 79 | test 80 | 81 | 82 | 83 | 84 | 85 | 86 | org.eclipse.jetty 87 | jetty-maven-plugin 88 | 9.3.9.v20160517 89 | 90 | 91 | /${project.artifactId} 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /src/main/java/oauth2/google/GoogleUser.java: -------------------------------------------------------------------------------- 1 | package oauth2.google; 2 | 3 | import java.net.URI; 4 | import java.net.URISyntaxException; 5 | import java.util.Collection; 6 | import java.util.Collections; 7 | import java.util.Locale; 8 | import java.util.Map; 9 | 10 | import org.springframework.security.core.GrantedAuthority; 11 | import org.springframework.security.core.userdetails.UserDetails; 12 | 13 | @SuppressWarnings("serial") 14 | public class GoogleUser implements UserDetails { 15 | 16 | private final String id; 17 | private final String email; 18 | // private final boolean emailVerified; 19 | private final String userName; 20 | private final String givenName; 21 | private final String familyName; 22 | private final URI link; 23 | private final URI picture; 24 | private final String gender; 25 | private final Locale locale; 26 | private final String hostDomain; 27 | 28 | public GoogleUser( 29 | String id, String email, String userName, String givenName, String familyName, 30 | URI link, URI picture, String gender, Locale locale, String hostDomain) { 31 | super(); 32 | this.id = id; 33 | this.email = email; 34 | this.userName = userName; 35 | this.givenName = givenName; 36 | this.familyName = familyName; 37 | this.link = link; 38 | this.picture = picture; 39 | this.gender = gender; 40 | this.locale = locale; 41 | this.hostDomain = hostDomain; 42 | } 43 | 44 | @Override 45 | public Collection getAuthorities() { 46 | return Collections.emptyList(); 47 | } 48 | 49 | @Override 50 | public String getPassword() { 51 | return null; 52 | } 53 | 54 | @Override 55 | public String getUsername() { 56 | return userName; 57 | } 58 | 59 | @Override 60 | public boolean isAccountNonExpired() { 61 | return true; 62 | } 63 | 64 | @Override 65 | public boolean isAccountNonLocked() { 66 | return true; 67 | } 68 | 69 | @Override 70 | public boolean isCredentialsNonExpired() { 71 | return true; 72 | } 73 | 74 | @Override 75 | public boolean isEnabled() { 76 | return true; 77 | } 78 | 79 | public String getId() { 80 | return id; 81 | } 82 | 83 | public String getEmail() { 84 | return email; 85 | } 86 | 87 | public String getGivenName() { 88 | return givenName; 89 | } 90 | 91 | public String getFamilyName() { 92 | return familyName; 93 | } 94 | 95 | public URI getLink() { 96 | return link; 97 | } 98 | 99 | public URI getPicture() { 100 | return picture; 101 | } 102 | 103 | public String getGender() { 104 | return gender; 105 | } 106 | 107 | public Locale getLocale() { 108 | return locale; 109 | } 110 | 111 | public String getHostDomain() { 112 | return hostDomain; 113 | } 114 | 115 | @Override 116 | public String toString() { 117 | StringBuilder builder = new StringBuilder(); 118 | builder.append("GoogleUser [id="); 119 | builder.append(id); 120 | builder.append(", email="); 121 | builder.append(email); 122 | builder.append(", userName="); 123 | builder.append(userName); 124 | builder.append("]"); 125 | return builder.toString(); 126 | } 127 | 128 | public static GoogleUser fromUserInfoMap(Map map) { 129 | URI link; 130 | if (map.containsKey("link")) { 131 | link = getUri(map, "link"); 132 | } else { 133 | link = getUri(map, "profile"); 134 | } 135 | URI picture = getUri(map, "picture"); 136 | return new GoogleUser( 137 | (String) (map.containsKey("id") ? map.get("id") : map.get("sub")), 138 | (String) map.get("email"), 139 | (String) map.get("name"), 140 | (String) map.get("given_name"), 141 | (String) map.get("family_name"), 142 | link, 143 | picture, 144 | (String) map.get("gender"), 145 | new Locale((String) map.get("locale")), 146 | (String) map.get("hd")); 147 | } 148 | 149 | private static URI getUri(Map map, String key) { 150 | URI uri = null; 151 | try { 152 | if (map.containsKey(key)) { 153 | uri = new URI((String) map.get(key)); 154 | } 155 | } catch (URISyntaxException e) {} 156 | return uri; 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /src/main/java/oauth2/google/GoogleUserInfoTokenServices.java: -------------------------------------------------------------------------------- 1 | package oauth2.google; 2 | 3 | import java.util.Collections; 4 | import java.util.LinkedList; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 11 | import org.springframework.security.core.AuthenticationException; 12 | import org.springframework.security.core.GrantedAuthority; 13 | import org.springframework.security.oauth2.client.OAuth2RestOperations; 14 | import org.springframework.security.oauth2.client.OAuth2RestTemplate; 15 | import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails; 16 | import org.springframework.security.oauth2.common.AuthenticationScheme; 17 | import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; 18 | import org.springframework.security.oauth2.common.OAuth2AccessToken; 19 | import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; 20 | import org.springframework.security.oauth2.provider.OAuth2Authentication; 21 | import org.springframework.security.oauth2.provider.OAuth2Request; 22 | import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; 23 | 24 | /** 25 | *

26 | * Based on {@link org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices}. 27 | *

28 | *

29 | * Intended to use Google API UserInfo endpoint. 30 | *

31 | */ 32 | public class GoogleUserInfoTokenServices implements ResourceServerTokenServices { 33 | 34 | protected final Logger logger = LoggerFactory.getLogger(this.getClass()); 35 | 36 | private final String userInfoEndpointUrl; 37 | 38 | private final String clientId; 39 | 40 | private OAuth2RestOperations restTemplate; 41 | 42 | private String tokenType = DefaultOAuth2AccessToken.BEARER_TYPE; 43 | 44 | // private AuthoritiesExtractor authoritiesExtractor = new FixedAuthoritiesExtractor(); 45 | 46 | public GoogleUserInfoTokenServices(String userInfoEndpointUrl, String clientId) { 47 | this.userInfoEndpointUrl = userInfoEndpointUrl; 48 | this.clientId = clientId; 49 | } 50 | 51 | public void setTokenType(String tokenType) { 52 | this.tokenType = tokenType; 53 | } 54 | 55 | public void setRestTemplate(OAuth2RestOperations restTemplate) { 56 | this.restTemplate = restTemplate; 57 | } 58 | 59 | /* 60 | public void setAuthoritiesExtractor(AuthoritiesExtractor authoritiesExtractor) { 61 | this.authoritiesExtractor = authoritiesExtractor; 62 | } 63 | */ 64 | 65 | @Override 66 | public OAuth2Authentication loadAuthentication(String accessToken) 67 | throws AuthenticationException, InvalidTokenException { 68 | Map map = getMap(this.userInfoEndpointUrl, accessToken); 69 | if (map.containsKey("error")) { 70 | this.logger.debug("userinfo returned error: {}", map.get("error")); 71 | throw new InvalidTokenException(accessToken); 72 | } 73 | this.logger.debug("userinfo returned: {}", map); 74 | return extractAuthentication(map); 75 | } 76 | 77 | private OAuth2Authentication extractAuthentication(Map map) { 78 | Object principal = getPrincipal(map); 79 | List authorities = new LinkedList<>(); 80 | // this.authoritiesExtractor.extractAuthorities(map); 81 | OAuth2Request request = new OAuth2Request(null, this.clientId, null, true, null, 82 | null, null, null, null); 83 | UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken( 84 | principal, "N/A", authorities); 85 | token.setDetails(map); 86 | return new OAuth2Authentication(request, token); 87 | } 88 | 89 | protected Object getPrincipal(Map map) { 90 | return GoogleUser.fromUserInfoMap(map); 91 | } 92 | 93 | @SuppressWarnings({ "unchecked" }) 94 | private Map getMap(String path, String accessToken) { 95 | this.logger.info("Getting user info from: {}", path); 96 | try { 97 | OAuth2RestOperations restTemplate = this.restTemplate; 98 | if (restTemplate == null) { 99 | BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails(); 100 | // resource.setTokenName("access_token"); 101 | resource.setAuthenticationScheme(AuthenticationScheme.query); 102 | restTemplate = new OAuth2RestTemplate(resource); 103 | } 104 | OAuth2AccessToken existingToken = restTemplate.getOAuth2ClientContext() 105 | .getAccessToken(); 106 | if (existingToken == null || !accessToken.equals(existingToken.getValue())) { 107 | DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken( 108 | accessToken); 109 | token.setTokenType(this.tokenType); 110 | restTemplate.getOAuth2ClientContext().setAccessToken(token); 111 | } 112 | return restTemplate.getForEntity(path, Map.class).getBody(); 113 | } 114 | catch (Exception ex) { 115 | this.logger.info("Could not fetch user details: " + ex.getClass() + ", " 116 | + ex.getMessage()); 117 | return Collections.singletonMap("error", 118 | "Could not fetch user details"); 119 | } 120 | } 121 | 122 | @Override 123 | public OAuth2AccessToken readAccessToken(String accessToken) { 124 | throw new UnsupportedOperationException("Not supported: read access token"); 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/web/sample/OAuth2ClientSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package web.sample; 2 | 3 | import java.util.Collections; 4 | import java.util.LinkedList; 5 | import java.util.List; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 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.Description; 12 | import org.springframework.context.annotation.PropertySource; 13 | import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; 14 | import org.springframework.security.authentication.AuthenticationManager; 15 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 16 | import org.springframework.security.config.annotation.web.builders.WebSecurity; 17 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 18 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 19 | import org.springframework.security.core.Authentication; 20 | import org.springframework.security.core.AuthenticationException; 21 | import org.springframework.security.oauth2.client.OAuth2ClientContext; 22 | import org.springframework.security.oauth2.client.OAuth2RestOperations; 23 | import org.springframework.security.oauth2.client.OAuth2RestTemplate; 24 | import org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter; 25 | import org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter; 26 | import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; 27 | import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException; 28 | import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; 29 | import org.springframework.security.oauth2.common.AuthenticationScheme; 30 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client; 31 | import org.springframework.security.oauth2.config.annotation.web.configuration.OAuth2ClientConfiguration; 32 | import org.springframework.security.web.AuthenticationEntryPoint; 33 | import org.springframework.security.web.access.ExceptionTranslationFilter; 34 | import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; 35 | import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; 36 | 37 | import oauth2.google.GoogleUserInfoTokenServices; 38 | 39 | /** 40 | *

41 | * Configures this web app as an OAuth2 client, meaning this web app will be a 42 | * calling some API services secured via OAuth2. In order to call these secured 43 | * API services, the user needs to authenticate with the authorization server, 44 | * and authorize the web app to access the API services in behalf of the user. 45 | *

46 | *

47 | * With the {@link EnableOAuth2Client} annotation, this configuration uses 48 | * {@link OAuth2ClientConfiguration} that is provided by Spring Security OAuth2. 49 | *

50 | */ 51 | @Configuration 52 | @EnableWebSecurity 53 | @EnableOAuth2Client 54 | @PropertySource("classpath:google-oauth2.properties") 55 | public class OAuth2ClientSecurityConfig extends WebSecurityConfigurerAdapter { 56 | 57 | // This is made possible because of @EnableOAuth2Client 58 | // and RequestContextListener. 59 | @Autowired 60 | private OAuth2ClientContext oauth2ClientContext; 61 | 62 | /** 63 | *

64 | * Handles a {@link UserRedirectRequiredException} that is thrown from 65 | * {@link OAuth2ClientAuthenticationProcessingFilter}. 66 | *

67 | *

68 | * This bean is configured because of @EnableOAuth2Client. 69 | *

70 | */ 71 | @Autowired 72 | private OAuth2ClientContextFilter oauth2ClientContextFilter; 73 | 74 | @Value("${oauth2.clientId}") 75 | private String clientId; 76 | 77 | @Value("${oauth2.clientSecret}") 78 | private String clientSecret; 79 | 80 | @Value("${oauth2.userAuthorizationUri}") 81 | private String userAuthorizationUri; 82 | 83 | @Value("${oauth2.accessTokenUri}") 84 | private String accessTokenUri; 85 | 86 | @Value("${oauth2.tokenName:authorization_code}") 87 | private String tokenName; 88 | 89 | @Value("${oauth2.scope}") 90 | private String scope; 91 | 92 | @Value("${oauth2.userInfoUri}") 93 | private String userInfoUri; 94 | 95 | @Value("${oauth2.filterCallbackPath}") 96 | private String oauth2FilterCallbackPath; 97 | 98 | private OAuth2ProtectedResourceDetails authorizationCodeResource() { 99 | AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails(); 100 | details.setId("google-oauth-client"); 101 | details.setClientId(clientId); 102 | details.setClientSecret(clientSecret); 103 | details.setUserAuthorizationUri(userAuthorizationUri); 104 | details.setAccessTokenUri(accessTokenUri); 105 | details.setTokenName(tokenName); 106 | String commaSeparatedScopes = scope; 107 | details.setScope(parseScopes(commaSeparatedScopes)); 108 | // Defaults to use current URI 109 | /* 110 | * If a pre-established redirect URI is used, it will need to be an 111 | * absolute URI. To do so, it'll need to compute the URI from a 112 | * request. The HTTP request object is available when you override 113 | * OAuth2ClientAuthenticationProcessingFilter#attemptAuthentication(). 114 | * 115 | * details.setPreEstablishedRedirectUri( 116 | * env.getProperty("oauth2.redirectUrl")); 117 | * details.setUseCurrentUri(false); 118 | */ 119 | details.setAuthenticationScheme(AuthenticationScheme.query); 120 | details.setClientAuthenticationScheme(AuthenticationScheme.form); 121 | return details; 122 | } 123 | 124 | private List parseScopes(String commaSeparatedScopes) { 125 | List scopes = new LinkedList<>(); 126 | Collections.addAll(scopes, commaSeparatedScopes.split(",")); 127 | return scopes; 128 | } 129 | 130 | /** 131 | * @return an OAuth2 client authentication processing filter 132 | */ 133 | @Bean 134 | @Description("Filter that checks for authorization code, " 135 | + "and if there's none, acquires it from authorization server") 136 | public OAuth2ClientAuthenticationProcessingFilter 137 | oauth2ClientAuthenticationProcessingFilter() { 138 | // Used to obtain access token from authorization server (AS) 139 | OAuth2RestOperations restTemplate = new OAuth2RestTemplate( 140 | authorizationCodeResource(), 141 | oauth2ClientContext); 142 | OAuth2ClientAuthenticationProcessingFilter filter = 143 | new OAuth2ClientAuthenticationProcessingFilter(oauth2FilterCallbackPath); 144 | filter.setRestTemplate(restTemplate); 145 | // Set a service that validates an OAuth2 access token 146 | // We can use either Google API's UserInfo or TokenInfo 147 | // For this, we chose to use UserInfo service 148 | filter.setTokenServices(googleUserInfoTokenServices()); 149 | return filter; 150 | } 151 | 152 | @Bean 153 | @Description("Google API UserInfo resource server") 154 | public GoogleUserInfoTokenServices googleUserInfoTokenServices() { 155 | GoogleUserInfoTokenServices userInfoTokenServices = 156 | new GoogleUserInfoTokenServices(userInfoUri, clientId); 157 | // TODO Configure bean to use local database to read authorities 158 | // userInfoTokenServices.setAuthoritiesExtractor(authoritiesExtractor); 159 | return userInfoTokenServices; 160 | } 161 | 162 | @Bean 163 | public AuthenticationEntryPoint authenticationEntryPoint() { 164 | // May need an OAuth2AuthenticationEntryPoint for non-browser clients 165 | return new LoginUrlAuthenticationEntryPoint(oauth2FilterCallbackPath); 166 | } 167 | 168 | @Override 169 | public void configure(WebSecurity web) throws Exception { 170 | web.ignoring().antMatchers( 171 | "/", "/static/**", "/webjars/**"); 172 | } 173 | 174 | @Override 175 | protected void configure(HttpSecurity http) throws Exception { 176 | http.exceptionHandling() 177 | .authenticationEntryPoint(authenticationEntryPoint()) 178 | .and() 179 | .authorizeRequests() 180 | .anyRequest().authenticated() 181 | .and() 182 | .logout() 183 | .logoutUrl("/logout") 184 | .logoutSuccessUrl("/") 185 | /* No need for form-based login or basic authentication 186 | .and() 187 | .formLogin() 188 | .loginPage("...") 189 | .loginProcessingUrl("...") 190 | .and() 191 | .httpBasic() 192 | */ 193 | .and() 194 | .addFilterAfter( 195 | oauth2ClientContextFilter, 196 | ExceptionTranslationFilter.class) 197 | .addFilterBefore( 198 | oauth2ClientAuthenticationProcessingFilter(), 199 | FilterSecurityInterceptor.class) 200 | .anonymous() 201 | // anonymous login must be disabled, 202 | // otherwise an anonymous authentication will be created, 203 | // and the UserRedirectRequiredException will not be thrown, 204 | // and the user will not be redirected to the authorization server 205 | .disable(); 206 | } 207 | 208 | @Override 209 | protected AuthenticationManager authenticationManager() throws Exception { 210 | return new NoopAuthenticationManager(); 211 | } 212 | 213 | private static class NoopAuthenticationManager implements AuthenticationManager { 214 | @Override 215 | public Authentication authenticate(Authentication authentication) 216 | throws AuthenticationException { 217 | throw new UnsupportedOperationException( 218 | "No authentication should be done with this AuthenticationManager"); 219 | } 220 | } 221 | 222 | @Bean 223 | @Description("Enables ${...} expressions in the @Value annotations" 224 | + " on fields of this configuration. Not needed if one is" 225 | + " already available.") 226 | public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { 227 | return new PropertySourcesPlaceholderConfigurer(); 228 | } 229 | 230 | } 231 | -------------------------------------------------------------------------------- /src/main/java/web/sample/SecurityWebAppInitializer.java: -------------------------------------------------------------------------------- 1 | package web.sample; 2 | 3 | import org.springframework.core.annotation.Order; 4 | import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; 5 | 6 | @Order(2) 7 | public class SecurityWebAppInitializer extends AbstractSecurityWebApplicationInitializer { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/web/sample/WebAppInitializer.java: -------------------------------------------------------------------------------- 1 | package web.sample; 2 | 3 | import javax.servlet.ServletContext; 4 | import javax.servlet.ServletException; 5 | 6 | import org.springframework.core.annotation.Order; 7 | import org.springframework.web.context.request.RequestContextListener; 8 | import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; 9 | 10 | @Order(1) 11 | public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { 12 | 13 | @Override 14 | public void onStartup(ServletContext servletContext) throws ServletException { 15 | super.onStartup(servletContext); 16 | /* 17 | * Needed to support a "request" scope in Spring Security filters, 18 | * since they're configured as a Servlet Filter. But not necessary 19 | * if they're configured as interceptors in Spring MVC. 20 | */ 21 | servletContext.addListener(new RequestContextListener()); 22 | } 23 | 24 | @Override 25 | protected Class[] getRootConfigClasses() { 26 | return null; 27 | } 28 | 29 | @Override 30 | protected Class[] getServletConfigClasses() { 31 | return new Class[] { 32 | WebMvcConfig.class, 33 | OAuth2ClientSecurityConfig.class 34 | }; 35 | } 36 | 37 | @Override 38 | protected String[] getServletMappings() { 39 | return new String[] { "/" }; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/web/sample/WebMvcConfig.java: -------------------------------------------------------------------------------- 1 | package web.sample; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 5 | import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; 6 | import org.springframework.web.servlet.config.annotation.ViewResolverRegistry; 7 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 8 | 9 | @Configuration 10 | @EnableWebMvc 11 | public class WebMvcConfig extends WebMvcConfigurerAdapter { 12 | 13 | @Override 14 | public void addViewControllers(ViewControllerRegistry registry) { 15 | // Here, we provide a view that can be accessed by anyone (even not authenticated) 16 | registry.addViewController("/").setViewName("welcome"); 17 | // Then, we provide a view that can *only* be accessed by an authenticated user 18 | registry.addViewController("/secured").setViewName("secured"); 19 | } 20 | 21 | @Override 22 | public void configureViewResolvers(ViewResolverRegistry registry) { 23 | registry.jsp("/WEB-INF/views/", ".jsp"); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/google-oauth2.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Refer to https://developers.google.com/identity/protocols/OAuth2WebServer 3 | # 4 | oauth2.clientId=1016921926680-vd07kma4a9am2qdg07rmr8b4ju3a3nmk.apps.googleusercontent.com 5 | oauth2.clientSecret=gY-p1OshijWHTCoBU1pxmaBz 6 | # 7 | # Comma-separated scopes 8 | # Refer to https://developers.google.com/identity/protocols/googlescopes 9 | oauth2.scope=https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/userinfo.profile 10 | # 11 | # E.g. "https://accounts.google.com/o/oauth2/v2/auth" 12 | oauth2.userAuthorizationUri=https://accounts.google.com/o/oauth2/v2/auth 13 | # 14 | # E.g. "https://www.googleapis.com/oauth2/v4/token" 15 | oauth2.accessTokenUri=https://www.googleapis.com/oauth2/v4/token 16 | # 17 | # Usually "authorization_code" 18 | oauth2.tokenName=authorization_code 19 | # 20 | # Defaults to current URI 21 | # If using a pre-established redirect URI (it must be absolute, not relative) 22 | #oauth2.redirectUrl=http://localhost:8080/.../oauth2/callback 23 | # 24 | # Path used by authentication processing filter and authentication entry point 25 | oauth2.filterCallbackPath=/oauth2/callback 26 | # 27 | # E.g. "https://www.googleapis.com/oauth2/v3/userinfo" 28 | oauth2.userInfoUri=https://www.googleapis.com/oauth2/v3/userinfo 29 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | true 9 | 10 | 11 | 13 | 14 | 15 | %-5level - %msg%n 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/views/secured.jsp: -------------------------------------------------------------------------------- 1 | <%@ page session="false" 2 | %><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" 3 | %><%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" 4 | %><%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" 5 | %> 6 | 7 | 8 | Testing OAuth2 with Google 9 | 10 | 11 |

Yey!

12 | 13 |
    14 |
  • Id:
  • 15 |
  • User name:
  • 16 |
  • Email:
  • 17 |
  • 18 | Picture: 19 | 20 |
  • 21 |
22 | 23 |

Since you're logged in, you can try 24 | 25 | logging out. 26 | Logging out does not sign you out of the provider (Google, in this case). 27 | It actually logs you out of this client (i.e. invalidates session and clears cookies of this web app). 28 |

29 | <%-- CSRF is enabled, by default. We'll need to POST /logout --%> 30 | 31 |
32 | 33 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/views/welcome.jsp: -------------------------------------------------------------------------------- 1 | <%@ page session="false" 2 | %><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" 3 | %> 4 | 5 | 6 | Testing OAuth2 with Google 7 | 8 | 9 |

Testing OAuth2 with Google

10 |

Go to this secured URL. You'll be asked to authenticate with the OAuth provider (in this case Google).

11 | 12 | -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | true 9 | 10 | 11 | 13 | 14 | 15 | %-5level %logger{36} - %msg%n 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | --------------------------------------------------------------------------------