Spring 启动安全性,Oauth2 将访问令牌替换为来自 facebook 的长期令牌
Spring boot Security, Oauth2 replace access token with long-lived token from facebook
我一直在关注 Dave Syer astounding tutorial 在为移动设备(Android 和 iOS)提供 RESTful API 的微服务中实施 OAuth2。我已使用以下代码配置网关安全性:
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
@EnableCircuitBreaker
@EnableFeignClients
@EnableOAuth2Client
public class GatewayApplication extends WebSecurityConfigurerAdapter {
private OAuth2ClientContext oauth2ClientContext;
private SimpleUrlAuthenticationSuccessHandler simpleUrlAuthenticationSuccessHandler;
private ScoreAuthorizationFilter scoreAuthorizationFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/", "/test", "/login**", "/webjars/**", "/error**")
.permitAll()
.anyRequest()
.authenticated()
.and().logout().logoutSuccessUrl("/").permitAll()
.and().addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class)
.addFilterBefore(scoreAuthorizationFilter, BasicAuthenticationFilter.class)
;
}
private Filter ssoFilter() {
OAuth2ClientAuthenticationProcessingFilter facebookFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/facebook");
OAuth2RestTemplate facebookTemplate = new OAuth2RestTemplate(facebook(), oauth2ClientContext);
facebookFilter.setRestTemplate(facebookTemplate);
UserInfoTokenServices tokenServices = new UserInfoTokenServices(facebookResource().getUserInfoUri(), facebook().getClientId());
tokenServices.setRestTemplate(facebookTemplate);
facebookFilter.setTokenServices(tokenServices);
facebookFilter.setAuthenticationSuccessHandler(simpleUrlAuthenticationSuccessHandler);
return facebookFilter;
}
@Bean
@ConfigurationProperties("facebook.client")
public AuthorizationCodeResourceDetails facebook() {
return new AuthorizationCodeResourceDetails();
}
@Bean
@ConfigurationProperties("facebook.resource")
public ResourceServerProperties facebookResource() {
return new ResourceServerProperties();
}
@Bean
public FilterRegistrationBean<OAuth2ClientContextFilter> oauth2ClientFilterRegistration(OAuth2ClientContextFilter filter) {
FilterRegistrationBean<OAuth2ClientContextFilter> registration = new FilterRegistrationBean<OAuth2ClientContextFilter>();
registration.setFilter(filter);
registration.setOrder(-100);
return registration;
}
@Bean
public RequestInterceptor getFeignClientInterceptor() {
return new FeignClientInterceptor();
}
}
原来用户的session过了一段时间就过期了。当我深入挖掘时,我发现 Facebook 不提供刷新令牌。相反,我们可以将短期令牌换成长期令牌 (Facebook long-lived token)。我如何覆盖 Spring 安全中实施的标准 OAuth2 流程,以向 Facebook 发送另一个请求以获取长期令牌,然后替换旧的访问令牌?
您可以像这样扩展 OAuth2ClientAuthenticationProcessingFilter
class 来实现您想要的效果:
public class CustomAuthenticationProcessingFilter extends OAuth2ClientAuthenticationProcessingFilter {
private ResourceServerTokenServices tokenServices;
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new OAuth2AuthenticationDetailsSource();
private ApplicationEventPublisher eventPublisher;
private AuthorizationCodeResourceDetails facebook;
private String longLivedTokenUri;
public CustomAuthenticationProcessingFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
setAuthenticationDetailsSource(authenticationDetailsSource);
}
@Override
public void setTokenServices(ResourceServerTokenServices tokenServices) {
this.tokenServices = tokenServices;
super.setTokenServices(tokenServices);
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
super.setApplicationEventPublisher(eventPublisher);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
OAuth2AccessToken accessToken;
try {
accessToken = restTemplate.getAccessToken();
} catch (OAuth2Exception e) {
BadCredentialsException bad = new BadCredentialsException("Could not obtain access token", e);
publish(new OAuth2AuthenticationFailureEvent(bad));
throw bad;
}
String longLivedToken = getFromFacebook(); //Get long lived token from facebook here
try {
OAuth2Authentication result = tokenServices.loadAuthentication(longLivedToken);
if (authenticationDetailsSource != null) {
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, longLivedToken);
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, accessToken.getTokenType());
result.setDetails(authenticationDetailsSource.buildDetails(request));
}
publish(new AuthenticationSuccessEvent(result));
return result;
} catch (InvalidTokenException e) {
BadCredentialsException bad = new BadCredentialsException("Could not obtain user details from token", e);
publish(new OAuth2AuthenticationFailureEvent(bad));
throw bad;
}
}
private void publish(ApplicationEvent event) {
if (eventPublisher != null) {
eventPublisher.publishEvent(event);
}
}
}
希望对您有所帮助。
我一直在关注 Dave Syer astounding tutorial 在为移动设备(Android 和 iOS)提供 RESTful API 的微服务中实施 OAuth2。我已使用以下代码配置网关安全性:
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
@EnableCircuitBreaker
@EnableFeignClients
@EnableOAuth2Client
public class GatewayApplication extends WebSecurityConfigurerAdapter {
private OAuth2ClientContext oauth2ClientContext;
private SimpleUrlAuthenticationSuccessHandler simpleUrlAuthenticationSuccessHandler;
private ScoreAuthorizationFilter scoreAuthorizationFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/", "/test", "/login**", "/webjars/**", "/error**")
.permitAll()
.anyRequest()
.authenticated()
.and().logout().logoutSuccessUrl("/").permitAll()
.and().addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class)
.addFilterBefore(scoreAuthorizationFilter, BasicAuthenticationFilter.class)
;
}
private Filter ssoFilter() {
OAuth2ClientAuthenticationProcessingFilter facebookFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/facebook");
OAuth2RestTemplate facebookTemplate = new OAuth2RestTemplate(facebook(), oauth2ClientContext);
facebookFilter.setRestTemplate(facebookTemplate);
UserInfoTokenServices tokenServices = new UserInfoTokenServices(facebookResource().getUserInfoUri(), facebook().getClientId());
tokenServices.setRestTemplate(facebookTemplate);
facebookFilter.setTokenServices(tokenServices);
facebookFilter.setAuthenticationSuccessHandler(simpleUrlAuthenticationSuccessHandler);
return facebookFilter;
}
@Bean
@ConfigurationProperties("facebook.client")
public AuthorizationCodeResourceDetails facebook() {
return new AuthorizationCodeResourceDetails();
}
@Bean
@ConfigurationProperties("facebook.resource")
public ResourceServerProperties facebookResource() {
return new ResourceServerProperties();
}
@Bean
public FilterRegistrationBean<OAuth2ClientContextFilter> oauth2ClientFilterRegistration(OAuth2ClientContextFilter filter) {
FilterRegistrationBean<OAuth2ClientContextFilter> registration = new FilterRegistrationBean<OAuth2ClientContextFilter>();
registration.setFilter(filter);
registration.setOrder(-100);
return registration;
}
@Bean
public RequestInterceptor getFeignClientInterceptor() {
return new FeignClientInterceptor();
}
}
原来用户的session过了一段时间就过期了。当我深入挖掘时,我发现 Facebook 不提供刷新令牌。相反,我们可以将短期令牌换成长期令牌 (Facebook long-lived token)。我如何覆盖 Spring 安全中实施的标准 OAuth2 流程,以向 Facebook 发送另一个请求以获取长期令牌,然后替换旧的访问令牌?
您可以像这样扩展 OAuth2ClientAuthenticationProcessingFilter
class 来实现您想要的效果:
public class CustomAuthenticationProcessingFilter extends OAuth2ClientAuthenticationProcessingFilter {
private ResourceServerTokenServices tokenServices;
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new OAuth2AuthenticationDetailsSource();
private ApplicationEventPublisher eventPublisher;
private AuthorizationCodeResourceDetails facebook;
private String longLivedTokenUri;
public CustomAuthenticationProcessingFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
setAuthenticationDetailsSource(authenticationDetailsSource);
}
@Override
public void setTokenServices(ResourceServerTokenServices tokenServices) {
this.tokenServices = tokenServices;
super.setTokenServices(tokenServices);
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
super.setApplicationEventPublisher(eventPublisher);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
OAuth2AccessToken accessToken;
try {
accessToken = restTemplate.getAccessToken();
} catch (OAuth2Exception e) {
BadCredentialsException bad = new BadCredentialsException("Could not obtain access token", e);
publish(new OAuth2AuthenticationFailureEvent(bad));
throw bad;
}
String longLivedToken = getFromFacebook(); //Get long lived token from facebook here
try {
OAuth2Authentication result = tokenServices.loadAuthentication(longLivedToken);
if (authenticationDetailsSource != null) {
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, longLivedToken);
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, accessToken.getTokenType());
result.setDetails(authenticationDetailsSource.buildDetails(request));
}
publish(new AuthenticationSuccessEvent(result));
return result;
} catch (InvalidTokenException e) {
BadCredentialsException bad = new BadCredentialsException("Could not obtain user details from token", e);
publish(new OAuth2AuthenticationFailureEvent(bad));
throw bad;
}
}
private void publish(ApplicationEvent event) {
if (eventPublisher != null) {
eventPublisher.publishEvent(event);
}
}
}
希望对您有所帮助。