/spring-cloud-lab6

Implementando e manipulando segurança com o protocolo OAuth2 e JWT

Primary LanguageJava

Laboratório 06

Objetivos

  • Implementando e manipulando segurança com o protocolo OAuth2 e JWT

Tarefas

Implemente um serviço de segurança utilizando o protocolo OAuth2

  • Utilize os projetos definidos no exercício anterior
  • Crie um novo projeto Spring Boot para representar o serviço de segurança security-server
  • Configure o suporte da plataforma Spring Cloud no pom.xml
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Dalston.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
  • Adicione as seguintes dependências no projeto
    • spring-boot-starter-web
    • spring-boot-starter-actuator
    • spring-cloud-starter-config
    • spring-cloud-starter-eureka
  • Adicione também a dependência spring-security-oauth2 no seu projeto
  <dependency>
      <groupId>org.springframework.security.oauth</groupId>
      <artifactId>spring-security-oauth2</artifactId>
  </dependency>
  • Configure o serviço de autorização OAuth2 utilizando a anotação @EnableAuthorizationServer
  @Configuration
  @EnableAuthorizationServer
  public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
      @Autowired
      @Qualifier("authenticationManagerBean")
      AuthenticationManager authenticationManager;

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

      @Override
      public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
          // TODO define the client details
      }

      @Override
      public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
          endpoints.authenticationManager(authenticationManager);
      }
  }
  • Configure os detalhes de segurança (usuário, perfil, etc) para serem utilizados pela aplicação
  @Configuration
  public class SecurityConfig extends WebSecurityConfigurerAdapter {
      @Bean
      @Override
      public AuthenticationManager authenticationManagerBean() throws Exception {
          return super.authenticationManagerBean();
      }

      @Autowired
      public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
          auth.inMemoryAuthentication()
              .withUser("barry").password("t0ps3cr3t").roles("USER").and()
              .withUser("larry").password("t0ps3cr3t").roles("USER", "MANAGER").and()
              .withUser("root").password("t0ps3cr3t").roles("USER", "MANAGER", "ADMIN");
      }      

      @Override
    	public void configure(HttpSecurity http) throws Exception {
          http.csrf().disable()
            	.requestMatchers().antMatchers("/login", "/oauth/authorize").and()
            		.authorizeRequests().anyRequest().authenticated().and()
            		.formLogin().permitAll();
    	}
  }
  • Configure o serviço de recursos OAuth2 utilizando a anotação @EnableResourceServer
  @Configuration
  @EnableResourceServer
  public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

     @Override
     public void configure(HttpSecurity http) throws Exception {
         http.authorizeRequests()
             .antMatchers("/users/ping").permitAll()
             .antMatchers("/users/current").authenticated()
             .anyRequest().authenticated();
     }
  }
  • Implemente também um REST controller para retornar as informações dos usuários
  @RestController
  @RequestMapping("/users")
  public class UserRestController {

      @RequestMapping("/current")
      public Principal current(Principal principal) {
          return principal;
      }

      @RequestMapping("/ping")
      public ResponseEntity<String> ping() {
          return ResponseEntity.ok("ping: " + System.currentTimeMillis());
      }
  }
  • Adicione a configuração do novo serviço de segurança security-server no Config Server
server:
  port: ${PORT:9999}

eureka:
  client:
    serviceUrl:
      defaultZone: ${EUREKA_URI:http://localhost:8761/eureka}
  instance:
    preferIpAddress: true

security:
  basic:
    enabled: false    
  • Não se esqueça de configurar o bootstrap.yml na aplicação para se conectar com o Config Server
spring:
  application:
    name: security-server
  cloud:
    config:
      uri: http://localhost:8888  
  • Adicione também a dependência do spring-cloud-starter-config no projeto
  • Execute e teste a aplicação

Teste o fluxo de geração tokens via protocolo OAuth2

  • Utilize os projetos definidos anteriormente
  • Modifique a configuração do AuthServerConfig no security-server para adicionar suporte ao fluxo de resource owner password
  @Override
  public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
      clients.inMemory().withClient("client")
           .secret("secret")
           .authorizedGrantTypes("password", "client_credentials", "authorization_code", "implicit")
           .scopes("oauth2")
           .autoApprove(true) ;
  }
  • Execute e teste a aplicação
    • Execute a seguinte requisição HTTP POST
      • http://client:secret@localhost:9999/oauth/token?grant_type=password&username=barry&password=t0ps3cr3t
      • Verifique como resultado o OAuth2 access_token retornado via username / password
    • Execute a seguinte requisição HTTP POST
      • http://client:secret@localhost:9999/oauth/token?grant_type=client_credentials
      • Verifique como resultado o OAuth2 access_token retornado via client credentials

Adicione o suporte JWT no serviço de segurança

  • Utilize os projetos definidos no exercício anterior
  • Adicione a dependência do spring-security-jwt no projeto do security-server
  <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-jwt</artifactId>
  </dependency>
  • Configure o suporte ao JWT ao serviço de autorização OAuth2 no projeto definindo por uma classe AuthServerJwtConfig
  @Configuration
  public class AuthServerJwtConfig {
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("123");
        return converter;
    }

  }
  • Modifique a configuração do servidor de autorização OAuth2 para adicionar suporte ao JWT
  @Configuration
  @EnableAuthorizationServer
  public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
      //...
      @Autowired TokenStore tokenStore;      
      @Autowired JwtAccessTokenConverter accessTokenConverter;

      @Override
      public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
          endpoints.tokenStore(tokenStore)
                   .accessTokenConverter(accessTokenConverter)
                   .authenticationManager(authenticationManager);
      }
  }
  • Configure o suporte ao JWT ao serviço de recursos OAuth2 no projeto definindo por uma classe ResourceServerJwtConfig
  @Configuration
  public class ResourceServerJwtConfig {
    @Autowired TokenStore tokenStore;

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore);
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
     }    
  }
  • Modifique a configuração do servidor de recursos OAuth2 para adicionar suporte ao JWT
  @Configuration
  @EnableResourceServer
  public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
      //...
      @Autowired DefaultTokenServices tokenServices;

      @Override
      public void configure(ResourceServerSecurityConfigurer config) {
          config.tokenServices(tokenServices);
      }
  }
  • Execute e teste a aplicação
    • Teste novamente os fluxos de autorização OAuth2 e verifique o JWT sendo utilizado

[OPCIONAL]: Manipule chaves assimétricas com JWT

  • Utilize os projetos definidos anteriormente
  • Gere a chave privada utilizando a ferramenta keytool
  keytool -genkeypair -alias security-server
                      -keyalg RSA
                      -keypass mypass
                      -keystore mykeys.jks
                      -storepass mypass
  keytool -list -rfc --keystore mykeys.jks | openssl x509 -inform pem -pubkey
  • Crie um arquivo public.txt com o conteúdo da chave pública retornada
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgIK2Wt4x2EtDl41C7vfp
OsMquZMyOyteO2RsVeMLF/hXIeYvicKr0SQzVkodHEBCMiGXQDz5prijTq3RHPy2
/5WJBCYq7yHgTLvspMy6sivXN7NdYE7I5pXo/KHk4nz+Fa6P3L8+L90E/3qwf6j3
DKWnAgJFRY8AbSYXt1d5ELiIG1/gEqzC0fZmNhhfrBtxwWXrlpUDT0Kfvf0QVmPR
xxCLXT+tEe1seWGEqeOLL5vXRLqmzZcBe1RZ9kQQm43+a9Qn5icSRnDfTAesQ3Cr
lAWJKl2kcWU1HwJqw+dZRSZ1X4kEXNMyzPdPBbGmU6MHdhpywI7SKZT7mX4BDnUK
eQIDAQAB
-----END PUBLIC KEY-----
  • Adicione as chaves privada e pública geradas no diretório src/main/resources do projeto security-server
    • mykeys.jks
    • public.txt
  • Configure a chave privada no suporte JWT do serviço de autorização OAuth2 definindo pela classe AuthServerJwtConfig
  @Configuration
  public class AuthServerJwtConfig {
      //...   
      @Bean
      public JwtAccessTokenConverter accessTokenConverter() {
          JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
          KeyStoreKeyFactory keyStoreKeyFactory =
              new KeyStoreKeyFactory(new ClassPathResource("mykeys.jks"), "mypass".toCharArray());
          converter.setKeyPair(keyStoreKeyFactory.getKeyPair("security-server"));
          return converter;
      }
  }
  • Configure a chave pública no suporte JWT do serviço de recursos OAuth2 definindo pela classe ResourceServerJwtConfig
  @Configuration
  public class ResourceServerJwtConfig {
      //...         
      public JwtAccessTokenConverter accessTokenConverter() {
          JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
          Resource resource = new ClassPathResource("public.txt");
          String publicKey = null;
          try {
              publicKey = IOUtils.toString(resource.getInputStream());
          } catch (final IOException e) {
              throw new RuntimeException(e);
          }
          converter.setVerifierKey(publicKey);
          return converter;
      }
  }
  • Execute e teste a aplicação
    • Teste novamente os fluxos de autorização OAuth2 e verifique o JWT via chaves assimétricas sendo validado