Добавьте spring-oauth2 (google + facebook) в существующее приложение spring-security 5.1 в качестве дополнительного метода аутентификации - PullRequest
0 голосов
/ 14 октября 2018

У меня есть и: рабочая spring-security конфигурация, и рабочая spring-oauth2 конфигурация.Оба протестированы и работают нормально по отдельности.Затем я хотел бы добавить oauth2 в качестве дополнительного метода аутентификации для пользователей.Таким образом, пользователь может выбрать, хотите ли он создать учетную запись в моем сервисе или пройти аутентификацию с помощью учетной записи Google или Facebook.

Чтобы достичь этого, я просто добавил свою рабочую ssoFilter в конфигурацию http:

.and().addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class)

Вот как это выглядит, когда я подключил две конфигурации:

package com.fridayweekend.web.security.oauth2;

import com.fridayweekend.lottery.model.Authority;
import com.fridayweekend.lottery.model.User;
import com.fridayweekend.lottery.model.UserLoginType;
import com.fridayweekend.lottery.service.UserService;
import com.fridayweekend.web.security.CustomAuthFailureHandler;
import com.fridayweekend.web.security.CustomAuthSuccessHandler;
import com.fridayweekend.web.security.CustomAuthenticationProcessingFilterEntryPoint;
import com.fridayweekend.web.security.CustomLogoutSuccessHandler;
import com.fridayweekend.web.security.CustomPersistentTokenBasedRememberMeServices;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.oauth2.resource.AuthoritiesExtractor;
import org.springframework.boot.autoconfigure.security.oauth2.resource.PrincipalExtractor;
import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter;
import org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter;
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails;
import org.springframework.security.oauth2.common.AuthenticationScheme;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.web.filter.CompositeFilter;

import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;

@Configuration
@EnableWebSecurity
@EnableOAuth2Client
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

    private static final Logger log = LoggerFactory.getLogger(MyWebSecurityConfigurerAdapter.class);

    @Autowired
    Properties myProperties;

    @Autowired
    UserService userService;

    @Autowired
    OAuth2ClientContext oauth2ClientContext;

    @Autowired
    UserDetailsService userDetailsService;

    @Autowired
    CustomLogoutSuccessHandler customLogoutSuccessHandler;

    @Autowired
    CustomPersistentTokenBasedRememberMeServices customPersistentTokenBasedRememberMeServices;

    @Autowired
    CustomAuthFailureHandler customAuthFailureHandler;

    @Autowired
    CustomAuthSuccessHandler customAuthSuccessHandler;

    @Autowired
    CustomAuthenticationProcessingFilterEntryPoint customAuthenticationProcessingFilterEntryPoint;

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception{
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        auth.authenticationProvider(provider);
    }

    @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
                .exceptionHandling()
                .authenticationEntryPoint(customAuthenticationProcessingFilterEntryPoint)

                .and()
                .formLogin()
                .loginPage("/login?now")
                .failureHandler(customAuthFailureHandler)
                .loginProcessingUrl("/user/j_spring_security_check")
                .successHandler(customAuthSuccessHandler)

                .and()
                .logout()
                .logoutUrl("/user/j_spring_security_logout")
                .clearAuthentication(true)
                .logoutSuccessHandler(customLogoutSuccessHandler)

                .and()
                .rememberMe()
                .rememberMeServices(customPersistentTokenBasedRememberMeServices).key("myRememberMeKey")

                .and()
                .authorizeRequests()
                .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
                .antMatchers("/moderator/**").access("hasRole('ROLE_MODERATOR')")
                .antMatchers("/user/**").access("hasRole('ROLE_USER')")

                .and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .and().addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class)
        ;
    }

    @Bean
    public FilterRegistrationBean oauth2ClientFilterRegistration(
            OAuth2ClientContextFilter filter) {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(filter);
        registration.setOrder(-100);
        return registration;
    }

    @Bean
    public ResourceServerProperties googleResource() {
        ResourceServerProperties rsp = new ResourceServerProperties();
        rsp.setUserInfoUri(myProperties.getProperty("google.resource.userInfoUri"));
        rsp.setPreferTokenInfo(Boolean.parseBoolean(myProperties.getProperty("google.resource.preferTokenInfo")));
        return rsp;
    }

    @Bean
    public AuthorizationCodeResourceDetails google() {
        AuthorizationCodeResourceDetails acrd = new AuthorizationCodeResourceDetails();
        acrd.setClientId(myProperties.getProperty("google.client.clientId"));
        acrd.setClientSecret(myProperties.getProperty("google.client.clientSecret"));
        acrd.setAccessTokenUri(myProperties.getProperty("google.client.accessTokenUri"));
        acrd.setUserAuthorizationUri(myProperties.getProperty("google.client.userAuthorizationUri"));
        acrd.setTokenName(myProperties.getProperty("google.client.tokenName"));
        acrd.setAuthenticationScheme(AuthenticationScheme.valueOf(myProperties.getProperty("google.client.authenticationScheme")));
        acrd.setClientAuthenticationScheme(AuthenticationScheme.valueOf(myProperties.getProperty("google.client.clientAuthenticationScheme")));
        acrd.setScope(Arrays.asList(myProperties.getProperty("google.client.scope")));
        return acrd;
    }

    @Bean
    public ResourceServerProperties facebookResource() {
        ResourceServerProperties rsp = new ResourceServerProperties();
        rsp.setUserInfoUri(myProperties.getProperty("facebook.resource.userInfoUri"));
        return rsp;
    }

    @Bean
    public AuthorizationCodeResourceDetails facebook() {
        AuthorizationCodeResourceDetails acrd = new AuthorizationCodeResourceDetails();
        acrd.setClientId(myProperties.getProperty("facebook.client.clientId"));
        acrd.setClientSecret(myProperties.getProperty("facebook.client.clientSecret"));
        acrd.setAccessTokenUri(myProperties.getProperty("facebook.client.accessTokenUri"));
        acrd.setUserAuthorizationUri(myProperties.getProperty("facebook.client.userAuthorizationUri"));
        acrd.setTokenName(myProperties.getProperty("facebook.client.tokenName"));
        acrd.setAuthenticationScheme(AuthenticationScheme.valueOf(myProperties.getProperty("facebook.client.authenticationScheme")));
        acrd.setClientAuthenticationScheme(AuthenticationScheme.valueOf(myProperties.getProperty("facebook.client.clientAuthenticationScheme")));
        return acrd;
    }

    @Bean
    public Filter ssoFilter() {
        CompositeFilter filter = new CompositeFilter();
        List<Filter> filters = new ArrayList<>();

        String facebookLoginPath = "/login/facebook";
        OAuth2ClientAuthenticationProcessingFilter facebookFilter = new OAuth2ClientAuthenticationProcessingFilter(facebookLoginPath);
        OAuth2RestTemplate facebookTemplate = new OAuth2RestTemplate(facebook(), oauth2ClientContext);
        facebookFilter.setRestTemplate(facebookTemplate);
        UserInfoTokenServices tokenServices = new UserInfoTokenServices(facebookResource().getUserInfoUri(), facebook().getClientId());
        PrincipalExtractor facebookPrincipalExtractor = principalExtractor(UserLoginType.FACEBOOK);
        AuthoritiesExtractor facebookAuthoritiesExtractor = authoritiesExtractor();
        tokenServices.setAuthoritiesExtractor(facebookAuthoritiesExtractor);
        tokenServices.setPrincipalExtractor(facebookPrincipalExtractor);
        tokenServices.setRestTemplate(facebookTemplate);
        facebookFilter.setTokenServices(tokenServices);
        filters.add(facebookFilter);

        String googleLoginPath = "/login/google";
        OAuth2ClientAuthenticationProcessingFilter googleFilter = new OAuth2ClientAuthenticationProcessingFilter(googleLoginPath);
        OAuth2RestTemplate googleTemplate = new OAuth2RestTemplate(google(), oauth2ClientContext);
        googleFilter.setRestTemplate(googleTemplate);
        tokenServices = new UserInfoTokenServices(googleResource().getUserInfoUri(), google().getClientId());
        PrincipalExtractor googlePrincipalExtractor = principalExtractor(UserLoginType.GOOGLE);
        tokenServices.setPrincipalExtractor(googlePrincipalExtractor);
        AuthoritiesExtractor googleAuthoritiesExtractor = authoritiesExtractor();
        tokenServices.setAuthoritiesExtractor(googleAuthoritiesExtractor);
        tokenServices.setRestTemplate(googleTemplate);
        googleFilter.setTokenServices(tokenServices);
        filters.add(googleFilter);

        filter.setFilters(filters);
        return filter;
    }


    private AuthoritiesExtractor authoritiesExtractor(){

        return map -> {
            String username = (String) map.get("id");
            log.info("Setting roles for user {}", username);
            User user = userService.getUserByName(username);
            if (user == null) {
                return Collections.<GrantedAuthority> emptyList();
            }
            List<Authority> authorities = user.getAuthorities();
            List<String> roles = authorities.stream()
                                    .map(Authority::getName)
                                        .collect(Collectors.toList());
            return AuthorityUtils.createAuthorityList(roles.stream().toArray(size -> new String[size]));
        };
    }

    private PrincipalExtractor principalExtractor(UserLoginType type) {

        return map -> {
            String username = (String) map.get("id");
            log.info("Searching for user {}", username);
            User user = userService.getUserByName(username);
            if (user == null) {
                log.info("No user found, generating profile for {}", username);
                user = new User();
                user.setName(username);
                user.setEmail((String) map.get("email"));
                user.setDisplayName((String) map.get("name"));
                userService.registerUser(user, null,null,null,null, type);
            }
            return user;
        };
    }
}

Соответствующий web.xml фрагмент кода:

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
    <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
              /WEB-INF/spring/web-context.xml,
              /WEB-INF/spring/web/wsServlet-context.xml,
              /WEB-INF/spring/security/spring-security.xml
    </param-value>
</context-param>

<!-- Spring Security -->
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>*</url-pattern>
</filter-mapping>

Версии зависимостей:

    <org.springframework-version>5.1.0.RELEASE</org.springframework-version>
    <org.springframework.security-version>5.1.0.RELEASE</org.springframework.security-version>
    <org.springboot-version>2.0.5.RELEASE</org.springboot-version>
    <org.sringsecurity.security.oauth-version>2.3.3.RELEASE</org.sringsecurity.security.oauth-version>
    <org.springframework.cloud.spring-cloud-security-version>2.0.0.RELEASE</org.springframework.cloud.spring-cloud-security-version>
    <org.springframework.webflow-version>2.4.1.RELEASE</org.springframework.webflow-version>
    <org.aspectj-version>1.7.3</org.aspectj-version>
    <org.slf4j-version>1.6.1</org.slf4j-version>
    <org.hibernate-version>5.3.6.Final</org.hibernate-version>
    <spring.integration.version>4.1.4.RELEASE</spring.integration.version>

Я что-то не так делаю?Чего мне не хватает?

Конфигурация sring-security работает (пользователь может создать учетную запись / логин / выход из системы), но когда он пытается получить доступ к URL-адресам login/facebook или login/google, появляется следующее исключение, и страницане отображается вообще:

0-Oct-2018 01:14:55.857 SEVERE [http-nio-8080-exec-6] org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [entertainmentSelector] in context with path [] threw exception
 org.springframework.security.oauth2.client.resource.UserRedirectRequiredException: A redirect is required to get the users approval
    at org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider.getRedirectForAuthorization(AuthorizationCodeAccessTokenProvider.java:359)
    at org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider.obtainAccessToken(AuthorizationCodeAccessTokenProvider.java:205)
    at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainNewAccessTokenInternal(AccessTokenProviderChain.java:148)
    at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainAccessToken(AccessTokenProviderChain.java:121)
    at org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:221)
    at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:173)
    at org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter.attemptAuthentication(OAuth2ClientAuthenticationProcessingFilter.java:105)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212)
    at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:112)
    at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:73)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:668)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:770)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1415)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:748)
...