Access Control List: Protegendo o acesso a seus objetos

O Spring Security é um dos módulos mais usados do Spring e sem dúvida cumpre o seu papel muito bem. Ele é bastante usado para restringirmos o acesso aos métodos e urls da nossa aplicação baseado nos perfis dos usuários do sistema. Uma situação mais complicada pode aparecer quando dois usuários possuem um perfil que permite o acesso a uma mesma url, por exemplo um endereço que exibe o detalhe de uma prova dele em um sistema de provas online. Segue abaixo o exemplo:

  /prova/resolucao/1

E se o outro usuário logado também tentar acessar o mesmo endereço de detalhe? Como você vai negar o acesso? Provavelmente acabaríamos escrevendo um código parecido com o que segue.

	Usuario usuarioLogado = ...;
	ResolucaoProva resolucao = resolucaoProvaDao.carrega(idResolucao);
	if(!resolucao.pertence(usuarioLogado)){
		throw new SemAutorizacaoException("espertinho...");
	}

Essa é uma solução perfeitamente válida. O ponto negativo é que você não vai reaproveitar para os outros sistemas. Além do mais, esse sistema de permissões pode ficar mais complexo com usuários podendo ver detalhes de outros usuários, mas sem possibilidade edição por exemplo. Tudo vai depender do tipo de aplicação que você estiver desenvolvendo.

Para amenizar essa complexidade, o Spring Security tem um módulo chamado de ACL(Access Control List). O objetivo dele é justamente generalizar essa implementação nos dando a possibilidade de aplicar regras de acesso para qualquer tipo de objeto! Essa semana eu e Rafael Rollo utilizamos ele em um projeto dentro da Caelum. O objetivo do post é justamente mostrar o código necessário para deixar tudo funcionando. Respire bastante, porque não é pouco código não.

Vamos começar pela parte mais interessante, pelo menos para mim, que é a de inserir as permissões de acesso para determinado objeto. Dê uma olhada no código abaixo:

	@Autowired
	private Permissoes permissoes;

	...

	@RequestMapping(value = "/finaliza", method = RequestMethod.POST)
	public String finaliza(HttpSession session,@AuthenticationPrincipal 
				Usuario usuarioLogado) {

		ResolucaoFinal resolucaoFinal = resolucaoEmAndamento
				.geraResolucaoFinal();
		resolucaoFinalDao.save(resolucaoFinal);

		//essa classe isola o código de adicionar permissões
		permissoes.adiciona(usuarioLogado,resolucaoFinal,BasePermission.READ);

		return "redirect:/resolucao/finalizada";
	}
	@Service
	public class Permissoes {
		
		@Autowired
		private MutableAclService mutableAclService;	

		public void adiciona(UserDetails user, Object objeto, 
                      Permission... permissoes) {
	        ObjectIdentity identity = new ObjectIdentityImpl(objeto);
	        MutableAcl acl = mutableAclService.createAcl(identity);
	        PrincipalSid principalSid = new PrincipalSid(user.getUsername());
	        for (Permission permissao : permissoes) {
	        	acl.insertAce(acl.getEntries().size(), permissao, principalSid, true);			
			}
	        mutableAclService.updateAcl(acl);	
		}

	}

Vamos focar na classe Permissoes, já que nela está acontecendo a parte que nos interessa. A classe MutableAclService é a nossa porta de entrada para criar, atualizar e ler as permissões de acesso a um objeto. Tanto que nesse pequeno trecho de código usamos dois métodos dela:

  1. createAcl
  2. updateAcl

Vamos analisá-los por partes, começando pelo createAcl. 

		public void adiciona(UserDetails user, Object objeto, 
                        Permission... permissoes) {
	        ObjectIdentity identity = new ObjectIdentityImpl(objeto);
	        MutableAcl acl = mutableAclService.createAcl(identity);
                ...

O createAcl espera como argumento um ObjectIdentity, que é justamente a representação do objeto que vai ter o acesso controlado. O retorno deste método é um objeto do tipo MutableAcl, a abstração para as permissões de um objetoComo você já deve ter notado, o construtor do ObjectIdentity recebe um Object como parâmetro, já que qualquer tipo de objeto pode ser protegido. Um ponto importante é que esse Object deve ter um método chamado getId, cujo retorno será usado como chave estrangeira nas tabelas que serão criadas(ainda vamos comentar sobre isso). Caso sua classe não possua um método chamado getId, você pode usar o outro construtor que recebe além do objeto, um id como argumento.

Para falarmos do updateAcl, precisamos primeiro dar uma olhada no método insertAcl da classe MutableAcl. Através dele é que vamos adicionando permissões para o nosso objeto.

	        MutableAcl acl = mutableAclService.createAcl(identity);
	        PrincipalSid principalSid = new PrincipalSid(user.getUsername());
	        for (Permission permissao : permissoes) {
	           acl.insertAce(acl.getEntries().size(), permissao, principalSid, true);			
		}

Para ser bem sincero, essa assinatura de método é um pouco estranha para mim :/. Vamos começar pelos argumentos que fazem mais sentido, o segundo e o terceiro. O segundo argumento é um objeto cuja classe implementa a interface Permission, que é a abstração de uma permissão dentro do ACL. Ele já tem uma implementação base que é tranquila para a gente usar.

	public class BasePermission extends AbstractPermission {
		public static final Permission READ = new BasePermission(1 << 0, 'R'); // 1
		public static final Permission WRITE = new BasePermission(1 << 1, 'W'); // 2
		public static final Permission CREATE = new BasePermission(1 << 2, 'C'); // 4
		public static final Permission DELETE = new BasePermission(1 << 3, 'D'); // 8
		public static final Permission ADMINISTRATION = new BasePermission(1 << 4, 'A'); // 16

		protected BasePermission(int mask) {
			super(mask);
		}

		protected BasePermission(int mask, char code) {
			super(mask, code);
		}
	}

Caso você esteja questionando o motivo da permissão ser representada por um int, bem vindo ao time :), neste link eles discutem sobre a decisão. O terceiro argumento do método é justamente o dono do objeto, que é representado pela classe PrincipalSid(Principal Security Identity).  Passamos como argumento no construtor dele um identificador desse “dono” que existe dentro do nosso sistema, no meu caso foi o email do usuário.

Pensando de maneira direta, os argumentos da permissão e do dono daquela permissão deveriam ser suficientes para definir a regra de acesso ao objeto, só que ainda temos outros dois. Vou tentar ser breve aqui, já que para mim eles não fazem muito sentido. A classe MutableAcl possui um outro método chamado updateAce, cuja assinatura segue abaixo:

    void updateAce(int aceIndex, Permission permission) throws NotFoundException;

Perceba que o primeiro argumento é um índice que é necessário para localizar a posição da permissão. Como para atualizar precisa do índice, para inserir você também precisar passar qual é a posição que você deseja adicionar a nova permissão. Essa é a razão do primeiro argumento do método insertAce ser um inteiro. O último argumento é um boolean para indicar se você realmente quer dar aquela permissão de acesso ao objeto. O que eu não entendo é: se você não quer dar permissão de acesso, não é melhor simplesmente não adicionar? Sei lá, devo estar me passando em alguma coisa aqui.

Com as permissões inseridas no objeto do tipo MutableAcl, podemos invocar o updateAcl da classe MutableAclService, para que tudo seja refletido no banco de dados.

Uma vez que as permissões estão associadas, você deve estar se perguntando como vamos proteger o acesso ao método. Dê uma olhada no código abaixo.

	@RequestMapping(value = "/prova/resolucao/{id}", method = RequestMethod.POST)
	@PreAuthorize("hasPermission(#id,'br.com.caelum.provas.models.ResolucaoFinal','read')")
	public String visualiza(@PathVariable("id") Integer id) {
		ResolucaoFinal resolucao = resolucaoDao.carrega(id);
		...
	}

Aqui fizemos uso das expressões suportadas pelo Spring. Por sinal esse é um tema que merece um post aqui no blog :). Temos acesso a função hasPermission que recebe três argumentos.

  1. O id objeto que está tentando ser acessado. Repare que usamos o #” para indicar que ele deve pegar esse valor do argumento de mesmo nome.
  2. A classe do objeto que ele precisa carregar.
  3. O nome da permissão que o usuário logado precisa ter. Esse nome precisa ser o mesmo da constante usada na hora de adicionar a permissão. Por default o ACL vai fazer o carregamento da permissão baseado nessa condição.

O Spring Security ACL vai verificar se o usuário logado tem acesso especificamente a este objeto. Essa é uma solução super genérica e que você pode aproveitar para qualquer projeto seu!

Para fechar, precisamos realizar as configurações necessárias para que tudo funcione. Essa parte toda eu vou deixar isolada em uma classe específica.

	@Configuration
	public class ConfiguracaoACL {

		@Autowired
		private DataSource dataSource;

		@Bean
		public AclAuthorizationStrategy aclAuthorization() {
			AclAuthorizationStrategyImpl aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(
					new SimpleGrantedAuthority("ROLE_ADMIN"));				
			return aclAuthorizationStrategy;
		}

		@Bean
		public PermissionGrantingStrategy permissionGrantingStrategy() {
			PermissionGrantingStrategy grantingStrategy = new DefaultPermissionGrantingStrategy(
					new ConsoleAuditLogger());
			return grantingStrategy;
		}

		@Bean	
		public Ehcache ehCache() {
			CacheManager cacheManager = CacheManager.create();
			if (!cacheManager.cacheExists("aclCache")) {
				cacheManager.addCache("aclCache");
			}

			Ehcache ehCache = cacheManager.getEhcache("aclCache");
			return ehCache;
		}
			

		@Bean
		public AclCache aclCache(Ehcache ehCache, PermissionGrantingStrategy grantingStrategy,
				AclAuthorizationStrategy aclAuthorizationStrategy) {
			AclCache aclCache = new EhCacheBasedAclCache(ehCache, grantingStrategy,
					aclAuthorizationStrategy);

			return aclCache;
		}

		@Bean
		public BasicLookupStrategy basicLookupStrategy(AclCache aclCache,
				AclAuthorizationStrategy aclAuthorizationStrategy,
				PermissionGrantingStrategy grantingStrategy) {

			return new BasicLookupStrategy(dataSource, aclCache, aclAuthorizationStrategy,
					grantingStrategy);
		}

		@Bean
		public MutableAclService mutableAclService(BasicLookupStrategy lookupStrategy, AclCache aclCache) {
			JdbcMutableAclService service = new JdbcMutableAclService(dataSource, lookupStrategy,
					aclCache);
			service.setClassIdentityQuery("select last_insert_id()");
			service.setSidIdentityQuery("select last_insert_id()");
			return service;
		}

		@Bean
		public MethodSecurityExpressionHandler expressionHandler(PermissionEvaluator permissionEvaluator) {
			DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
			handler.setPermissionEvaluator(permissionEvaluator);
			return handler;
		}

		@Bean
		public PermissionEvaluator permissionEvaluator(MutableAclService aclService) {
			AclPermissionEvaluator aclPermissionEvaluator = new AclPermissionEvaluator(aclService);
			return aclPermissionEvaluator;
		}

	}

Aqui você precisa medir seus esforços. Não se pode negar que cada um desses métodos produz um objeto que é necessário para o funcionamento, mas não acho saudável gastar vários parágrafos explicando um por um. Vou deixar uma lista apenas com uma breve ideia :).

  1. LookupStrategy: dependência do JdbcMutableAclService para fazer buscas no banco pelas permissões.
  2. AclCache: Camada de cache para não ficar indo no banco o tempo todo.
  3. AclAuthorizationStrategy: Verifica se certa role pode alterar permissões de um objeto.
  4. PermissionGrantingStrategy: Verifica se certo usuário pode realizar uma operação sobre o objeto.
  5. PermissionEvalutaor: Necessária para suportar o hasPermission nos métodos.

Como é de praxe, aconselho que você olhe o Javadoc de todas elas, para saber exatamente do papel de cada uma. Aproveitando, já vou fazer uma atualização no SetupMyProject para suportar a configuração do ACL :). Para completar é necessário criar as tabelas necessárias para que o módulo do ACL funcione e, além disso, modificar sua classe de configuração do Spring Security e adicionar a annotation @EnableGlobalMethodSecurity(prePostEnabled=true). Essa annotation habilita a checagem da annotation @PreAuthorize :).

Para você não esquecer, também deixo as dependências que precisam ser adicionadas no pom.xml.

		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-acl</artifactId>
		</dependency>
		<!-- cache -->

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-cache</artifactId>
		</dependency>

		<dependency>
			<groupId>net.sf.ehcache</groupId>
			<artifactId>ehcache</artifactId>
		</dependency>

Pronto! O post foi um pouco longo, mas também o uso e configuração dele não é lá uma das coisas mais fáceis. E esse foi só o primeiro deste ano, se preparem para mais. Também não deixem de mandar sugestões para temas, por aqui ou me referenciando no twitter.

Advertisements

Configurações automáticas do Spring Boot

Uma das mágicas do Spring Boot é que colocamos uma nova dependência no nosso projeto e magicamente tudo ou quase tudo já está configurado. A pergunta que fica é: como essa configuração é feita? Aí entra as classes denominadas de AutoConfigurations do Spring Boot.

Vamos dar uma olhada na classe abaixo:

	@Configuration
	@ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class,
			EnableTransactionManagement.class, EntityManager.class })
	@Conditional(HibernateEntityManagerCondition.class)
	@AutoConfigureAfter({ DataSourceAutoConfiguration.class })
	public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration {

		private static final Log logger = LogFactory
				.getLog(HibernateJpaAutoConfiguration.class);

		private static final String JTA_PLATFORM = "hibernate.transaction.jta.platform";

		/**
		 * {@code NoJtaPlatform} implementations for various Hibernate versions.
		 */
		private static final String NO_JTA_PLATFORM_CLASSES[] = {
				"org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform",
				"org.hibernate.service.jta.platform.internal.NoJtaPlatform" };

		/**
		 * {@code WebSphereExtendedJtaPlatform} implementations for various Hibernate
		 * versions.
		 */
		private static final String WEBSHERE_JTA_PLATFORM_CLASSES[] = {
				"org.hibernate.engine.transaction.jta.platform.internal.WebSphereExtendedJtaPlatform",
				"org.hibernate.service.jta.platform.internal.WebSphereExtendedJtaPlatform", };

		@Autowired
		private JpaProperties properties;

		@Autowired
		private DataSource dataSource;

		@Override
		protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
			return new HibernateJpaVendorAdapter();
		}
              
                ...
     }

Perceba que várias annotations foram usadas em cima da classe HibernateJpaAutoConfiguration, vamos analisar cada uma delas para que tudo fique mais claro. A mais comum é @Configuration, que é utilizada quando queremos indicar que uma classe contém métodos que produzem beans. Caso fique mais curioso, vá na classe mãe e veja os métodos anotados com @Bean. 

Uma outra interessante é a @ConditionalOnClass, que como o próprio nome diz, é utilizada para verificar se determinada classe está no classpath do projeto. Faz até bastante sentido, já que se não tiver tais classes não faz sentido configurar nada da JPA. Além dessa annotation uma outra importante é a @Conditional. Ela recebe como argumento uma classe que implementa a interface Condition, do próprio Spring, e que é utilizada para executar algum código baseada em alguma condição. Vamos dar uma olhada na classe HibernateEntityManagerCondition.

	//classe interna
	static class HibernateEntityManagerCondition extends SpringBootCondition {

		private static String[] CLASS_NAMES = {
				"org.hibernate.ejb.HibernateEntityManager",
				"org.hibernate.jpa.HibernateEntityManager" };

		@Override
		public ConditionOutcome getMatchOutcome(ConditionContext context,
				AnnotatedTypeMetadata metadata) {
			for (String className : CLASS_NAMES) {
				if (ClassUtils.isPresent(className, context.getClassLoader())) {
					return ConditionOutcome.match("found HibernateEntityManager class");
				}
			}
			return ConditionOutcome.noMatch("did not find HibernateEntityManager class");
		}
	}

Simplesmente ela verifica se a implementação do EntityManager, provida pelo Hibernate, está no classpath. Ao invés de implementar a interface Condition diretamente, ela herda da interface SpringBootCondition. A ideia é que caso a condição não seja atendida um log mais claro seja escrito, para que o programador possa ter uma visão mais clara de como resolver o problema. Apenas para matar a curiosidade, vamos dar uma olhada dentro da annotation @ConditionalOnClass. 

	@Conditional(OnClassCondition.class)
	public @interface ConditionalOnClass {

		/**
		 * The classes that must be present. Since this annotation parsed by loading class
		 * bytecode it is safe to specify classes here that may ultimately not be on the
		 * classpath.
		 * @return the classes that must be present
		 */
		Class<?>[] value() default {};

		/**
		 * The classes names that must be present.
		 * @return the class names that must be present.
		 */
		String[] name() default {};

	}

Ela é só um atalho para uma @Condition que usa uma implementação pronta para checar se uma classe existe ou não no classpath. Isso é muito usado no Spring, annotations representam a composição de várias outras para que a configuração fique mais fácil para os programadores.

Para fechar a análise das annotations, faltou apenas darmos uma olhada na @AutoConfigureAfter. Como o próprio nome informa, ele diz para o Spring Boot que a classe HibernateJpaAutoConfiguration deve ser usada para criar as configurações apenas depois que a classe DataSourceAutoConfiguration for executada.

Para não passar desapercebido, dê também uma olhada no fonte da classe para você ver que realmente a maioria dos métodos que a gente se acostumou a configurar para ter o Hibernate/JPA funcionando, já está escrito dentro da AutoConfiguration provida pelo Spring Boot.

Esse foi um post que passeou um pouco mais por dentro do Spring Boot, algo que não afeta diretamente no seu dia a dia de desenvolvimento. De todo jeito, quando mais você souber do que acontece por dentro das tecnologias, mais preparado você fica para lidar com algum problema que possa aparecer.

Um pouco de gambi não faz mal a ninguém: Spring em ambientes não gerenciados

Outro dia, na Caelum, passamos por uma situação interessante. Dois colegas de trabalho, Lucas e Marcio, estavam programando no projeto de final de curso deles e tinha a seguinte situação: o projeto é todo baseado no Spring, mas mesmo assim eles decidiram realizar o deploy em um servidor de aplicação, no caso o Wildfly. É uma decisão interessante, ao invés de optar por um caminho ou outro, eles juntaram as forças das duas plataformas. Vai ser o tema do próximo post deste blog :).

E a decisão valeu a pena! Eles precisavam lidar com WebSockets e a especificação no JAVA EE 7 deixa muito simples implementar qualquer funcionalidade baseada  nesse protocolo. O problema é que agora eles tem uma classe que é gerenciada pelo servidor de aplicação, mas precisam acessar objetos que estão gerenciados pelo Spring. O que fazer? Os container não se falam e, nessa situação, só nos restou partir para um workaround(gambi).  Dê uma olhada no esboço da classe deles:

	@ServerEndpoint("/channel/sales")
	public class AgileBoardEndpoint {
		
		@Inject
		private ConnectedUsers connectedUsers;
		//dao gerenciado pelo Spring
		@AutoWired
		private TaskDao tasks;
		
		@OnOpen
		public void onNewUser(Session session){
			connectedUsers.add(session);
		}
		
		@OnMessage
		public void onMessage(String message) {
			tasks.save(new Task(message));
			connectedUser.alert(task);
		}
	}

Não adianta eles anotarem nenhum atributo com AutoWired, já que, como dissemos, o objeto desta classe não vai ser instanciado pelo Spring. Para resolver tal situação é necessário que você acesse o objeto que representa uma parte do container do Spring de maneira programática.

	@ServerEndpoint("/channel/sales")
	public class AgileBoardEndpoint {
		
		@Inject
		private ConnectedUsers connectedUsers;
		
		@OnOpen
		public void onNewUser(Session session){
			connectedUsers.add(session);
		}
		
		@OnMessage
		public void onMessage(String message) {
                        ApplicationContext ctx = ApplicationContextHolder.getInstance();
			TaskDao tasks = ctx.getBean(TaskDao.class);
			tasks.save(new Task(message));
			connectedUser.alert(task);
		}
	}

A classe ApplicationContext possui o método getBean, que te permite fazer o lookup de um objeto pela sua classe. A instância retornada foi criada exatamente da mesma forma que seria se você tivesse pedido a injeção pelo atributo usando o AutoWired. A pergunta que fica é: como vamos obter uma instância do objeto do tipo ApplicationContext?  É justamente que entra nossa solução de contorno(workaround).

	@Component
	public class ApplicationContextHolder implements ApplicationContextAware{
		
		private static ApplicationContext instance;

		@Override
		public void setApplicationContext(ApplicationContext applicationContext)
				throws BeansException {
			ApplicationContextHolder.instance = applicationContext;
		}

		public static ApplicationContext getInstance(){
	        return instance;
		}


	}

Implementamos a interface ApplicationContextAware quando precisamos ser notificados da criação do ApplicationContext. É parecido com um desses listeners que implementamos quando queremos ser notificados, por exemplo, sobre o inicio do nosso servlet container. Como, em geral, todas as classes ficam gerenciadas sob o mesmo contexto, podemos guardar a instância passada em um atributo estático.

O benefício dessa solução é que conseguimos acessar o contexto do Spring de qualquer lugar, foi justamente o que fizemos a partir da classe que lidava com os WebSockets do JAVA EE. É um bom truque para integrar duas pontas que não se falam muito. O malefício, é exatamente o mesmo :). Você pode acessar de qualquer lugar, tem que tomar cuidado para não sair usando este poder onde não é necessário e acabar prejudicando a manutenção do código. Outro ponto ruim é que você acabou de deixar seu código mais difícil de testar.

Este tipo de decisão acontece todos os dias enquanto estamos desenvolvendo. O importante é saber o que ganhamos e perdemos, porque assim vamos por um caminho baseado em um julgamento crítico, sabendo exatamente as consequências.  No mundo dos sonhos, o Spring vai implementar a especificação do CDI e vai ficar tudo bem para a gente. Lembre, saber tirar proveito da tecnologia que você usa, faz muita diferença no produto final que será entregue.

Deployando sua aplicação com o Spring Boot

Já escrevi aqui no blog, duas vezes, o quanto o Spring Boot pode nos ajudar no desenvolvimento das aplicações. Levou ao extremo o termo convenção sobre configuração e realmente deixou tudo muito mais simples. Um ponto que eu ainda não comentei foi sobre o processo de deploy, se você tiver escolhido o JSP como tecnologia de view. Caso tenha escolhido outras, como o ascendente Thymeleaf, o processo de deploy é o mais simples possível, já que é só gerar o jar e rodar. Em outro post eu comento porque eu escolhi não trocar de tecnologia de view.

O processo até é bem documentado no site, mas acho que alguns pontos merecem um reforço. O primeiro é justamente o fato de termos escolhido JSP como view. O Spring Boot configura o Tomcat para sempre procurar as páginas a partir do caminho src/main/webapp, relativo ao ponto de execução do seu jar. Só que esses arquivos não são copiados quando você executa um mvn clean install, ou seja, você precisa colocar no seu script de deploy um passo para copiar a estrutura e jogar no mesmo local do seu jar executável. Caso você seja curioso, dê uma olhada nesse commit e veja os valores fixos na classe.  A própria documentação sugere que você não deve usar o jar executável quando estiver com jsps.

Para contornar esse problema e deixar, pelo menos para mim, tudo um pouco mais simples, podemos recorrer a um outro plugin do maven que também roda um tomcat embedded, só que referenciando um war.  O webapp-runner gera para você um jar que sobe o tomcat e permite que você passe como argumento o caminho para um arquivo war.

java -jar dependency/webapp-runner.jar tudosobreesporte-1.0.0-SNAPSHOT.war

Para usá-lo, basta que você adicione a seguinte entrada no seu pom.xml. 

	<plugin>
		<groupId>org.apache.maven.plugins</groupId>
		<artifactId>maven-dependency-plugin</artifactId>
		<executions>
			<execution>
				<phase>package</phase>
				<goals>
					<goal>copy</goal>
				</goals>
				<configuration>
					<artifactItems>
						<artifactItem>
							<groupId>com.github.jsimone</groupId>
							<artifactId>webapp-runner</artifactId>
							<version>7.0.57.2</version>
							<destFileName>webapp-   runner.jar</destFileName>
						</artifactItem>
					</artifactItems>
				</configuration>
			</execution>
		</executions>
	</plugin>

Além disso você precisa fazer só mais algumas alterações no seu pom.xml. Quando o SetupMyProject gera o seu projeto para o Spring Boot, o tipo de projeto é jar, justamente porque é o que precisa para você subir uma aplicação com o Spring Boot em dev. Você precisa trocar o tipo para war.

  <packaging>war</packaging>

Outro detalhe importante é que precisamos apagar o plugin do Spring Boot que gera um fat jar quando executamos um mvn install. 

        <!-- apague esse trecho -->
	<plugin>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-maven-plugin</artifactId>
	</plugin>

Só que agora surgiu um possível problema. Quando gerávamos o jar com tudo dentro, era também incorporado uma classe específica do Spring Boot que usava a classe do nosso projeto que possuía o método main, geralmente aquela que chamamos de Boot,  para conseguir dar o start no nosso projeto. Isso é tão verdade que se você tiver mais de uma classe no projeto com o método main, dá erro de execução quando vamos rodar o mvn install com o plugin do bootAgora que temos um war, que vai ser carregado normalmente pelo Servlet Container, precisamos voltar a estratégia padrão do Spring MVC, ou seja, é necessário ter uma classe que implemente a interface WebApplicationInitializer. 

Para a nossa sorte o Spring Boot já possui uma implementação dessa interface justamente pensando nesse estilo de deploy :). Basta que a sua classe que tem o main e que, geralmente, também tem a annotation SpringBootApplication, herde de SpringBootServletInitializer.  Não teria problema alguma se você optasse por criar uma classe separada e herdasse também, fiz direto na classe Boot apenas pela facilidade.

  public class Boot extends SpringBootServletInitializer {
    ...
  }

Para fechar, precisamos indicar para a classe SpringBootServletInitializer quais as nossas classes responsáveis pela configuração do nosso projeto.

	@Override
	protected SpringApplicationBuilder configure(
			SpringApplicationBuilder application) {
		return application.sources(Boot.class);
	}

Repare que eu passei como argumento a própria classe anotada com SpringBootApplication, já que essa annotation já define que todas as classes, a partir do pacote onde ela está declarada, devem ser scaneadas.

Agora que já ajustamos o código precisamos só prestar atenção em último detalhe. O tempo inteiro numa aplicação com o Spring Boot usamos o arquivo application.properties. Só que quando vamos para produção, em geral vamos ter outras configurações. Alguns exemplos:

  1. Nome e dados de acesso do banco de dados
  2. Caminhos para escrita de arquivos

Essa ideia de environment já é bem suportada pelo Spring e o Spring Boot tira apenas proveito. Na hora que você passe o argumento -Dspring.profiles.active atribuindo o valor de ambiente que você quer.

  java -Dspring.profiles.active=production -jar webapp-runner.jar tudosobreesporte-1.0.0-SNAPSHOT.war& 

No exemplo acima você está ensinando ao Spring Boot a procurar por um arquivo chamado application-production.properties :).

É importante ressaltar que você pode continuar, em dev, executando a classe Boot. A única diferença é que em ambiente de produção você agora roda um war convencional.

O post de hoje teve mais o objetivo de centralizar algumas informações importantes sobre deploy de uma aplicação usando o Spring Boot. Como já comentei, o mundo do Spring é gigante e, se tiver algum assunto que você esteja mais interessado, não deixe de comentar ;).

 

Primeiros passos com o Spring Integration

O Spring Integration é o módulo do Spring dedicado a implementação dos patterns de integração entre sistemas. De longe não é um projeto trivial, até porque quando o tema é integração, o assunto nunca é simples. O objetivo deste post é dar uma visão inicial e já usar um exemplo que foi necessário no meu último projeto.

Precisava fazer um agregador de noticias, já com alguns canais selecionados e focados num determinado público. A ideia é puxar vários feed relacionados a um tema, transformar os dados para objetos do meu domínio e gravar no meu banco de dados. Pode perceber que acabei de descrever um fluxo para você:

  1. Tenho um canal de entrada
  2. Manipulo os dados para deixar do jeito que eu preciso
  3. Jogo para um canal de saída

Esse fluxo poderia ser bem mais complicado, vamos pensar em outro cenário:

  1. Ficar buscando dados de uma tabela de outro sistema, já que ele não expõe via serviço.
  2. Criar vários objetos do seu domínio representando o conjunto de novas informações que foram buscadas
  3. Realizar essa atividade a cada 1 hora
  4. Enviar mensagens através do sistema de mensageria contendo esses novos objetos
  5. Caso dê algum problema, grava num arquivo e tenta fazer de novo em 30 minutos

Tudo vai depender do tipo de aplicação que estamos trabalhando. Dois projetos bem famosos para ajudar neste tipo de cenário são o Apache Camel e o Spring Integration. Baseado no nome do blog, já dá para saber que vamos trabalhar com o último.

O projeto foi criado usando o Spring Boot. Para quem ainda não conhece, é só dar uma olhada em outro post aqui do blog. Além disso, para criar o projeto, você pode usar o SetupMyProject. Com tudo pronto, nosso primeiro trabalho é adicionar as dependências necessárias.

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-integration</artifactId>
		<scope>compile</scope>
	</dependency>
	<dependency>
		<groupId>org.springframework.integration</groupId>
		<artifactId>spring-integration-feed</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springframework.integration</groupId>
		<artifactId>spring-integration-redis</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springframework.integration</groupId>
		<artifactId>spring-integration-java-dsl</artifactId>
		<version>1.1.0.RELEASE</version>
	</dependency>

Agora vamos montar nosso fluxo. Você tem duas opções, ou usa a DSL que eles criaram ou pode optar por fazer tal configuração em um arquivo xml. Em geral sou adepto sempre da configuração programática, mas dessa vez optei por começar via xml porque, inicialmente, achei mais simples.

	<?xml version="1.0" encoding="UTF-8"?>
	<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration" xmlns:int-feed="http://www.springframework.org/schema/integration/feed" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd http://www.springframework.org/schema/integration/feed http://www.springframework.org/schema/integration/feed/spring-integration-feed.xsd">

	<int-feed:inbound-channel-adapter id="feedAdapter" channel="inputFeedChannel" auto-startup="true" metadata-store="metadataStore" url="url">
		<int:poller fixed-rate="10000" max-messages-per-poll="10" />
	</int-feed:inbound-channel-adapter>

	<int:channel id="inputFeedChannel">
		<int:queue capacity="100000"/>
	</int:channel>

	<int:poller default="true" fixed-rate="10000"/>	

	<int:transformer input-channel="inputFeedChannel" ref="feedToDownloadedContentTransformer" output-channel="storeFeedContentChannel"/>

	<int:outbound-channel-adapter id="storeFeedContentChannel" ref="storeFeedContentEndpoint" method="handle"/>	    	
		
	</beans>

Vamos começar analisando de baixo para cima. A última configuração, feita pela tag outbound-channel-adapter, indica o ponto final do meu fluxo, ela simplesmente aponta para uma classe minha que vai receber o objeto pronto para eu salvar no banco. Perceba que inclusive eu falo qual método deve ser chamado.

package br.com.ideiasaleatorias.tudosobreesporte.integration;

...

@MessageEndpoint
public class StoreFeedContentEndpoint {

	@Autowired
	private DownloadedContentDao downloads;
	private static Logger logger = LoggerFactory
			.getLogger(StoreFeedContentEndpoint.class);

	public void handle(Message<DownloadedContent> message) {
		DownloadedContent payload = message.getPayload();

		try {
				downloads.save(payload);
		} catch (Exception e) {
			logger.error("Não foi possivel gravar a informacao do {}",payload.getLink());
		}

	}
}

Só que precisamos de alguém que seja capaz de pegar uma entrada do feed e transformar para o objeto do meu domínio. Esse é o objetivo da tag transformer. Perceba que foi usado dois atributos: input-channel e o output-channel, justamente para ele puxar informação de um lugar e jogar para outro. Além disso, precisamos configurar um intervalo para que o transformer fique verificando o canal de entrada, aí entra a tag poller. Configuramos que o comportamento default para a realização do polling é de dez em dez segundos. Inclusive você pode configurar de outras formas,  por exemplo com expressões estilo crontabA tag ref faz referencia a um bean configurado dentro da aplicação.

	@Component
	public class FeedToDownloadedContentTransformer {

		@Transformer
		public DownloadedContent transform(SyndEntry payload){
			return DowloadedContentFactory.createFromPayload(payload);
		}
	}

Perceba que anoto o método com @Transformer para que o Spring Integration saiba qual método deve ser invocado. O tipo do argumento depende da fonte que você está puxando a informação, no meu caso, como é um feed, o Spring Integration já fornece a interface SyndEntry.  Para quase fechar, é importante especificar a fonte de dados. É para isso que serve a parte inicial da configuração.

	<int-feed:inbound-channel-adapter id="feedAdapter" channel="inputFeedChannel" auto-startup="true" metadata-store="metadataStore" url="url">
		<int:poller fixed-rate="10000" max-messages-per-poll="10" />
	</int-feed:inbound-channel-adapter>

	<int:channel id="inputFeedChannel">
		<int:queue capacity="100000"/>
	</int:channel>

A tag inbound-channel-adapter, do namespace referenciado pelo prefixo int-feed, é a responsável por fazer a busca do feed em si. Informamos uma url, de onde ela deve baixar as novas noticias e indicamos um local onde deve ser guardado o conteúdo baixado, até que o transformer comece a consumir. Isso é feito através do atributo channel.  Ele é o primeiro consumidor do conteúdo, tanto que o mesmo também é referenciado pelo transformer. Existem várias implementações para um channel, todas elas implementam a interface chamada MessageChannel. No nosso caso, usamos uma QueueChannel, pois ela pode ser associada com um pooler para consumir o que chega de tempos em tempos.

Ainda em relação a configuração acima, pode ser que você tenha ficado curioso em relação ao atributo metadata-store. O problema é que não queremos ficar baixando feeds que já foram gravados, então podemos passar para ele uma implementação da interface MetadataStore, que já fica responsável por controlar essa funcionalidade. A interface é bem simples, basicamente segue a ideia de um Map. Para não ter que implementar e também para manter os dados que já foram baixados entre os restarts do servidor, eu escolhi a implementação baseado no redis. Basta que você faça o download do projeto e suba o servidor dele. Aí é só fornecer o bean para o Spring Integration.

	@Bean
	public JedisConnectionFactory redisFactory() {
		JedisConnectionFactory jcf = new JedisConnectionFactory();
		jcf.afterPropertiesSet();
		return jcf;
	}

	@Bean
	public RedisMetadataStore metadataStore(JedisConnectionFactory factory) {
		return new RedisMetadataStore(factory);
	}

Para quem ficou curioso em relação a configuração programática, abaixo segue o exemplo de como ficaria para o mesmo exemplo.

	@Bean
	public IntegrationFlow feedFlow(MetadataStore metadataStore) throws MalformedURLException {
		URL feedUrl = new URL("enderecoDoFeed");
		return IntegrationFlows
				.from(s -> s.feed(feedUrl, "MetaData").metadataStore(metadataStore),
						e -> e.poller(p -> p.fixedDelay(10,TimeUnit.SECONDS)))				
				.channel(c -> c.queue("feedEntries"))				
				.transform(new FeedToDownloadedContentTransformer())				
				.handle("storeFeedContentEndpoint","handle")
				.get();
	}

Esse para mim é um uso meio exagerado dos lambdas, então realmente eu ainda prefiro ficar com o xml.

O nosso exemplo foi baseado em consumo de feed, mas existem várias opções. A entrada podia ser uma fila do seu servidor de mensageria, um banco de dados, um serviço baseado em HTTP etc. Para a saída você pode considerar as mesmas possibilidades. Além disso, aqui implementamos alguns passos com nossas próprias classes, muitas vezes você pode usar as configurações do próprio Spring Integration para fazer todos os passos!

Caso sua aplicação necessite desses tipos de fluxos, o Spring Integration é uma ótima opção. Ele não é tão simples de começar, por isso que é importante você associar o aprendizado dele com o estudo dos patterns de integração. Lembre que ele é só a ferramenta, sem a teoria a tendência é que tudo mais complicado.

Prefira as tags de formulário do Spring MVC!

Uma das coisas que atrasa o desenvolvimento de um projeto é quando o programador resolve investir tempo para construir uma parte do software que já foi pensada por uma biblioteca ou framework. Quando pensamos no Spring MVC, um caso que me vem a mente são formulários que não são construídos usando as tags providas pelo framework.

    <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
    <%@taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>

      <div class="form-group">
          <input type="text" name="nome" value="${profissional.nome}"/>
      </div>
      <div class="form-group">
        <input type="text" name="cpf" value="${profissional.cpf}"/>          
      </div>
      <div class="form-group">
        <input type="text" name="dataNascimento" 
          value="<fmt:formatData value="${profissional.dataNascimento.time}" pattern="dd/MM/yyyy"/>
      </div>
      <div class="form-group">       
       <select name="estado">
        <c:forEach items="${estados}" var="estadoAtual">
<option value="${estadoAtual.value}" ${profissional.estado == estadoAtual ? 'selected' : ''}>${estadoAtual.nome}
          </option>
        </c:forEach>
       </select>
      </div>

Desenvolvendo dessa forma, vai fazer com que o programador perca tempo implementando toda aquela lógica de manter o estado dos campos nos erros de validação, ou quando o usuário tiver que entrar numa tela de edição. Só que fica pior, viu o código para manter o estado do  select? E para exibir data formatada?

Para todos os casos citados, você poderia usar as tags de formulário do Spring MVC. O mesmo form que foi mostrado acima, poderia ser escrito da seguinte forma:

    <%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
    <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
    <div class="form-group">                    
        <form:input path='nome' type='text'/>
        <form:errors path='nome'/>          
    </div>
   <div class="form-group">                    
        <form:input path='cpf' type='text'/>
        <form:errors path='cpf'/>          
    </div>
    <div class="form-group">                    
        <form:input path='dataNascimento' type='text'/>
        <form:errors path='dataNascimento'/>          
    </div>
    <div class="form-group">
      <form:select path='estado' itemLabel="nome" itemValue="value" items="${estadoList}"/>
      <form:errors path='estado'/>
    </div>

Você simplesmente usa as tags disponíveis referenciando as propriedades do seu objeto e deixa o Spring MVC fazer o trabalho dele. Por exemplo, ele já vai verificar o estado relacionado com seu objeto e deixar a opção escolhida para você. Só que ainda vai além, no campo de data ele vai usar a annotation de formatação que você usou no seu atributo e já vai exibir a data da maneira correta.

  public class Profissional {
      
     ...
     @DateTimeFormat(iso=ISO.DATE)
     private Calendar dataNascimento;
     @NotNull
     private Estado estado;

     ...
  }  

Isso tudo somado, poupa um bom tempo de desenvolvimento que pode ser investido para melhorar outras partes importantes do seu código.

Lembre de sempre tirar proveito do que o seu framework já possui, não gastando tempo escrevendo algo que já foi pensado e implementado por outras pessoas cujo foco era exatamente facilitar a sua vida! Como é o último dia do mês, faço aqui o meu jabá dizendo que isso e muitas outras coisas podem ser encontradas no meu livro na Casa do Código :).

Será que você realmente está protegendo suas urls?

Esse é um post rápido, mas pode ser útil para quem utiliza o Spring Security em suas aplicações. Não sei se você já percebeu, mas para cada url mapeada através de um @RequestMapping, por default, você também tem a possibilidade de acessar variações dela. Veja o seguinte exemplo:

	@RequestMapping("/produtos/form")
	public ModelAndView form(@ModelAttribute Product product){
		ModelAndView modelAndView = new ModelAndView("products/form");
		modelAndView.addObject("bookTypes", BookType.values());
		return modelAndView;
	}

Além de poder acessar o endereço a partir da url mapeada, também podemos acessar alguma variações.

  • /produtos/form/
  • /produtos/form.

Parece inofensivo, não? Agora e se você tiver utilizado o Spring Security para proteger essa url?

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()		
		.antMatchers("/produtos/form").hasRole("ADMIN")
                //outras configurações

	}

Nessa situação, caso a pessoa tente acessar o endereço protegido usando uma das variações, ela vai conseguir! Caso você queira se proteger disso através do próprio Spring Security, pode alterar a configuração de url e usar uma regex, ao invés do AntMatcher.

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()
		.regexMatchers("/produtos/form[/\\.]?").hasRole("ADMIN")
		//outras configurações

Uma outra abordagem é fazer a servlet do Spring MVC ignorar os caminhos que não baterem exatamente com os configurados.

	@Override
	public void configurePathMatch(final PathMatchConfigurer configurer) {
		configurer.setUseSuffixPatternMatch(false);
		configurer.setUseTrailingSlashMatch(false);
	}

Basta que você sobreescreva o método configurePathMatch e invoque os métodos acima. A classe PathMatchConfigurer é justamente a responsável por guardar essas configurações. Tome apenas um cuidado, no momento em que você passa a ignorar tudo que vem depois do caminho configurado, você deixou de ter o suporte as facilidades para devolver outros tipos, que não o HTML, por exemplo quando você quer retornar json usando o sufixo .json. Agora você sempre se apoiará no cabeçalho accept do HTTP. Já tinha passado por essa situação? Como você tinha resolvido?