Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to configure Spring Boot and Spring Security to support both form login and Google OAuth2 login

I am strugging to configure a Spring Boot application with Spring Security to support two login mechanisms: form login and Google OAuth2 login.

I want to have a login page with the traditional login form. This page would also have a "Authenticate with Google button".

The login form would be the default login method, that is to say, when trying to access a protected resource the login.jsp would render. Here the user could click the oauth button.

The point is that I can configure them separately, either form login or Google auth, but I am not able to make them work together.

1.- Form login:

@EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter{

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .anyRequest().authenticated()
        .and()
            .formLogin()

2.- Google auth:

@EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter{

    private final String LOGIN_URL = "/login"

    @Autowired
    OAuth2ClientContextFilter oAuth2ClientContextFilter

    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() {
        return new LoginUrlAuthenticationEntryPoint(LOGIN_URL)
    }

    @Bean
    public OpenIDConnectAuthenticationFilter openIdConnectAuthenticationFilter(){
        return new OpenIDConnectAuthenticationFilter(LOGIN_URL)
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterAfter(oAuth2ClientContextFilter, AbstractPreAuthenticatedProcessingFilter.class)
            .addFilterAfter(openIdConnectAuthenticationFilter(), OAuth2ClientContextFilter.class)
            .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint())
        .and()
            .authorizeRequests()
                .anyRequest.authenticated()
    }
}


class OpenIDConnectAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    @Resource
    private OAuth2RestOperations restTemplate

    protected OpenIDConnectAuthenticationFilter(String defaultFilterProcessesUrl) {
        super(defaultFilterProcessesUrl)
        setAuthenticationManager({authentication -> authentication}) // AbstractAuthenticationProcessingFilter requires an authentication manager.
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException, IOException, ServletException {
        final ResponseEntity<UserInfo> userInfoResponseEntity = restTemplate.getForEntity("https://www.googleapis.com/oauth2/v2/userinfo", UserInfo.class)
        new PreAuthenticatedAuthenticationToken(userInfoResponseEntity.getBody(), empty(), NO_AUTHORITIES)
    }
}

class UserInfo {
    final String id
    final String name
    final String givenName
    final String familyName
    final String gender
    final String picture
    final String link

    @JsonCreator
    public UserInfo(@JsonProperty("id") String id,
                    @JsonProperty("name") String name,
                    @JsonProperty("given_name") String givenName,
                    @JsonProperty("family_name") String familyName,
                    @JsonProperty("gender") String gender,
                    @JsonProperty("picture") String picture,
                    @JsonProperty("link") String link) {
        this.id = id
        this.name = name
        this.givenName = givenName
        this.familyName = familyName
        this.gender = gender
        this.picture = picture
        this.link = link
    }

}

@Configuration
@EnableOAuth2Client
class OAuth2Client {

    @Value('${google.oauth2.clientId}')
    private String clientId

    @Value('${google.oauth2.clientSecret}')
    private String clientSecret

    @Bean
    // TODO retrieve from https://accounts.google.com/.well-known/openid-configuration ?
    public OAuth2ProtectedResourceDetails googleOAuth2Details() {
        AuthorizationCodeResourceDetails googleOAuth2Details = new AuthorizationCodeResourceDetails()
        googleOAuth2Details.setAuthenticationScheme(form)
        googleOAuth2Details.setClientAuthenticationScheme(form)
        googleOAuth2Details.setClientId(clientId)
        googleOAuth2Details.setClientSecret(clientSecret)
        googleOAuth2Details.setUserAuthorizationUri("https://accounts.google.com/o/oauth2/auth")
        googleOAuth2Details.setAccessTokenUri("https://www.googleapis.com/oauth2/v3/token")
        googleOAuth2Details.setScope(asList("openid"))
        googleOAuth2Details
    }

    @Resource
    private OAuth2ClientContext oAuth2ClientContext

    @Bean
    @Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
    public OAuth2RestOperations googleOAuth2RestTemplate() {
        new OAuth2RestTemplate(googleOAuth2Details(), oAuth2ClientContext)
    }
}

class CustomUserDetailsService implements AuthenticationUserDetailsService<OpenIDAuthenticationToken> {

    UserDetails loadUserDetails(OpenIDAuthenticationToken token) throws UsernameNotFoundException {
        new User(token.name, "", AuthorityUtils.createAuthorityList("ROLE_USER"))
    }
}
like image 280
codependent Avatar asked Oct 18 '22 20:10

codependent


1 Answers

This is how I solved it using two WebSecurityConfigurerAdapters:

@EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter{

    @Configuration
    @Order(1)
    static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .antMatcher("/secure-home")
                .authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                .formLogin()
                    .loginPage("/login")
                    .permitAll()
        }
    }

    @Configuration
    @Order(2)
    static class OAuth2SecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

        private final String LOGIN_URL = "/googleLogin";

        @Autowired
        OAuth2ClientContextFilter oAuth2ClientContextFilter

        @Bean
        AuthenticationEntryPoint authenticationEntryPoint() {
            new LoginUrlAuthenticationEntryPoint(LOGIN_URL)
        }

        @Bean
        OpenIDConnectAuthenticationFilter openIdConnectAuthenticationFilter() {
            new OpenIDConnectAuthenticationFilter(LOGIN_URL)
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .addFilterAfter(oAuth2ClientContextFilter, AbstractPreAuthenticatedProcessingFilter.class)
                .addFilterAfter(openIdConnectAuthenticationFilter(), OAuth2ClientContextFilter.class)
            .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint())
            .and()
                .authorizeRequests()
                    .antMatchers(GET, "/googleOAuth2").authenticated()
        }
    }
}

The full source code is available here: https://github.com/codependent/spring-boot-google-signin

like image 163
codependent Avatar answered Oct 21 '22 05:10

codependent