首页 文章

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

提问于
浏览
7

我目前正在使用OAuth2-Authentication开发Spring Boot-Application . 我有一个本地OAuth2-Server,在我使用Spring Boot的UserDetails和UserService发布本地数据库的用户名和密码时,我会收到一个令牌.1188494 . 一切都很好,很好 .

但现在我想通过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);
    }
}

}

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

1 回答

  • 0

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

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

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

    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 ,这使我们能够通过角色保护 endpoints (在 endpoints 上使用 @PreAuthorize("hasRole ('ADMIN')") ,仅允许访问管理员)

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

相关问题