spring-security-oauth2支持的注解有:
1.EnableOAuth2Client
適用於使用spring security,並且想從Oauth2認證服務器來獲取授權的web應用環境代碼中,它啟用了一個Oauth2 客戶端配置。為了更好的利用這個特性,需要在客戶端應用中的DelegatingFilterProxy(代理一個名為oauth2ClientContextFilter)增加一個servlet filter。當filter配置到client app時,可以使用注解@AccessTokenRequest提供的另一個bean來創建一個Oauth2RequestTemplate。示例:
@Configuration @EnableOAuth2Client public class RemoteResourceConfiguration {@Bean
public OAuth2RestOperations restTemplate(OAuth2ClientContext oauth2ClientContext) {
return new OAuth2RestTemplate(remote(), oauth2ClientContext);
}}
Client App使用client credential授權,不需要AccessTokenRequest或者域內RestOperation(對app來說,狀態是全局的),但在需要時仍然使用filter來觸發OAuth2RestOperation來獲取token。使用密碼授權的app需要在RestOperation動作之前為OAuth2ProtectedResouceDetail設置認證屬性,這就是說,resouce detail 本身也需要session(假設系統中有多個用戶)。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(OAuth2ClientConfiguration.class) public @interface EnableOAuth2Client {}
實現OAuth2ClientConfiguration
@Configuration public class OAuth2ClientConfiguration {@Bean </span><span style="color: #0000ff;">public</span><span style="color: #000000;"> OAuth2ClientContextFilter oauth2ClientContextFilter() { OAuth2ClientContextFilter filter </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> OAuth2ClientContextFilter(); </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> filter; } @Bean @Scope(value </span>= "request", proxyMode =<span style="color: #000000;"> ScopedProxyMode.INTERFACES) </span><span style="color: #0000ff;">protected</span> AccessTokenRequest accessTokenRequest(@Value("#{request.parameterMap}"<span style="color: #000000;">) Map</span><String, String[]> parameters, @Value("#{request.getAttribute('currentUri')}"<span style="color: #000000;">) String currentUri) { DefaultAccessTokenRequest request </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> DefaultAccessTokenRequest(parameters); request.setCurrentUri(currentUri); </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> request; } @Configuration </span><span style="color: #0000ff;">protected</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">class</span><span style="color: #000000;"> OAuth2ClientContextConfiguration { @Resource @Qualifier(</span>"accessTokenRequest"<span style="color: #000000;">) </span><span style="color: #0000ff;">private</span><span style="color: #000000;"> AccessTokenRequest accessTokenRequest; @Bean @Scope(value </span>= "session", proxyMode =<span style="color: #000000;"> ScopedProxyMode.INTERFACES) </span><span style="color: #0000ff;">public</span><span style="color: #000000;"> OAuth2ClientContext oauth2ClientContext() { </span><span style="color: #0000ff;">return</span> <span style="color: #0000ff;">new</span><span style="color: #000000;"> DefaultOAuth2ClientContext(accessTokenRequest); } }
}
2. EnableAuthorizationServer
工具方法,用來在當前應用context里(必須是一個DispatcherServlet context)開啟一個授權server(例如AuthorizationEndpoint)和一個TokenEndpoint。server的多個屬性可以通過自定義AuthorizationServerConfigurer類型(如AuthorizationServerConfigurerAdapter的擴展)的Bean來定制。通過正常使用spring security的特色EnableWebSecurity,用戶負責保證授權Endpoint(/oauth/authorize)的安全,但Token Endpoint(/oauth/token)將自動使用http basic的客戶端憑證來保證安全。通過一個或者多個AuthorizationServerConfigurer提供一個ClientDetailService來注冊client(必須)。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class}) public @interface EnableAuthorizationServer {}
2.1 AuthorizationServerEndpointsConfiguration
private AuthorizationServerEndpointsConfigurer endpoints = new AuthorizationServerEndpointsConfigurer();@Autowired </span><span style="color: #0000ff;">private</span><span style="color: #000000;"> ClientDetailsService clientDetailsService; @Autowired </span><span style="color: #0000ff;">private</span> List<AuthorizationServerConfigurer> configurers =<span style="color: #000000;"> Collections.emptyList(); @PostConstruct </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> init() { </span><span style="color: #0000ff;">for</span><span style="color: #000000;"> (AuthorizationServerConfigurer configurer : configurers) { </span><span style="color: #0000ff;">try</span><span style="color: #000000;"> { configurer.configure(endpoints); } </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (Exception e) { </span><span style="color: #0000ff;">throw</span> <span style="color: #0000ff;">new</span> IllegalStateException("Cannot configure enpdoints"<span style="color: #000000;">, e); } } endpoints.setClientDetailsService(clientDetailsService); }</span></pre>
@Component protected static class TokenKeyEndpointRegistrar implements BeanDefinitionRegistryPostProcessor {</span><span style="color: #0000ff;">private</span><span style="color: #000000;"> BeanDefinitionRegistry registry; @Override </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span> postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) <span style="color: #0000ff;">throws</span><span style="color: #000000;"> BeansException { String[] names </span>=<span style="color: #000000;"> BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, JwtAccessTokenConverter.</span><span style="color: #0000ff;">class</span>, <span style="color: #0000ff;">false</span>, <span style="color: #0000ff;">false</span><span style="color: #000000;">); </span><span style="color: #0000ff;">if</span> (names.length > 0<span style="color: #000000;">) { BeanDefinitionBuilder builder </span>= BeanDefinitionBuilder.rootBeanDefinition(TokenKeyEndpoint.<span style="color: #0000ff;">class</span><span style="color: #000000;">); builder.addConstructorArgReference(names[</span>0<span style="color: #000000;">]); registry.registerBeanDefinition(TokenKeyEndpoint.</span><span style="color: #0000ff;">class</span><span style="color: #000000;">.getName(), builder.getBeanDefinition()); } } @Override </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span> postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) <span style="color: #0000ff;">throws</span><span style="color: #000000;"> BeansException { </span><span style="color: #0000ff;">this</span>.registry =<span style="color: #000000;"> registry; } }</span></pre>
2.2 AuthorizationServerSecurityConfiguration
@Configuration @Order(0) @Import({ ClientDetailsServiceConfiguration.class, AuthorizationServerEndpointsConfiguration.class }) public class AuthorizationServerSecurityConfiguration extends WebSecurityConfigurerAdapter {@Autowired </span><span style="color: #0000ff;">private</span> List<AuthorizationServerConfigurer> configurers =<span style="color: #000000;"> Collections.emptyList(); @Autowired </span><span style="color: #0000ff;">private</span><span style="color: #000000;"> ClientDetailsService clientDetailsService; @Autowired </span><span style="color: #0000ff;">private</span><span style="color: #000000;"> AuthorizationServerEndpointsConfiguration endpoints; @Autowired </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span> configure(ClientDetailsServiceConfigurer clientDetails) <span style="color: #0000ff;">throws</span><span style="color: #000000;"> Exception { </span><span style="color: #0000ff;">for</span><span style="color: #000000;"> (AuthorizationServerConfigurer configurer : configurers) { configurer.configure(clientDetails); } } @Override </span><span style="color: #0000ff;">protected</span> <span style="color: #0000ff;">void</span> configure(AuthenticationManagerBuilder auth) <span style="color: #0000ff;">throws</span><span style="color: #000000;"> Exception { </span><span style="color: #008000;">//</span><span style="color: #008000;"> Over-riding to make sure this.disableLocalConfigureAuthenticationBldr = false </span><span style="color: #008000;">//</span><span style="color: #008000;"> This will ensure that when this configurer builds the AuthenticationManager it will not attempt </span><span style="color: #008000;">//</span><span style="color: #008000;"> to find another 'Global' AuthenticationManager in the ApplicationContext (if available), </span><span style="color: #008000;">//</span><span style="color: #008000;"> and set that as the parent of this 'Local' AuthenticationManager. </span><span style="color: #008000;">//</span><span style="color: #008000;"> This AuthenticationManager should only be wired up with an AuthenticationProvider </span><span style="color: #008000;">//</span><span style="color: #008000;"> composed of the ClientDetailsService (wired in this configuration) for authenticating 'clients' only.</span>
}
@Override </span><span style="color: #0000ff;">protected</span> <span style="color: #0000ff;">void</span> configure(HttpSecurity http) <span style="color: #0000ff;">throws</span><span style="color: #000000;"> Exception { AuthorizationServerSecurityConfigurer configurer </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> AuthorizationServerSecurityConfigurer(); FrameworkEndpointHandlerMapping handlerMapping </span>=<span style="color: #000000;"> endpoints.oauth2EndpointHandlerMapping(); http.setSharedObject(FrameworkEndpointHandlerMapping.</span><span style="color: #0000ff;">class</span><span style="color: #000000;">, handlerMapping); configure(configurer); http.apply(configurer); String tokenEndpointPath </span>= handlerMapping.getServletPath("/oauth/token"<span style="color: #000000;">); String tokenKeyPath </span>= handlerMapping.getServletPath("/oauth/token_key"<span style="color: #000000;">); String checkTokenPath </span>= handlerMapping.getServletPath("/oauth/check_token"<span style="color: #000000;">); </span><span style="color: #0000ff;">if</span> (!<span style="color: #000000;">endpoints.getEndpointsConfigurer().isUserDetailsServiceOverride()) { UserDetailsService userDetailsService </span>= http.getSharedObject(UserDetailsService.<span style="color: #0000ff;">class</span><span style="color: #000000;">); endpoints.getEndpointsConfigurer().userDetailsService(userDetailsService); } </span><span style="color: #008000;">//</span><span style="color: #008000;"> @formatter:off</span>
http
.authorizeRequests()
.antMatchers(tokenEndpointPath).fullyAuthenticated()
.antMatchers(tokenKeyPath).access(configurer.getTokenKeyAccess())
.antMatchers(checkTokenPath).access(configurer.getCheckTokenAccess())
.and()
.requestMatchers()
.antMatchers(tokenEndpointPath, tokenKeyPath, checkTokenPath)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
// @formatter:on
http.setSharedObject(ClientDetailsService.class, clientDetailsService);
}</span><span style="color: #0000ff;">protected</span> <span style="color: #0000ff;">void</span> configure(AuthorizationServerSecurityConfigurer oauthServer) <span style="color: #0000ff;">throws</span><span style="color: #000000;"> Exception { </span><span style="color: #0000ff;">for</span><span style="color: #000000;"> (AuthorizationServerConfigurer configurer : configurers) { configurer.configure(oauthServer); } }
}
3. EnableResourceServer
Oauth2 資源服務器的便利方法,開啟了一個spring security的filter,這個filter通過一個Oauth2的token進行認證請求。使用者應該增加這個注解,並提供一個ResourceServerConfigurer類型的Bean(例如通過ResouceServerConfigurerAdapter)來指定資源(url路徑和資源id)的細節。為了利用這個filter,你必須在你的應用中的某些地方EnableWebSecurity,或者使用這個注解的地方,或者其他別的地方。
這個注解創建了一個WebSecurityConfigurerAdapter,且自帶了硬編碼的order=3.在spring中,由於技術原因不能立即改變order的順序,因此你必須在你的spring應用中避免使用order=3的其他WebSecurityConfigurerAdapter。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(ResourceServerConfiguration.class) public @interface EnableResourceServer {}
ResourceServerConfiguration
@Override protected void configure(HttpSecurity http) throws Exception { ResourceServerSecurityConfigurer resources = new ResourceServerSecurityConfigurer(); ResourceServerTokenServices services = resolveTokenServices(); if (services != null) { resources.tokenServices(services); } else { if (tokenStore != null) { resources.tokenStore(tokenStore); } else if (endpoints != null) { resources.tokenStore(endpoints.getEndpointsConfigurer().getTokenStore()); } } if (eventPublisher != null) { resources.eventPublisher(eventPublisher); } for (ResourceServerConfigurer configurer : configurers) { configurer.configure(resources); } // @formatter:off http.authenticationProvider(new AnonymousAuthenticationProvider("default")) // N.B. exceptionHandling is duplicated in resources.configure() so that // it works .exceptionHandling() .accessDeniedHandler(resources.getAccessDeniedHandler()).and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .csrf().disable(); // @formatter:on http.apply(resources); if (endpoints != null) { // Assume we are in an Authorization Server http.requestMatcher(new NotOAuthRequestMatcher(endpoints.oauth2EndpointHandlerMapping())); } for (ResourceServerConfigurer configurer : configurers) { // Delegates can add authorizeRequests() here configurer.configure(http); } if (configurers.isEmpty()) { // Add anyRequest() last as a fall back. Spring Security would // replace an existing anyRequest() matcher with this one, so to // avoid that we only add it if the user hasn't configured anything. http.authorizeRequests().anyRequest().authenticated(); } }
ResourceServerSecurityConfigurer
重新的兩個方法
1.init
@Override public void init(HttpSecurity http) throws Exception { registerDefaultAuthenticationEntryPoint(http); }@SuppressWarnings(</span>"unchecked"<span style="color: #000000;">) </span><span style="color: #0000ff;">private</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> registerDefaultAuthenticationEntryPoint(HttpSecurity http) { ExceptionHandlingConfigurer</span><HttpSecurity> exceptionHandling =<span style="color: #000000;"> http .getConfigurer(ExceptionHandlingConfigurer.</span><span style="color: #0000ff;">class</span><span style="color: #000000;">); </span><span style="color: #0000ff;">if</span> (exceptionHandling == <span style="color: #0000ff;">null</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">return</span><span style="color: #000000;">; } ContentNegotiationStrategy contentNegotiationStrategy </span>= http.getSharedObject(ContentNegotiationStrategy.<span style="color: #0000ff;">class</span><span style="color: #000000;">); </span><span style="color: #0000ff;">if</span> (contentNegotiationStrategy == <span style="color: #0000ff;">null</span><span style="color: #000000;">) { contentNegotiationStrategy </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> HeaderContentNegotiationStrategy(); } MediaTypeRequestMatcher preferredMatcher </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> MediaTypeRequestMatcher(contentNegotiationStrategy, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_XML, MediaType.MULTIPART_FORM_DATA, MediaType.TEXT_XML); preferredMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL)); exceptionHandling.defaultAuthenticationEntryPointFor(postProcess(authenticationEntryPoint), preferredMatcher); }</span></pre>
2.configure
@Override public void configure(HttpSecurity http) throws Exception {AuthenticationManager oauthAuthenticationManager </span>=<span style="color: #000000;"> oauthAuthenticationManager(http); <span style="color: #ff0000;">resourcesServerFilter </span></span><span style="color: #ff0000;">= new</span><span style="color: #000000;"><span style="color: #ff0000;"> OAuth2AuthenticationProcessingFilter();</span> resourcesServerFilter.setAuthenticationEntryPoint(authenticationEntryPoint); resourcesServerFilter.setAuthenticationManager(oauthAuthenticationManager); </span><span style="color: #0000ff;">if</span> (eventPublisher != <span style="color: #0000ff;">null</span><span style="color: #000000;">) { resourcesServerFilter.setAuthenticationEventPublisher(eventPublisher); } </span><span style="color: #0000ff;">if</span> (tokenExtractor != <span style="color: #0000ff;">null</span><span style="color: #000000;">) { resourcesServerFilter.setTokenExtractor(tokenExtractor); } resourcesServerFilter </span>=<span style="color: #000000;"> postProcess(resourcesServerFilter); resourcesServerFilter.setStateless(stateless); </span><span style="color: #008000;">//</span><span style="color: #008000;"> @formatter:off</span>
http
.authorizeRequests().expressionHandler(expressionHandler)
.and()
.addFilterBefore(resourcesServerFilter, AbstractPreAuthenticatedProcessingFilter.class)
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler)
.authenticationEntryPoint(authenticationEntryPoint);
// @formatter:on
}
其中OAuth2AuthenticationProcessingFilter:A pre-authentication filter for OAuth2 protected resources. Extracts an OAuth2 token from the incoming request and uses it to populate the Spring Security context with an {@link OAuth2Authentication} (if used in conjunction with an{@link OAuth2AuthenticationManager}).