天天看點

Spring Cloud OAuth2、 Actuator、Docker聯配最佳實踐

應用使用Spring Cloud OAuth2保護之後,牽扯到的配置,關聯的地方是比較多的,下面給出一個我們在生産環境中使用的配置模型,歡迎留言讨論。

Authorization Server接口分析

  1. /oauth/token_key:需要驗證;接口是resource server使用security.oauth2.client.client-id和security.oauth2.client.client-secret作為使用者名和密碼,使用basic發起請求的(參看JwtTokenServicesConfiguration#getKeyFromServer());
  2. /oauth/token:需要驗證;接口自帶驗證資訊
  3. /oauth/check_token:需要驗證;從RemoteTokenServices#getAuthorizationHeader,此接口在請求時,與/oauth/token_key接口類似,也會将security.oauth2.client.client-id和security.oauth2.client.client-secret的配置的值作為賬号和密碼basic協定一起發送給伺服器;

    也就是說,隻要我們的Authorization Server開啟了httpBasic驗證,Resource Server或者OAuth2 Client配置了正确的security.oauth2.client.client-id和security.oauth2.client.client-secret,這三個接口可以與Resource Server和OAuth2 Client正常互動,不會産生授權失敗的問題;

Actuator

Actuator提供的接口分為敏感接口和非敏感接口,敏感接口隻有授權登入之後,才能通路,非敏感接口,可以直接通路,獲得全部,或者部分資訊,擷取部分資訊的接口,授權登入之後,可以獲得全部的資訊。我們想實作的效果是,Actuator的接口不再區分是敏感還是不敏感,無論是在Authorization Server還是Resource Server還是Oauth2 Client上,全部被Spring Security保護起來;并且所有的服務都開啟basic驗證,可以讓其他的元件使用basic協定授權登入,通路這些受保護的接口;

Spring Cloud Security OAuth2 Authorization Server和Actuator

在Authorization Server上,我們想實作的功能是,Authorization Server的接口是預設權限,Actuator被Spring Security 完全保護起來;Authorization Server上的核心配置如下:

[Authorization Server]

/**
 * 
 * @author chenzheyang
 * @since 0.1.0
 *
 */
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

  private static final Logger logger = LoggerFactory.getLogger(OAuth2AuthorizationServerConfiguration.class);

  @Value("${security.oauth2.resource.jwt.private-key}")
  private String privateKey;

  @Value("${security.oauth2.resource.jwt.public-key}")
  private String publicKey;

  @Autowired
  private AuthenticationManager authenticationManager;

  @Autowired
  private DataSource dataSource;

  @Autowired
  private ClientDetailsService clientDetailsService;

  @Bean
  public JwtAccessTokenConverter jwtTokenEnhancer() {
    logger.info("Initializing JWT with public key:\n" + publicKey);
    JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    converter.setSigningKey(privateKey);
    converter.setVerifierKey(publicKey);
    return converter;
  }

  @Bean
  public TokenStore jwtTokenStore() {
    return new JwtTokenStore(jwtTokenEnhancer());
  }

  /**
   *
   * @param oauthServer
   * @throws Exception
   */
  @Override
  public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
    oauthServer.tokenKeyAccess("permitAll()") // permitAll()
        .checkTokenAccess("isAuthenticated()"); // isAuthenticated()
    oauthServer.allowFormAuthenticationForClients();
  }

  /**
   *
   * @param endpoints
   * @throws Exception
   */
  @Override
  public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    DefaultOAuth2RequestFactory requestFactory = new DefaultOAuth2RequestFactory(clientDetailsService);
    // requestFactory.setCheckUserScopes(true);
    endpoints.authenticationManager(authenticationManager).tokenStore(jwtTokenStore())
        .accessTokenConverter(jwtTokenEnhancer());
    endpoints.requestFactory(requestFactory);
  }

  /**
   * client_credentials/password/authorization_code/refresh_token
   */
  @Override
  public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.jdbc(dataSource);
  }

}      

[SecurityConfiguration]

/**
 * 
 * @author chenzheyang
 * @since 0.1.0
 *
 */
@Configuration
@EnableWebSecurity
@Slf4j
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

  @Autowired
  private DataSource dataSource;

  @Value("${security.user.name}")
  private String actuatorUserName = "admin2";

  @Value("${security.user.password}")
  private String actuatorUserPassword = "secret2";

  @Override
  public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers("/favicon.ico");
  }

  @Bean
  @Override
  protected AuthenticationManager authenticationManager() throws Exception {
    JdbcUserDetailsManager jdbcUserDetailsManager = new JdbcUserDetailsManager();
    jdbcUserDetailsManager.setDataSource(dataSource);
    DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
    authenticationProvider.setUserDetailsService(jdbcUserDetailsManager);
    ProviderManager authenticationManager = new ProviderManager(Arrays.asList(authenticationProvider));

    UserDetails actuatorUser = User.withUsername(actuatorUserName).password(actuatorUserPassword)
        .authorities("ACTUATOR").build();
    jdbcUserDetailsManager.deleteUser(actuatorUserName);
    jdbcUserDetailsManager.createUser(actuatorUser);
    if (log.isInfoEnabled()) {
      log.info("created actuator user :{}:{}", actuatorUserName, actuatorUserPassword);
    }
    return authenticationManager;
  }

  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().anyRequest().authenticated().and().httpBasic().and().csrf().disable();
  }
}
      

Spring Cloud Security OAuth2 Resource Server和Actuator

Resource Server上的配置示例如下:

@Configuration
@EnableWebSecurity
@EnableResourceServer
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {

  private static final String RESOURCE_ID = "weather-forecast";

  @Autowired
  private JwtAccessTokenConverter jwtAccessTokenConverter;

  @Bean
  public TokenStore tokenStore() {
    return new JwtTokenStore(jwtAccessTokenConverter);
  }

  @Bean
  public OAuth2RestTemplate restTemplate(UserInfoRestTemplateFactory factory) {
    return factory.getUserInfoRestTemplate();
  }

  @Override
  public void configure(ResourceServerSecurityConfigurer resources) {
    resources.resourceId(RESOURCE_ID).stateless(true);
    resources.tokenStore(tokenStore());
  }

  @Override
  public void configure(HttpSecurity http) throws Exception {
//具體的權限配置規則,再此配置;           http.csrf().disable().authorizeRequests().anyRequest().authenticated().and().httpBasic();
  }

}      

Spring Cloud Security OAuth2 OAuth Client(Zuul Server)和Actuator

Zuul Server 有兩種用法,一種是作為UI伺服器,一種是作為接口伺服器(參看​​Spring Cloud OAuth2 & Zuul​​​);作為UI伺服器,點很多,後門專門開篇部落格描述一下我們的實踐,作為接口伺服器的話;配置如下,所有請求全部允許,鑒權的操作下沉到各個資源伺服器,actuator的接口的權限還是全部需要驗證:

[OAuth2ResourceServerConfig ]

/**
 * 
 * @author chenzhenyang
 *
 */
@Configuration
@EnableResourceServer
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {

  private static final String DEMO_RESOURCE_ID = "zuul-server";

  @Autowired
  private JwtAccessTokenConverter jwtAccessTokenConverter;

  @Bean
  public TokenStore tokenStore() {
    return new JwtTokenStore(jwtAccessTokenConverter);
  }

  @Bean
  public OAuth2RestTemplate loadBalancedRestTemplate(UserInfoRestTemplateFactory factory) {
    return factory.getUserInfoRestTemplate();
  }

  @Override
  public void configure(ResourceServerSecurityConfigurer resources) {
    resources.resourceId(DEMO_RESOURCE_ID).stateless(true);
    resources.tokenStore(tokenStore());
  }

  @Override
  public void configure(HttpSecurity http) throws Exception {
    http.csrf().disable().httpBasic().and().authorizeRequests().anyRequest().permitAll();
  }

}      

[SecurityConfiguration]

/**
 * 
 * @author chenzhenyang
 *
 */
@Configuration
@EnableOAuth2Client
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

  @Bean
  public OAuth2RestTemplate restTemplate(OAuth2ClientContext oauth2ClientContext,
      OAuth2ProtectedResourceDetails details) {
    return new OAuth2RestTemplate(details, oauth2ClientContext);
  }

  /**
   * 角色繼承配置
   * 
   * @return
   */
  @Bean
  public RoleHierarchy roleHierarchy() {
    RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
    roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_DBA ROLE_DBA > ROLE_USER");
    return roleHierarchy;
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {

  }
}
      

别忘了開啟management endpoint的鑒權配置:

management:
  context-path: /actuator
  security:
    enabled: true      

Spring Cloud Security OAuth2 OAuth 、Spring Cloud Admin Server和Actuator

各個執行個體與Spring Cloud Admin互動的原理是,各個執行個體通過eureka.instance.metadata-map.user.name和eureka.instance.metadata-map.user.password将本執行個體可以通路actuator接口的賬戶發送給Spring Cloud Admin Server,Spring Cloud Admin Server在請求各個執行個體的actuator的接口的時候,使用basic協定,将此賬号一塊發送給具體的執行個體;如果我們修改了預設的actuator的context-path的話,還需要配置eureka.instance.metadata-map.context-path,将management.context-path一塊發送給Spring Cloud Admin Server;

與Eureka Server

eureka client與eureka server的互動都是eureka-client 主動發起的,也就是沒有ereka- server 通路eureak-client的接口的時候,也就是不需要在eureka-client上做專門的配置,隻需要在eureka-server上做配置就行了,用戶端配置的defaultZone.service: http://username:password@host…

Spring Cloud Security OAuth2 OAuth、Actuator和Docker

version: '3'
services:
  weather-forecast:
    image: xxx
    hostname: weather-forecast
    ports:
    - 5001:5001/tcp
    restart: always
    networks:
    - global
    healthcheck:
      test:
      - CMD
      - curl
      - -f
      - http://admin:secret@localhost:5001/actuator/health
      interval: 0m30s
      timeout: 10s
      retries: 3      

賬号權限體系

  1. 可以為每個應用的actuator建立一個賬号,主要是有ACTUATOR權限,也可以通過management.security.roles自己指定這個角色的名稱,可以多個應用共用一個賬号;
  2. 剩下的就是業務系統的賬号權限體系,可以根據自己的業務情況,進行設計

注意

  1. 所有的伺服器都要開啟Spring Security Http Basic驗證;Actuator的接口都是通過Http Basic驗證的;

總結