计算机系统应用教程网站

网站首页 > 技术文章 正文

spring-security-oauth2注解原理内幕

btikc 2024-09-18 08:40:39 技术文章 20 ℃ 0 评论

Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

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
 public OAuth2ClientContextFilter oauth2ClientContextFilter() {
 OAuth2ClientContextFilter filter = new OAuth2ClientContextFilter();
 return filter;
 }
 @Bean
 @Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)
 protected AccessTokenRequest accessTokenRequest(@Value("#{request.parameterMap}")
 Map<String, String[]> parameters, @Value("#{request.getAttribute('currentUri')}")
 String currentUri) {
 DefaultAccessTokenRequest request = new DefaultAccessTokenRequest(parameters);
 request.setCurrentUri(currentUri);
 return request;
 }
 
 @Configuration
 protected static class OAuth2ClientContextConfiguration {
 
 @Resource
 @Qualifier("accessTokenRequest")
 private AccessTokenRequest accessTokenRequest;
 
 @Bean
 @Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
 public OAuth2ClientContext oauth2ClientContext() {
 return new 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
 private ClientDetailsService clientDetailsService;
 @Autowired
 private List<AuthorizationServerConfigurer> configurers = Collections.emptyList();
 @PostConstruct
 public void init() {
 for (AuthorizationServerConfigurer configurer : configurers) {
 try {
 configurer.configure(endpoints);
 } catch (Exception e) {
 throw new IllegalStateException("Cannot configure enpdoints", e);
 }
 }
 endpoints.setClientDetailsService(clientDetailsService);
 }
 @Component
 protected static class TokenKeyEndpointRegistrar implements BeanDefinitionRegistryPostProcessor {
 private BeanDefinitionRegistry registry;
 @Override
 public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
 String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory,
 JwtAccessTokenConverter.class, false, false);
 if (names.length > 0) {
 BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(TokenKeyEndpoint.class);
 builder.addConstructorArgReference(names[0]);
 registry.registerBeanDefinition(TokenKeyEndpoint.class.getName(), builder.getBeanDefinition());
 }
 }
 @Override
 public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
 this.registry = registry;
 }
 }

2.2 AuthorizationServerSecurityConfiguration

@Configuration
@Order(0)
@Import({ ClientDetailsServiceConfiguration.class, AuthorizationServerEndpointsConfiguration.class })
public class AuthorizationServerSecurityConfiguration extends WebSecurityConfigurerAdapter {
 @Autowired
 private List<AuthorizationServerConfigurer> configurers = Collections.emptyList();
 @Autowired
 private ClientDetailsService clientDetailsService;
 @Autowired
 private AuthorizationServerEndpointsConfiguration endpoints;
 @Autowired
 public void configure(ClientDetailsServiceConfigurer clientDetails) throws Exception {
 for (AuthorizationServerConfigurer configurer : configurers) {
 configurer.configure(clientDetails);
 }
 }
 @Override
 protected void configure(AuthenticationManagerBuilder auth) throws Exception {
 // Over-riding to make sure this.disableLocalConfigureAuthenticationBldr = false
 // This will ensure that when this configurer builds the AuthenticationManager it will not attempt
 // to find another 'Global' AuthenticationManager in the ApplicationContext (if available),
 // and set that as the parent of this 'Local' AuthenticationManager.
 // This AuthenticationManager should only be wired up with an AuthenticationProvider
 // composed of the ClientDetailsService (wired in this configuration) for authenticating 'clients' only.
 }
 @Override
 protected void configure(HttpSecurity http) throws Exception {
 AuthorizationServerSecurityConfigurer configurer = new AuthorizationServerSecurityConfigurer();
 FrameworkEndpointHandlerMapping handlerMapping = endpoints.oauth2EndpointHandlerMapping();
 http.setSharedObject(FrameworkEndpointHandlerMapping.class, handlerMapping);
 configure(configurer);
 http.apply(configurer);
 String tokenEndpointPath = handlerMapping.getServletPath("/oauth/token");
 String tokenKeyPath = handlerMapping.getServletPath("/oauth/token_key");
 String checkTokenPath = handlerMapping.getServletPath("/oauth/check_token");
 if (!endpoints.getEndpointsConfigurer().isUserDetailsServiceOverride()) {
 UserDetailsService userDetailsService = http.getSharedObject(UserDetailsService.class);
 endpoints.getEndpointsConfigurer().userDetailsService(userDetailsService);
 }
 // @formatter:off
 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);
 }
 protected void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
 for (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("unchecked")
 private void registerDefaultAuthenticationEntryPoint(HttpSecurity http) {
 ExceptionHandlingConfigurer<HttpSecurity> exceptionHandling = http
 .getConfigurer(ExceptionHandlingConfigurer.class);
 if (exceptionHandling == null) {
 return;
 }
 ContentNegotiationStrategy contentNegotiationStrategy = http.getSharedObject(ContentNegotiationStrategy.class);
 if (contentNegotiationStrategy == null) {
 contentNegotiationStrategy = new HeaderContentNegotiationStrategy();
 }
 MediaTypeRequestMatcher preferredMatcher = new 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);
 }

2.configure

@Override
 public void configure(HttpSecurity http) throws Exception {
 AuthenticationManager oauthAuthenticationManager = oauthAuthenticationManager(http);
 resourcesServerFilter = new OAuth2AuthenticationProcessingFilter();
 resourcesServerFilter.setAuthenticationEntryPoint(authenticationEntryPoint);
 resourcesServerFilter.setAuthenticationManager(oauthAuthenticationManager);
 if (eventPublisher != null) {
 resourcesServerFilter.setAuthenticationEventPublisher(eventPublisher);
 }
 if (tokenExtractor != null) {
 resourcesServerFilter.setTokenExtractor(tokenExtractor);
 }
 resourcesServerFilter = postProcess(resourcesServerFilter);
 resourcesServerFilter.setStateless(stateless);
 // @formatter:off
 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}).

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表