本文主要讲一下如何使用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(); } }
这样就大功告成了。