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

问题描述 投票:7回答:1

我目前正在使用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);
    }
}

}

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

spring facebook spring-boot oauth oauth-2.0
1个回答
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;
    }

}

这样,过滤授权标头,识别他们是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 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);
    }
}

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

这个SocialResourceUtils用于通过facebook API获取facebook用户信息(使用方法getResourceByToken)。 facebook资源网址在application.propertiesconfig.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);
    }

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

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

@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);
    }

}

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

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

© www.soinside.com 2019 - 2024. All rights reserved.