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 Filter
和Authentication 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 Security-----SpringSocial社交登录详解
Spring Boot security/OAuth2 - 丢弃远程身份验证并在访问远程 userInfo 端点后恢复本地