Spring Boot社交登录和本地OAuth2-Server

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Boot社交登录和本地OAuth2-Server相关的知识,希望对你有一定的参考价值。

我目前正在使用OAuth2-Authentication开发Spring Boot-Application。我有一个本地OAuth2-Server,在我使用Spring Boot的UserDetails和UserService发布本地数据库的用户名和密码时,我会收到一个令牌,我的情况是http://localhost:8080/v1/oauth/token。一切都很好,很好。

但现在我想通过Facebook社交登录来增强我的程序,并希望登录到我的本地OAuth2-Server或使用外部Facebook-Server。我检查了Spring Boot示例https://spring.io/guides/tutorials/spring-boot-oauth2/并改编了SSO-Filter的想法。现在我可以使用我的Facebook客户端和密码ID登录,但我无法访问我受限制的localhost-站点。

我想要的是Facebook-Token“行为”与本地生成的令牌相同,例如作为我本地令牌存储的一部分。我检查了几个教程和其他Stackoverflow问题,但没有运气。以下是我到目前为止使用的自定义Authorization-Server,我认为我仍然缺少一些非常基本的东西来获取外部Facebook-和内部localhost-Server之间的链接:

@Configuration
public class OAuth2ServerConfiguration {
private static final String SERVER_RESOURCE_ID = "oauth2-server";

@Autowired
private TokenStore tokenStore;

@Bean
public TokenStore tokenStore() {
    return new InMemoryTokenStore();
}

protected class ClientResources {
    @NestedConfigurationProperty
    private AuthorizationCodeResourceDetails client = new AuthorizationCodeResourceDetails();

    @NestedConfigurationProperty
    private ResourceServerProperties resource = new ResourceServerProperties();

    public AuthorizationCodeResourceDetails getClient() {
        return client;
    }

    public ResourceServerProperties getResource() {
        return resource;
    }
}

@Configuration
@EnableResourceServer
@EnableOAuth2Client
protected class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Value("${pia.requireauth}")
    private boolean requireAuth;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenStore(tokenStore).resourceId(SERVER_RESOURCE_ID);
    }

    @Autowired
    OAuth2ClientContext oauth2ClientContext;

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

    @Bean
    @ConfigurationProperties("facebook")
    public ClientResources facebook() {
        return new ClientResources();
    }

    private Filter ssoFilter() {
        CompositeFilter filter = new CompositeFilter();
        List<Filter> filters = new ArrayList<>();
        filters.add(ssoFilter(facebook(), "/login/facebook"));
        filter.setFilters(filters);
        return filter;
    }

    private Filter ssoFilter(ClientResources client, String path) {
        OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(path);
        OAuth2RestTemplate template = new OAuth2RestTemplate(client.getClient(), oauth2ClientContext);
        filter.setRestTemplate(template);
        UserInfoTokenServices tokenServices = new UserInfoTokenServices(client.getResource().getUserInfoUri(),
                client.getClient().getClientId());
        tokenServices.setRestTemplate(template);
        filter.setTokenServices(tokenServices);
        return filter;
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        if (!requireAuth) {
            http.antMatcher("/**").authorizeRequests().anyRequest().permitAll();
        } else {
            http.antMatcher("/**").authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN")
                    .antMatchers("/", "/login**", "/webjars/**").permitAll().anyRequest().authenticated().and()
                    .exceptionHandling().and().csrf()
                    .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
                    .addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
        }
    }
}

@Configuration
@EnableAuthorizationServer
protected class OAuth2Configuration extends AuthorizationServerConfigurerAdapter {
    @Value("${pia.oauth.tokenTimeout:3600}")
    private int expiration;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    @Qualifier("userDetailsService")
    private UserDetailsService userDetailsService;

    // password encryptor
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer configurer) throws Exception {
        configurer.authenticationManager(authenticationManager).tokenStore(tokenStore).approvalStoreDisabled();
        configurer.userDetailsService(userDetailsService);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory().withClient("pia").secret("alphaport").accessTokenValiditySeconds(expiration)
                .authorities("ROLE_USER").scopes("read", "write").authorizedGrantTypes("password", "refresh_token")
                .resourceIds(SERVER_RESOURCE_ID);
    }
}

}

任何有关此问题的帮助和/或示例都非常感谢! :)

答案

一种可能的解决方案是实施Authentication FilterAuthentication Provider

在我的情况下,我已经实现了OAuth2身份验证,并允许用户使用facebook access_token访问某些端点

身份验证筛选器如下所示:

public class ServerAuthenticationFilter extends GenericFilterBean {

    private BearerAuthenticationProvider bearerAuthenticationProvider;
    private FacebookAuthenticationProvider facebookAuthenticationProvider;

    public ServerAuthenticationFilter(BearerAuthenticationProvider bearerAuthenticationProvider,
            FacebookAuthenticationProvider facebookAuthenticationProvider) {
        this.bearerAuthenticationProvider = bearerAuthenticationProvider;
        this.facebookAuthenticationProvider = facebookAuthenticationProvider;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        Optional<String> authorization = Optional.fromNullable(httpRequest.getHeader("Authorization"));
        try {
            AuthType authType = getAuthType(authorization.get());
            if (authType == null) {
                SecurityContextHolder.clearContext();
                httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            }
            String strToken = authorization.get().split(" ")[1];
            if (authType == AuthType.BEARER) {
                if (strToken != null) {
                    Optional<String> token = Optional.of(strToken);
                    logger.debug("Trying to authenticate user by Bearer method. Token: " + token.get());
                    processBearerAuthentication(token);
                }
            } else if (authType == AuthType.FACEBOOK) {
                if (strToken != null) {
                    Optional<String> token = Optional.of(strToken);
                    logger.debug("Trying to authenticate user by Facebook method. Token: " + token.get());
                    processFacebookAuthentication(token);
                }
            }
            logger.debug(getClass().getSimpleName() + " is passing request down the filter chain.");
            chain.doFilter(request, response);
        } catch (InternalAuthenticationServiceException internalAuthenticationServiceException) {
            SecurityContextHolder.clearContext();
            logger.error("Internal Authentication Service Exception", internalAuthenticationServiceException);
            httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } catch (AuthenticationException authenticationException) {
            SecurityContextHolder.clearContext();
            httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, authenticationException.getMessage());
        } catch (Exception e) {
            SecurityContextHolder.clearContext();
            e.printStackTrace();
            httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
        }
    }

    private AuthType getAuthType(String value) {
        if (value == null)
            return null;
        String[] basicSplit = value.split(" ");
        if (basicSplit.length != 2)
            return null;
        if (basicSplit[0].equalsIgnoreCase("bearer"))
            return AuthType.BEARER;
        if (basicSplit[0].equalsIgnoreCase("facebook"))
            return AuthType.FACEBOOK;
        return null;
    }

    private void processBearerAuthentication(Optional<String> token) {
        Authentication resultOfAuthentication = tryToAuthenticateWithBearer(token);
        SecurityContextHolder.getContext().setAuthentication(resultOfAuthentication);
    }

    private void processFacebookAuthentication(Optional<String> token) {
        Authentication resultOfAuthentication = tryToAuthenticateWithFacebook(token);
        SecurityContextHolder.getContext().setAuthentication(resultOfAuthentication);
    }

    private Authentication tryToAuthenticateWithBearer(Optional<String> token) {
        PreAuthenticatedAuthenticationToken requestAuthentication = new PreAuthenticatedAuthenticationToken(token,
                null);
        return tryToAuthenticateBearer(requestAuthentication);
    }

    private Authentication tryToAuthenticateWithFacebook(Optional<String> token) {
        PreAuthenticatedAuthenticationToken requestAuthentication = new PreAuthenticatedAuthenticationToken(token,
                null);
        return tryToAuthenticateFacebook(requestAuthentication);
    }

    private Authentication tryToAuthenticateBearer(Authentication requestAuthentication) {
        Authentication responseAuthentication = bearerAuthenticationProvider.authenticate(requestAuthentication);
        if (responseAuthentication == null || !responseAuthentication.isAuthenticated()) {
            throw new InternalAuthenticationServiceException(
                    "Unable to Authenticate for provided credentials.");
        }
        logger.debug("Application successfully authenticated by bearer method.");
        return responseAuthentication;
    }

    private Authentication tryToAuthenticateFacebook(Authentication requestAuthentication) {
        Authentication responseAuthentication = facebookAuthenticationProvider.authenticate(requestAuthentication);
        if (responseAuthentication == null || !responseAuthentication.isAuthenticated()) {
            throw new InternalAuthenticationServiceException(
                    "Unable to Authenticate for provided credentials.");
        }
        logger.debug("Application successfully authenticated by facebook method.");
        return responseAuthentication;
    }

}

这样,过滤授权标头,识别他们是Facebook还是持票人,然后指向特定的提供商。

Facebook提供商看起来像这样:

public class FacebookAuthenticationProvider implements AuthenticationProvider {
    @Value("${config.oauth2.facebook.resourceURL}")
    private String facebookResourceURL;

    private static final String PARAMETERS = "fields=name,email,gender,picture";

    @Autowired
    FacebookUserRepository facebookUserRepository;
    @Autowired
    UserRoleRepository userRoleRepository;

    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Override
    public Authentication authenticate(Authentication auth) throws AuthenticationException {
        Optional<String> token = auth.getPrincipal() instanceof Optional ? (Optional) auth.getPrincipal() : null;
        if (token == null || !token.isPresent() || token.get().isEmpty())
            throw new BadCredentialsException("Invalid Grants");
        SocialResourceUtils socialResourceUtils = new SocialResourceUtils(facebookResourceURL, PARAMETERS);
        SocialUser socialUser = socialResourceUtils.getResourceByToken(token.get());
        if (socialUser != null && socialUser.getId() != null) {
            User user = findOriginal(socialUser.getId());
            if (user == null)
                throw new BadCredentialsException("Authentication failed.");
            Credentials credentials = new Credentials();
            credentials.setId(user.getId());
            credentials.setUsername(user.getEmail());
            credentials.setName(user.getName());
            credentials.setRoles(parseRoles(user.translateRoles()));
            credentials.setToken(token.get());
            return new UsernamePasswordAuthenticationToken(credentials, credentials.getId(),
                    parseAuthorities(getUserRoles(user.getId())));
        } else
            throw new BadCredentialsException("Authentication failed.");

    }

    protected User findOriginal(String id) {
        FacebookUser facebookUser = facebookUserRepository.findByFacebookId(facebookId);
        return null == facebookUser ? null : userRepository.findById(facebookUser.getUserId()).get();
    }

    protected

以上是关于Spring Boot社交登录和本地OAuth2-Server的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot 和 OAuth2 社交登录,无法获取 refreshToken

如何使用 OAuth2 和社交登录保护 Spring Boot RESTful 服务

如何在使用 OAuth2 社交登录保护 REST API 的同时配置 Spring Boot 登录页面

为社交登录用户生成不记名令牌 Spring Boot

Spring Security-----SpringSocial社交登录详解

Spring Boot security/OAuth2 - 丢弃远程身份验证并在访问远程 userInfo 端点后恢复本地