JavaRush /Blogue Java /Random-PT /JAAS - Introdução à Tecnologia (Parte 1)
Viacheslav
Nível 3

JAAS - Introdução à Tecnologia (Parte 1)

Publicado no grupo Random-PT
A segurança de acesso está implementada em Java há bastante tempo e a arquitetura para fornecer essa segurança é chamada de JAAS - Java Authentication and Authorization Service. Esta revisão tentará desvendar o mistério do que é autenticação, autorização e o que JAAS tem a ver com isso. Como o JAAS é amigo da API do Servlet e onde eles têm problemas no relacionamento.
JAAS - Introdução à Tecnologia (Parte 1) - 1

Introdução

Nesta revisão, gostaria de discutir um tópico como segurança de aplicativos da web. Java possui diversas tecnologias que fornecem segurança: Mas nossa conversa de hoje será sobre outra tecnologia, que se chama “Java Authentication and Authorization Service (JAAS)”. É ela quem descreve coisas tão importantes como autenticação e autorização. Vejamos isso com mais detalhes.
JAAS - Introdução à Tecnologia (Parte 1) - 2

JAS

JAAS é uma extensão do Java SE e está descrito no Java Authentication and Authorization Service (JAAS) Reference Guide . Como o nome da tecnologia sugere, JAAS descreve como a autenticação e autorização devem ser realizadas:
  • " Autenticação ": Traduzido do grego, "authentikos" significa "real, genuíno". Ou seja, a autenticação é um teste de autenticidade. Que quem está sendo autenticado é realmente quem diz ser.

  • Autorização ”: traduzido do inglês significa “permissão”. Ou seja, autorização é o controle de acesso realizado após autenticação bem-sucedida.

Ou seja, JAAS trata de determinar quem está solicitando acesso a um recurso e tomar uma decisão se ele pode obter esse acesso. Uma pequena analogia da vida: você está dirigindo na estrada e um inspetor o para. Forneça documentos - autenticação. Você pode dirigir um carro com documentos - autorização. Ou, por exemplo, você deseja comprar bebidas alcoólicas em uma loja. Primeiro, é solicitado um passaporte - autenticação. Em seguida, com base na sua idade, é decidido se você tem direito a comprar bebidas alcoólicas. Isto é autorização. Em aplicativos da web, fazer login como usuário (inserir um nome de usuário e senha) é uma autenticação. E determinar quais páginas você pode abrir é determinado por autorização. É aqui que “O Serviço de Autenticação e Autorização Java (JAAS)” nos ajuda. Ao considerar o JAAS, é importante compreender vários conceitos-chave que o JAAS descreve: Assunto, Princípios, Credenciais. Assunto é o assunto da autenticação. Ou seja, é o portador ou detentor de direitos. Na documentação, Assunto é definido como a origem de uma solicitação para realizar alguma ação. O assunto ou fonte deve ser descrito de alguma forma e para esse propósito é usado o Principal, que em russo às vezes também é chamado de principal. Ou seja, cada Principal é uma representação de um Sujeito sob um determinado ponto de vista. Para ficar mais claro, vamos dar um exemplo: Uma determinada pessoa é um Sujeito. E os seguintes podem atuar como Diretores:
  • sua carteira de motorista como representação de uma pessoa como usuário da estrada
  • seu passaporte, como representação de uma pessoa como cidadão de seu país
  • seu passaporte estrangeiro, como representação de uma pessoa como participante nas relações internacionais
  • seu cartão de biblioteca na biblioteca, como uma representação de uma pessoa como leitor vinculado à biblioteca
Além disso, o Assunto possui um conjunto de “Credencial”, que significa “identidade” em inglês. É assim que o Sujeito confirma que é ele. Por exemplo, a senha do usuário pode ser a Credencial. Ou qualquer objeto com o qual o usuário possa confirmar que é realmente ele. Vamos agora ver como o JAAS é usado em aplicações web.
JAAS - Introdução à Tecnologia (Parte 1) - 3

Aplicativo web

Portanto, precisamos de um aplicativo da web. O sistema automático de construção de projetos Gradle nos ajudará a criá-lo. Graças ao uso do Gradle, podemos, executando pequenos comandos, montar um projeto Java no formato que precisamos, criar automaticamente a estrutura de diretórios necessária e muito mais. Você pode ler mais sobre o Gradle na breve visão geral: " Uma breve introdução ao Gradle " ou na documentação oficial " Gradle Getting Started ". Precisamos inicializar o projeto (Inicialização), e para isso o Gradle possui um plugin especial: “ Gradle Init Plugin ” (Init é a abreviatura de Inicialização, fácil de lembrar). Para usar este plugin, execute o comando na linha de comando:
gradle init --type java-application
Após a conclusão bem-sucedida, teremos um projeto Java. Vamos agora abrir o script de construção do nosso projeto para edição. Um script de construção é um arquivo chamado build.gradle, que descreve as nuances da construção do aplicativo. Daí o nome, script de construção. Podemos dizer que este é um script de construção de projeto. Gradle é uma ferramenta versátil, cujos recursos básicos são expandidos com plug-ins. Portanto, antes de mais nada, vamos prestar atenção ao bloco “plugins”:
plugins {
    id 'java'
    id 'application'
}
Por padrão, o Gradle, de acordo com o que especificamos " --type java-application", configurou um conjunto de alguns plugins principais, ou seja, aqueles plugins que estão incluídos na distribuição do próprio Gradle. Se você for para a seção "Docs" (ou seja, documentação) no site gradle.org , à esquerda na lista de tópicos da seção "Referência" veremos a seção " Plugins principais ", ou seja, seção com uma descrição desses plug-ins muito básicos. Vamos escolher exatamente os plugins que precisamos, e não aqueles que o Gradle gerou para nós. De acordo com a documentação, o “ Gradle Java Plugin ” fornece operações básicas com código Java, como compilar código-fonte. Além disso, de acordo com a documentação, o " plugin de aplicativo Gradle " nos fornece ferramentas para trabalhar com o "aplicativo JVM executável", ou seja, com um aplicativo Java que pode ser iniciado como um aplicativo independente (por exemplo, um aplicativo de console ou um aplicativo com sua própria UI). Acontece que não precisamos do plugin “aplicativo”, porque... não precisamos de um aplicativo independente, precisamos de um aplicativo web. Vamos excluí-lo. Bem como a configuração “mainClassName”, que é conhecida apenas por este plugin. Além disso, na mesma seção " Embalagem e distribuição " onde o link para a documentação do plug-in do aplicativo foi fornecido, há um link para o plug-in Gradle War. O Gradle War Plugin , conforme indicado na documentação, fornece suporte para a criação de aplicações web Java em formato war. No formato WAR significa que em vez de um arquivo JAR, um arquivo WAR será criado. Parece que é disso que precisamos. Além disso, como diz a documentação, "O plugin War estende o plugin Java". Ou seja, podemos substituir o plugin java pelo plugin war. Portanto, nosso bloco de plugins ficará assim:
plugins {
    id 'war'
}
Também na documentação do "Gradle War Plugin" é dito que o plugin usa um "Layout de Projeto" adicional. O layout é traduzido do inglês como localização. Ou seja, o plugin war por padrão espera a existência de um determinado local de arquivos que utilizará para suas tarefas. Ele usará o seguinte diretório para armazenar arquivos de aplicativos da web: src/main/webapp O comportamento do plugin é descrito a seguir:
JAAS - Introdução à Tecnologia (Parte 1) - 4
Ou seja, o plugin levará em consideração os arquivos deste local ao construir o arquivo WAR de nossa aplicação web. Além disso, a documentação do Gradle War Plugin diz que este diretório será a "raiz do arquivo". E já nele podemos criar um diretório WEB-INF e adicionar o arquivo web.xml lá. Que tipo de arquivo é esse? web.xml- este é um "descritor de implantação" ou "descritor de implantação". Este é um arquivo que descreve como configurar nosso aplicativo web para funcionar. Este arquivo especifica quais solicitações nosso aplicativo irá tratar, configurações de segurança e muito mais. Em sua essência, é um pouco semelhante a um arquivo de manifesto de um arquivo JAR (consulte " Trabalhando com arquivos de manifesto: noções básicas "). O arquivo de manifesto informa como trabalhar com um aplicativo Java (ou seja, um arquivo JAR) e o web.xml informa como trabalhar com um aplicativo Java Web (ou seja, um arquivo WAR). O próprio conceito de "Descritor de Implantação" não surgiu por si só, mas está descrito no documento " Especificação da API do Servlet"". Qualquer aplicação web Java depende desta "API Servlet". É importante entender que se trata de uma API - ou seja, é uma descrição de algum contrato de interação. Aplicações web não são aplicações independentes. Elas são executadas em um servidor web , que fornece comunicação de rede com os usuários. Ou seja, um servidor web é uma espécie de “contêiner” para aplicações web. Isso é lógico, porque queremos escrever a lógica de uma aplicação web, ou seja, quais páginas o usuário verá e como eles devem reagir às ações do usuário. E não queremos escrever código sobre como uma mensagem será enviada ao usuário, como os bytes de informação serão transferidos e outras coisas de baixo nível e que exigem muita qualidade. Além disso, Acontece que os aplicativos da Web são todos diferentes, mas a transferência de dados é a mesma, ou seja, um milhão de programadores teriam que escrever código para o mesmo propósito repetidamente.Portanto, o servidor da Web é responsável por parte da interação do usuário e troca de dados, e a aplicação web e o desenvolvedor são responsáveis ​​por gerar esses dados. E para conectar essas duas partes, ou seja, servidor web e aplicativo web, você precisa de um contrato para sua interação, ou seja, que regras eles seguirão para fazer isso? Para descrever de alguma forma o contrato, como deveria ser a interação entre uma aplicação web e um servidor web, a API Servlet foi inventada. Curiosamente, mesmo se você usar estruturas como Spring, ainda existe uma API Servlet em execução nos bastidores. Ou seja, você usa Spring, e Spring trabalha com a API Servlet para você. Acontece que nosso projeto de aplicação web deve depender da API do Servlet. Neste caso, a API do Servlet será uma dependência. Como sabemos, o Gradle também permite descrever dependências do projeto de forma declarativa. Os plug-ins descrevem como as dependências podem ser gerenciadas. Por exemplo, o plug-in Java Gradle introduz um método de gerenciamento de dependência "testImplementation", que diz que tal dependência é necessária apenas para testes. Mas o plug-in Gradle War adiciona um método de gerenciamento de dependência “providedCompile”, que diz que tal dependência não será incluída no arquivo WAR de nossa aplicação web. Por que não incluímos a API Servlet em nosso arquivo WAR? Porque a API do Servlet será fornecida à nossa aplicação web pelo próprio servidor web. Se um servidor web fornecer uma API de Servlet, o servidor será chamado de contêiner de servlet. Portanto, é responsabilidade do servidor web nos fornecer a API do Servlet, e é nossa responsabilidade fornecer a ServletAPI apenas no momento da compilação do código. É por isso providedCompile. Assim, o bloco de dependências ficará assim:
dependencies {
    providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
    testImplementation 'junit:junit:4.12'
}
Então, vamos voltar ao arquivo web.xml. Por padrão, o Gradle não cria nenhum descritor de implantação, então precisamos fazer isso nós mesmos. Vamos criar um diretório src/main/webapp/WEB-INF, e nele criaremos um arquivo XML chamado web.xml. Agora vamos abrir a própria "Especificação do Servlet Java" e o capítulo " CAPÍTULO 14 Deployment Descriptor ". Conforme declarado em "14.3 Descritor de Implementação", o documento XML do Descritor de Implementação é descrito pelo esquema http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd . Um esquema XML descreve em quais elementos um documento pode consistir e em que ordem eles devem aparecer. Quais são obrigatórios e quais não são. Em geral, descreve a estrutura do documento e permite verificar se o documento XML está composto corretamente. Agora vamos usar o exemplo do capítulo " 14.5 Exemplos ", mas o esquema deve ser especificado para a versão 3.1, ou seja,
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd
Nosso vazio web.xmlficará assim:
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    <display-name>JAAS Example</display-name>
</web-app>
Vamos agora descrever o servlet que protegeremos usando JAAS. Anteriormente, Gradle gerava a classe App para nós. Vamos transformá-lo em um servlet. Conforme declarado na especificação em " CAPÍTULO 2 A Interface do Servlet ", que " Para a maioria dos propósitos, os desenvolvedores estenderão o HttpServlet para implementar seus servlets ", ou seja, para tornar uma classe um servlet, você precisa herdar esta classe de HttpServlet:
public class App extends HttpServlet {
	public String getGreeting() {
        return "Secret!";
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.getWriter().print(getGreeting());
    }
}
Como dissemos, a API Servlet é um contrato entre o servidor e nossa aplicação web. Este contrato nos permite descrever que quando um usuário entrar em contato com o servidor, o servidor irá gerar uma solicitação do usuário na forma de um objeto HttpServletRequeste passá-la para o servlet. Ele também fornecerá ao servlet um objeto HttpServletResponsepara que o servlet possa escrever uma resposta para o usuário. Assim que a execução do servlet terminar, o servidor poderá fornecer ao usuário uma resposta baseada nele HttpServletResponse. Ou seja, o servlet não se comunica diretamente com o usuário, mas apenas com o servidor. Para que o servidor saiba que temos um servlet e para quais solicitações ele precisa ser usado, precisamos informar o servidor sobre isso no descritor de implantação:
<servlet>
	<servlet-name>app</servlet-name>
	<servlet-class>jaas.App</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>app</servlet-name>
	<url-pattern>/secret</url-pattern>
</servlet-mapping>
Nesse caso, todas as solicitações /secretnão serão endereçadas ao nosso servlet por nome app, que corresponde à classe jaas.App. Como dissemos anteriormente, uma aplicação web só pode ser implantada em um servidor web. O servidor web pode ser instalado separadamente (autônomo). Mas para os propósitos desta revisão, uma opção alternativa é adequada - rodando em um servidor incorporado. Isso significa que o servidor será criado e iniciado programaticamente (o plugin fará isso para nós) e, ao mesmo tempo, nosso aplicativo web será implantado nele. O sistema de compilação Gradle permite que você use o plugin " Gradle Gretty Plugin " para estes fins:
plugins {
    id 'war'
    id 'org.gretty' version '2.2.0'
}
Além disso, o plugin Gretty possui uma boa documentação . Vamos começar com o fato de que o plugin Gretty permite alternar entre diferentes servidores web. Isso é descrito com mais detalhes na documentação: " Alternando entre contêineres de servlet ". Vamos mudar para o Tomcat, porque... é um dos mais populares em uso, e também possui boa documentação e muitos exemplos e problemas analisados:
gretty {
    // Переключаемся с дефолтного Jetty на Tomcat
    servletContainer = 'tomcat8'
    // Укажем Context Path, он же Context Root
    contextPath = '/jaas'
}
Agora podemos executar "gradle appRun" e então nossa aplicação web estará disponível em http://localhost:8080/jaas/secret
JAAS - Introdução à Tecnologia (Parte 1) - 5
É importante verificar se o contêiner do servlet está selecionado pelo Tomcat (ver #1) e verificar em qual endereço nossa aplicação web está disponível (ver #2).
JAAS - Introdução à Tecnologia (Parte 1) - 6

Autenticação

As configurações de autenticação geralmente consistem em duas partes: configurações no lado do servidor e configurações no lado do aplicativo Web executado neste servidor. As configurações de segurança de um aplicativo da web não podem deixar de interagir com as configurações de segurança do servidor da web, pelo menos porque um aplicativo da web não pode deixar de interagir com o servidor da web. Não foi em vão que mudamos para o Tomcat, porque... O Tomcat possui uma arquitetura bem descrita (veja " Arquitetura Apache Tomcat 8 "). Pela descrição desta arquitetura fica claro que o Tomcat, como servidor web, representa a aplicação web como um determinado contexto, que é chamado de “ Contexto Tomcat ”. Este contexto permite que cada aplicação web tenha suas próprias configurações, isoladas de outras aplicações web. Além disso, a aplicação web pode influenciar as configurações deste contexto. Flexível e conveniente. Para uma compreensão mais profunda, recomendamos a leitura do artigo " Understanding Tomcat Context Containers " e da seção da documentação do Tomcat " The Context Container ". Conforme declarado acima, nosso aplicativo da web pode influenciar o contexto do Tomcat do nosso aplicativo usando um arquivo /META-INF/context.xml. E uma das configurações muito importantes que podemos influenciar são os Reinos de Segurança. Security Realms é uma espécie de “área de segurança”. Uma área para a qual são especificadas configurações de segurança específicas. Da mesma forma, ao usar um Realm de Segurança, aplicamos as configurações de segurança definidas para este Realm. Os domínios de segurança são gerenciados por um contêiner, ou seja, servidor web, não nosso aplicativo web. Só podemos informar ao servidor qual escopo de segurança precisa ser estendido à nossa aplicação. A documentação do Tomcat na seção " O componente Realm " descreve um Realm como uma coleção de dados sobre usuários e suas funções para realizar autenticação. O Tomcat fornece um conjunto de diferentes implementações do Security Realm, uma das quais é o " Jaas Realm ". Tendo entendido um pouco da terminologia, vamos descrever o contexto do Tomcat no arquivo /META-INF/context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<Context>
    <Realm className="org.apache.catalina.realm.JAASRealm"
           appName="JaasLogin"
           userClassNames="jaas.login.UserPrincipal"
           roleClassNames="jaas.login.RolePrincipal"
           configFile="jaas.config" />
</Context>
appName- Nome da Aplicação. O Tomcat tentará combinar esse nome com os nomes especificados no arquivo configFile. configFile- este é o "arquivo de configuração de login". Um exemplo disso pode ser visto na documentação do JAAS: " Apêndice B: Exemplos de Configurações de Login ". Além disso, é importante que esse arquivo seja pesquisado primeiro nos recursos. Portanto, nosso aplicativo da web pode fornecer esse arquivo sozinho. Atributos userClassNamese roleClassNamescontém indicação das classes que representam o principal do usuário. JAAS separa os conceitos de “usuário” e “função” como dois conceitos diferentes java.security.Principal. Vamos descrever as classes acima. Vamos criar a implementação mais simples para o usuário principal:
public class UserPrincipal implements Principal {
    private String name;
    public UserPrincipal(String name) {
        this.name = name;
    }
    @Override
    public String getName() {
        return name;
    }
}
Repetiremos exatamente a mesma implementação para RolePrincipal. Como você pode ver na interface, o principal do Principal é armazenar e retornar algum nome (ou ID) que represente o Principal. Agora temos um Security Realm, temos aulas principais. Resta preencher o arquivo do configFileatributo " ", também conhecido como login configuration file. Sua descrição pode ser encontrada na documentação do Tomcat: " The Realm Component ".
JAAS - Introdução à Tecnologia (Parte 1) - 7
Ou seja, podemos colocar a configuração JAAS Login Config nos recursos de nossa aplicação web e graças ao Tomcat Context poderemos utilizá-la. Este arquivo deve estar disponível como recurso para o ClassLoader, portanto seu caminho deve ser assim: \src\main\resources\jaas.config Vamos definir o conteúdo deste arquivo:
JaasLogin {
    jaas.login.JaasLoginModule required debug=true;
};
É importante notar que context.xmlo mesmo nome é usado aqui e em. Isso mapeia o Security Realm para o LoginModule. Portanto, o Contexto do Tomcat nos disse quais classes representam os principais, bem como qual LoginModule usar. Tudo o que precisamos fazer é implementar este LoginModule. LoginModule é talvez uma das coisas mais interessantes do JAAS. A documentação oficial nos ajudará no desenvolvimento do LoginModule: " Java Authentication and Authorization Service (JAAS): LoginModule Developer's Guide ". Vamos implementar o módulo de login. Vamos criar uma classe que implemente a interface LoginModule:
public class JaasLoginModule implements LoginModule {
}
Primeiro descrevemos o método de inicialização LoginModule:
private CallbackHandler handler;
private Subject subject;
@Override
public void initialize(Subject subject, CallbackHandler callbackHandler, <String, ?> sharedState, Map<String, ?> options) {
	handler = callbackHandler;
	this.subject = subject;
}
Este método salvará Subject, que autenticaremos posteriormente e preencheremos com informações sobre os principais. Também guardaremos para uso futuro CallbackHandler, que nos é dado. Com ajuda, CallbackHandlerpodemos solicitar diversas informações sobre o assunto autenticação um pouco mais tarde. Você pode ler mais sobre isso CallbackHandlerna seção correspondente da documentação: " JAAS Reference Guide: CallbackHandler ". A seguir, o método loginde autenticação é executado Subject. Esta é a primeira fase da autenticação:
@Override
public boolean login() throws LoginException {
	// Добавляем колбэки
	Callback[] callbacks = new Callback[2];
	callbacks[0] = new NameCallback("login");
	callbacks[1] = new PasswordCallback("password", true);
	// При помощи колбэков получаем через CallbackHandler логин и пароль
	try {
		handler.handle(callbacks);
		String name = ((NameCallback) callbacks[0]).getName();
		String password = String.valueOf(((PasswordCallback) callbacks[1]).getPassword());
		// Далее выполняем валидацию.
		// Тут просто для примера проверяем определённые значения
		if (name != null && name.equals("user123") && password != null && password.equals("pass123")) {
			// Сохраняем информацию, которая будет использована в методе commit
			// Не "пачкаем" Subject, т.к. не факт, что commit выполнится
			// Для примера проставим группы вручную, "хардcodeно".
			login = name;
			userGroups = new ArrayList<String>();
			userGroups.add("admin");
			return true;
		} else {
			throw new LoginException("Authentication failed");
		}
	} catch (IOException | UnsupportedCallbackException e) {
		throw new LoginException(e.getMessage());
	}
}
É importante que loginnão mudemos o Subject. Tais alterações deverão ocorrer apenas no método de confirmação commit. A seguir, devemos descrever o método para confirmar a autenticação bem-sucedida:
@Override
public boolean commit() throws LoginException {
	userPrincipal = new UserPrincipal(login);
	subject.getPrincipals().add(userPrincipal);
	if (userGroups != null && userGroups.size() > 0) {
		for (String groupName : userGroups) {
			rolePrincipal = new RolePrincipal(groupName);
			subject.getPrincipals().add(rolePrincipal);
		}
	}
	return true;
}
Pode parecer estranho separar o método logine commit. Mas a questão é que os módulos de login podem ser combinados. E para uma autenticação bem-sucedida pode ser necessário que vários módulos de login funcionem com êxito. E somente se todos os módulos necessários funcionarem, salve as alterações. Esta é a segunda fase da autenticação. Vamos terminar com os métodos aborte logout:
@Override
public boolean abort() throws LoginException {
	return false;
}
@Override
public boolean logout() throws LoginException {
	subject.getPrincipals().remove(userPrincipal);
	subject.getPrincipals().remove(rolePrincipal);
	return true;
}
O método aborté chamado quando a primeira fase da autenticação falha. O método logouté chamado quando o sistema efetua logout. Depois de implementar o nosso Login Modulee configurá-lo Security Realm, agora precisamos indicar web.xmlo fato de que queremos usar um específico Login Config:
<login-config>
  <auth-method>BASIC</auth-method>
  <realm-name>JaasLogin</realm-name>
</login-config>
Especificamos o nome do nosso Security Realm e especificamos o Método de Autenticação - BASIC. Este é um dos tipos de autenticação descritos na API do Servlet na seção “ 13.6 Autenticação ”. Permaneceu n
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION