本文主要講一下如何使用spring security oauth2做爲一個client來使用前端
OAuth 2.0定義了四種受權方式。java
client爲瀏覽器/前端應用
)用戶密碼暴露給client端不安全
)主要用於api認證,跟用戶無關
)這裏以authorization code模式爲例git
封裝獲取token方法
)對rest template的封裝,爲獲取token等提供便捷方法
DefaultUserInfoRestTemplateFactory實例了OAuth2RestTemplategithub
spring-boot-autoconfigure-1.5.9.RELEASE-sources.jar!/org/springframework/boot/autoconfigure/security/oauth2/resource/DefaultUserInfoRestTemplateFactory.javaspring
/** * 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); } }
這個提供了OAuth2RestTemplateapi
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配置中建立的
這個是給resource server用的,於是client要使用的話,須要本身建立安全
OAuth2ClientAuthenticationProcessingFilter
)spring security oauth2 照樣提供了便利的類可供處理:
spring-security-oauth2-2.0.14.RELEASE-sources.jar!/org/springframework/security/oauth2/client/filter/OAuth2ClientAuthenticationProcessingFilter.javacookie
/** * 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); } //...... }
它的構造器須要傳入defaultFilterProcessesUrl,用於指定這個filter攔截哪一個url。
它依賴OAuth2RestTemplate來獲取token
還依賴ResourceServerTokenServices進行校驗tokensession
通過上面的分析,這個config主要是配置3個
獲取token
)校驗token
)攔截redirectUri,根據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); //設置回調成功的頁面 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服務 * @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; } }
上面定義了OAuth2ClientAuthenticationProcessingFilter,還有最重要的一步,就是配置filter的順序,若是配置不當則前功盡棄。
這裏須要配置在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
有的是說本地開發,auth server與client都是localhost,形成JSESSIONID相互影響問題。能夠經過配置client的context-path或者session名稱來解決
這裏配置了session
server: port: 8081 session: cookie: name: OAUTH2SESSION
不過貌似沒解決,最後先臨時關閉AuthorizationCodeAccessTokenProvider的stateMandatory屬性
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
以後就是登錄,而後受權,而後就成功回調,而後跳轉到設置的/home
回調以後,會將token與當前session綁定,以後利用OAuth2RestTemplate能夠透明訪問受權資源
@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(); } }
這樣就大功告成了。