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

Chr*_*ayr 7 spring facebook oauth oauth-2.0 spring-boot

我目前正在使用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);
    }
}
Run Code Online (Sandbox Code Playgroud)

}

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

小智 0

一种可能的解决方案是实施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;
    }

}
Run Code Online (Sandbox Code Playgroud)

这会过滤授权标头,识别它们是 facebook 还是 bearer,然后定向到特定的提供商。

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 List<String> getUserRoles(String id) {
        List<String> roles = new ArrayList<>();
        userRoleRepository.findByUserId(id).forEach(applicationRole -> roles.add(applicationRole.getRole()));
        return roles;
    }

    private List<Roles> parseRoles(List<String> strRoles) {
        List<Roles> roles = new ArrayList<>();
        for(String strRole : strRoles) {
            roles.add(Roles.valueOf(strRole));
        }
        return roles;
    }

    private Collection<? extends GrantedAuthority> parseAuthorities(Collection<String> roles) {
        if (roles == null || roles.size() == 0)
            return Collections.emptyList();
        return roles.stream().map(role -> (GrantedAuthority) () -> "ROLE_" + role).collect(Collectors.toList());
    }

    @Override
    public boolean supports(Class<?> auth) {
        return auth.equals(UsernamePasswordAuthenticationToken.class);
    }
}
Run Code Online (Sandbox Code Playgroud)

唯一FacebookUser引用本地用户 ID 和 Facebook Id(这是 facebook 和我们的应用程序之间的链接)。

SocialResourceUtils用于通过 facebook API 获取 facebook 用户信息(使用方法getResourceByToken)。Facebook 资源 URL 设置为application.properties( config.oauth2.facebook.resourceURL)。这个方法基本上是:

    public SocialUser getResourceByToken(String token) {
        RestTemplate restTemplate = new RestTemplate();
        String authorization = token;
        JsonNode response = null;
        try {
            response = restTemplate.getForObject(accessUrl + authorization, JsonNode.class);
        } catch (RestClientException e) {
            throw new BadCredentialsException("Authentication failed.");
        }
        return buildSocialUser(response);
    }
Run Code Online (Sandbox Code Playgroud)

Bearer Provider是你的本地身份验证,你可以自己制作,或者使用springboot默认值,使用其他身份验证方法,idk(我不会把我的实现放在这里,那是你的)。

最后,您需要创建网络安全配置器:

@ConditionalOnProperty("security.basic.enabled")
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
    private BearerAuthenticationProvider bearerAuthenticationProvider;

    @Autowired
    private FacebookAuthenticationProvider facebookAuthenticationProvider;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.addFilterBefore(new ServerAuthenticationFilter(bearerAuthenticationProvider,
                facebookAuthenticationProvider), BasicAuthenticationFilter.class);
    }

}
Run Code Online (Sandbox Code Playgroud)

ConditionalOnProperty请注意,它具有启用/禁用属性的注释security.basic.enabled。允许@EnableGlobalMethodSecurity(prePostEnabled = true)使用注释,@PreAuthorize这使我们能够按角色保护端点(@PreAuthorize("hasRole ('ADMIN')")通过端点使用,仅允许管理员访问)

这段代码需要很多改进,但我希望我有所帮助。