序
本文主要講一下如何使用spring security oauth2作為一個client來使用
四種模式
OAuth 2.0定義了四種授權(quán)方式选酗。
- 授權(quán)碼模式(authorization code)
- 簡化模式(implicit)(
client為瀏覽器/前端應(yīng)用
) - 密碼模式(resource owner password credentials)(
用戶密碼暴露給client端不安全
) - 客戶端模式(client credentials)(
主要用于api認證桃序,跟用戶無關(guān)
)
這里以authorization code模式為例
實現(xiàn)client的主要思路
- 需要新建一個處理redirectUri的controller或者filter進行處理
- 根據(jù)authentication code去請求token
- 獲取token之后將token與用戶綁定
- 之后就可以使用token去獲取授權(quán)的資源
OAuth2RestTemplate(封裝獲取token方法
)
對rest template的封裝交播,為獲取token等提供便捷方法
DefaultUserInfoRestTemplateFactory實例了OAuth2RestTemplate
DefaultUserInfoRestTemplateFactory
spring-boot-autoconfigure-1.5.9.RELEASE-sources.jar!/org/springframework/boot/autoconfigure/security/oauth2/resource/DefaultUserInfoRestTemplateFactory.java
/**
* Factory used to create the {@link OAuth2RestTemplate} used for extracting user info
* during authentication if none is available.
*
* @author Dave Syer
* @author Stephane Nicoll
* @since 1.5.0
*/
public class DefaultUserInfoRestTemplateFactory implements UserInfoRestTemplateFactory {
private static final AuthorizationCodeResourceDetails DEFAULT_RESOURCE_DETAILS;
static {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setClientId("<N/A>");
details.setUserAuthorizationUri("Not a URI because there is no client");
details.setAccessTokenUri("Not a URI because there is no client");
DEFAULT_RESOURCE_DETAILS = details;
}
private final List<UserInfoRestTemplateCustomizer> customizers;
private final OAuth2ProtectedResourceDetails details;
private final OAuth2ClientContext oauth2ClientContext;
private OAuth2RestTemplate oauth2RestTemplate;
public DefaultUserInfoRestTemplateFactory(
ObjectProvider<List<UserInfoRestTemplateCustomizer>> customizers,
ObjectProvider<OAuth2ProtectedResourceDetails> details,
ObjectProvider<OAuth2ClientContext> oauth2ClientContext) {
this.customizers = customizers.getIfAvailable();
this.details = details.getIfAvailable();
this.oauth2ClientContext = oauth2ClientContext.getIfAvailable();
}
@Override
public OAuth2RestTemplate getUserInfoRestTemplate() {
if (this.oauth2RestTemplate == null) {
this.oauth2RestTemplate = createOAuth2RestTemplate(
this.details == null ? DEFAULT_RESOURCE_DETAILS : this.details);
this.oauth2RestTemplate.getInterceptors()
.add(new AcceptJsonRequestInterceptor());
AuthorizationCodeAccessTokenProvider accessTokenProvider = new AuthorizationCodeAccessTokenProvider();
accessTokenProvider.setTokenRequestEnhancer(new AcceptJsonRequestEnhancer());
this.oauth2RestTemplate.setAccessTokenProvider(accessTokenProvider);
if (!CollectionUtils.isEmpty(this.customizers)) {
AnnotationAwareOrderComparator.sort(this.customizers);
for (UserInfoRestTemplateCustomizer customizer : this.customizers) {
customizer.customize(this.oauth2RestTemplate);
}
}
}
return this.oauth2RestTemplate;
}
private OAuth2RestTemplate createOAuth2RestTemplate(
OAuth2ProtectedResourceDetails details) {
if (this.oauth2ClientContext == null) {
return new OAuth2RestTemplate(details);
}
return new OAuth2RestTemplate(details, this.oauth2ClientContext);
}
}
這個提供了OAuth2RestTemplate
ResourceServerTokenServicesConfiguration
spring-boot-autoconfigure-1.5.9.RELEASE-sources.jar!/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfiguration.java
/**
* Configuration for an OAuth2 resource server.
*
* @author Dave Syer
* @author Madhura Bhave
* @author Eddú Meléndez
* @since 1.3.0
*/
@Configuration
@ConditionalOnMissingBean(AuthorizationServerEndpointsConfiguration.class)
public class ResourceServerTokenServicesConfiguration {
@Bean
@ConditionalOnMissingBean
public UserInfoRestTemplateFactory userInfoRestTemplateFactory(
ObjectProvider<List<UserInfoRestTemplateCustomizer>> customizers,
ObjectProvider<OAuth2ProtectedResourceDetails> details,
ObjectProvider<OAuth2ClientContext> oauth2ClientContext) {
return new DefaultUserInfoRestTemplateFactory(customizers, details,
oauth2ClientContext);
}
//......
}
而DefaultUserInfoRestTemplateFactory主要是在ResourceServerTokenServicesConfiguration配置中創(chuàng)建的
這個是給resource server用的,因而client要使用的話啥辨,需要自己創(chuàng)建
redirectUri的處理(OAuth2ClientAuthenticationProcessingFilter
)
spring security oauth2 照樣提供了便利的類可供處理:
spring-security-oauth2-2.0.14.RELEASE-sources.jar!/org/springframework/security/oauth2/client/filter/OAuth2ClientAuthenticationProcessingFilter.java
/**
* An OAuth2 client filter that can be used to acquire an OAuth2 access token from an authorization server, and load an
* authentication object into the SecurityContext
*
* @author Vidya Valmikinathan
*
*/
public class OAuth2ClientAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {
public OAuth2RestOperations restTemplate;
private ResourceServerTokenServices tokenServices;
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new OAuth2AuthenticationDetailsSource();
private ApplicationEventPublisher eventPublisher;
public OAuth2ClientAuthenticationProcessingFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
setAuthenticationManager(new NoopAuthenticationManager());
setAuthenticationDetailsSource(authenticationDetailsSource);
}
//......
}
它的構(gòu)造器需要傳入defaultFilterProcessesUrl是偷,用于指定這個filter攔截哪個url淌实。
它依賴OAuth2RestTemplate來獲取token
還依賴ResourceServerTokenServices進行校驗token
oauth client config
經(jīng)過上面的分析,這個config主要是配置3個
- OAuth2RestTemplate(
獲取token
) - ResourceServerTokenServices(
校驗token
) - OAuth2ClientAuthenticationProcessingFilter(
攔截redirectUri,根據(jù)authentication code獲取token越败,依賴前面兩個對象
)
@Configuration
@EnableOAuth2Client
public class Oauth2ClientConfig {
@Bean
public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext context, OAuth2ProtectedResourceDetails details) {
OAuth2RestTemplate template = new OAuth2RestTemplate(details, context);
AuthorizationCodeAccessTokenProvider authCodeProvider = new AuthorizationCodeAccessTokenProvider();
authCodeProvider.setStateMandatory(false);
AccessTokenProviderChain provider = new AccessTokenProviderChain(
Arrays.asList(authCodeProvider));
template.setAccessTokenProvider(provider);
}
/**
* 注冊處理redirect uri的filter
* @param oauth2RestTemplate
* @param tokenService
* @return
*/
@Bean
public OAuth2ClientAuthenticationProcessingFilter oauth2ClientAuthenticationProcessingFilter(
OAuth2RestTemplate oauth2RestTemplate,
RemoteTokenServices tokenService) {
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(redirectUri);
filter.setRestTemplate(oauth2RestTemplate);
filter.setTokenServices(tokenService);
//設(shè)置回調(diào)成功的頁面
filter.setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler() {
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
this.setDefaultTargetUrl("/home");
super.onAuthenticationSuccess(request, response, authentication);
}
});
return filter;
}
/**
* 注冊check token服務(wù)
* @param details
* @return
*/
@Bean
public RemoteTokenServices tokenService(OAuth2ProtectedResourceDetails details) {
RemoteTokenServices tokenService = new RemoteTokenServices();
tokenService.setCheckTokenEndpointUrl(checkTokenUrl);
tokenService.setClientId(details.getClientId());
tokenService.setClientSecret(details.getClientSecret());
return tokenService;
}
}
security config
上面定義了OAuth2ClientAuthenticationProcessingFilter触幼,還有最重要的一步,就是配置filter的順序究飞,如果配置不當(dāng)則前功盡棄置谦。
這里需要配置在BasicAuthenticationFilter之前
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
OAuth2ClientAuthenticationProcessingFilter oauth2ClientAuthenticationProcessingFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().permitAll()
.and()
.addFilterBefore(oauth2ClientAuthenticationProcessingFilter,BasicAuthenticationFilter.class)
.csrf().disable();
}
}
異常
Possible CSRF detected - state parameter was required but no state could be found
有的是說本地開發(fā)堂鲤,auth server與client都是localhost,造成JSESSIONID相互影響問題媒峡∥疗埽可以通過配置client的context-path或者session名稱來解決
這里配置了session
server:
port: 8081
session:
cookie:
name: OAUTH2SESSION
不過貌似沒解決,最后先臨時關(guān)閉AuthorizationCodeAccessTokenProvider的stateMandatory屬性
client相關(guān)yml配置
security:
oauth2:
client:
clientId: demoApp
clientSecret: demoAppSecret
accessTokenUri: ${TOKEN_URL:http://localhost:8080}/oauth/token
userAuthorizationUri: ${USER_AUTH_URL:http://localhost:8080}/oauth/authorize
pre-established-redirect-uri: http://localhost:8081/callback
驗證
http://localhost:8080/oauth/authorize?response_type=code&client_id=demoApp&redirect_uri=http://localhost:8081/callback
之后就是登陸谅阿,然后授權(quán)半哟,然后就成功回調(diào),然后跳轉(zhuǎn)到設(shè)置的/home
回調(diào)之后签餐,會將token與當(dāng)前session綁定寓涨,之后利用OAuth2RestTemplate可以透明訪問授權(quán)資源
@RequestMapping("")
@RestController
public class DemoController {
@Autowired
OAuth2RestTemplate oAuth2RestTemplate;
@Value("${client.resourceServerUrl}")
String resourceServerUrl;
@GetMapping("/demo/{id}")
public String getDemoAuthResource(@PathVariable Long id){
ResponseEntity<String> responseEntity = oAuth2RestTemplate.getForEntity(resourceServerUrl+"/demo/"+id, String.class);
return responseEntity.getBody();
}
}
這樣就大功告成了。