Spring Security: Explorando a autenticação JDBC
1. Visão geral
Neste breve tutorial, vamos explorar os recursos oferecidos pelo Spring para realizar a autenticação JDBC usando uma configuraçãoDataSource existente.
Em nosso postAuthentication with a Database-backed UserDetailsService, analisamos uma abordagem para conseguir isso, implementando nós mesmos a sinterfaceUserDetailService .
Desta vez, usaremos a diretivaAuthenticationManagerBuilder#jdbcAuthentication para analisar os prós e os contras dessa abordagem mais simples.
2. Usando uma conexão H2 incorporada
Em primeiro lugar, analisaremos como podemos obter autenticação usando um banco de dados H2 incorporado.
Isso é fácil de conseguir porque a maior parte da autoconfiguração do Spring Boot é preparada fora da caixa para este cenário.
2.1. Dependências e configuração do banco de dados
Vamos começar seguindo as instruções de nossa postagem anteriorSpring Boot With H2 Database para:
-
Inclui as dependências despring-boot-starter-data-jpa andh2 correspondentes
-
Configurar a conexão com o banco de dados com propriedades do aplicativo
-
Ativar o console H2
2.2. Configurando a autenticação JDBC
Usaremoshttps://spring.io/guides/topicals/spring-security-architecture#customizing_authentication_managers[Spring Security’s _AuthenticationManagerBuilder auxiliar de configuração] para configurar a autenticação JDBC:
@Autowired
private DataSource dataSource;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema()
.withUser(User.withUsername("user")
.password(passwordEncoder().encode("pass"))
.roles("USER"));
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
Como podemos ver, estamos usando oDataSource. autoconfigurado. A diretivawithDefaultSchema adiciona um script de banco de dados que preencherá o esquema padrão, permitindo que usuários e autoridades sejam armazenados.
Este esquema de usuário básico é documentado emthe Spring Security Appendix.
Por fim, estamos criando uma entrada no banco de dados com um usuário padrão programaticamente.
2.3. Verificando a configuração
Vamos criar um endpoint muito simples para recuperar as informações autenticadasPrincipal:
@RestController
@RequestMapping("/principal")
public class UserController {
@GetMapping
public Principal retrievePrincipal(Principal principal) {
return principal;
}
}
Além disso, protegeremos este endpoint, permitindo o acesso ao console H2:
@Configuration
public class SecurityConfiguration
extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity httpSecurity)
throws Exception {
httpSecurity.authorizeRequests()
.antMatchers("/h2-console/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin();
httpSecurity.csrf()
.ignoringAntMatchers("/h2-console/**");
httpSecurity.headers()
.frameOptions()
.sameOrigin();
}
}
Nota: aqui estamos reproduzindothe former security configuration implementado pelo Spring Boot, masin a real-life scenario, we probably won’t enable the H2 console at all.
Agora vamos executar o aplicativo e navegar no console H2. Podemos verificar queSpring is creating two tables in our embedded database: users and authorities.
Sua estrutura corresponde à estrutura definida no Apêndice Spring Security que mencionamos anteriormente.
Por fim, vamos autenticar e solicitar o endpoint/principal para ver as informações relacionadas, incluindo os detalhes do usuário.
2.4. Sob o capô
No início desta postagem, apresentamos um link para um tutorial que explicava comowe can customize database-backed authentication implementing the UserDetailsService interface; Recomendamos fortemente que você dê uma olhada nessa postagem se quisermos entender como as coisas funcionam nos bastidores.
Nesse caso, estamos contando com uma implementação dessa mesma interface fornecida pelo Spring Security; oJdbcDaoImpl.
Se explorarmos esta classe, veremos a simplificaçãoUserDetails que ela usa e os mecanismos para recuperar informações do usuário do banco de dados.
Isso funciona muito bem para esse cenário simples, mas tem algumas desvantagens se queremos personalizar o esquema do banco de dados ou mesmo se queremos usar um fornecedor de banco de dados diferente.
Vamos ver o que acontece se mudarmos a configuração para usar um serviço JDBC diferente.
3. Adaptando o esquema para um banco de dados diferente
Nesta seção, vamos configurar a autenticação em nosso projeto usando um banco de dados MySQL.
Como veremos a seguir, para conseguir isso, precisamos evitar o uso do esquema padrão e fornecer o nosso próprio.
3.1. Dependências e configuração do banco de dados
Para começar, vamos remover a dependênciah2 e substituí-la pela biblioteca MySQL correspondente:
mysql
mysql-connector-java
8.0.17
Como sempre, podemos procurar a versão mais recente da biblioteca emMaven Central.
Agora vamos redefinir as propriedades do aplicativo de acordo:
spring.datasource.url=
jdbc:mysql://localhost:3306/jdbc_authentication
spring.datasource.username=root
spring.datasource.password=pass
3.2. Executando a configuração padrão
Obviamente, eles devem ser personalizados para se conectar ao servidor MySQL em execução. Para fins de teste, aqui vamos iniciar uma nova instância usando Docker:
docker run -p 3306:3306
--name bael-mysql
-e MYSQL_ROOT_PASSWORD=pass
-e MYSQL_DATABASE=jdbc_authentication
mysql:latest
Vamos executar o projeto agora para ver se a configuração padrão é adequada para um banco de dados MySQL.
Na verdade, o aplicativo não poderá ser iniciado por causa de umSQLSyntaxErrorException. Isso realmente faz sentido; como dissemos, a maior parte da configuração automática padrão é adequada para um HSQLDB.
Neste caso,the DDL script provided with the withDefaultSchema directive uses a dialect not suitable for MySQL.
Portanto, precisamos evitar o uso desse esquema e fornecer o nosso.
3.3. Adaptando a configuração de autenticação
Como não queremos usar o esquema padrão, teremos que remover a instrução apropriada da configuraçãoAuthenticationManagerBuilder.
Além disso, como forneceremos nossos próprios scripts SQL, podemos evitar tentar criar o usuário programaticamente:
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource);
}
Agora vamos dar uma olhada nos scripts de inicialização do banco de dados.
Primeiro, nossoschema.sql:
CREATE TABLE users (
username VARCHAR(50) NOT NULL,
password VARCHAR(100) NOT NULL,
enabled TINYINT NOT NULL DEFAULT 1,
PRIMARY KEY (username)
);
CREATE TABLE authorities (
username VARCHAR(50) NOT NULL,
authority VARCHAR(50) NOT NULL,
FOREIGN KEY (username) REFERENCES users(username)
);
CREATE UNIQUE INDEX ix_auth_username
on authorities (username,authority);
E então, nossodata.sql:
-- User user/pass
INSERT INTO users (username, password, enabled)
values ('user',
'$2a$10$8.UnVuG9HHgffUDAlk8qfOuVGkqRzgVymGe07xd00DMxs.AQubh4a',
1);
INSERT INTO authorities (username, authority)
values ('user', 'ROLE_USER');
Por fim, devemos modificar algumas outras propriedades do aplicativo:
-
Como não esperamos que o Hibernate crie o esquema agora, devemos desabilitar a propriedadeddl-auto
-
Por padrão, o Spring Boot inicializa a fonte de dados apenas para bancos de dados incorporados, o que não é o caso aqui:
spring.datasource.initialization-mode=always
spring.jpa.hibernate.ddl-auto=none
Como resultado, agora devemos ser capazes de iniciar nosso aplicativo corretamente, autenticando e recuperandoPrincipal data do endpoint.
4. Adaptando as consultas para um esquema diferente
Vamos dar um passo adiante. Imagine que o esquema padrão não seja adequado para nossas necessidades.
4.1. Alterando o esquema padrão
Imagine, por exemplo, que já temos um banco de dados com uma estrutura que difere um pouco do padrão:
CREATE TABLE bael_users (
name VARCHAR(50) NOT NULL,
email VARCHAR(50) NOT NULL,
password VARCHAR(100) NOT NULL,
enabled TINYINT NOT NULL DEFAULT 1,
PRIMARY KEY (email)
);
CREATE TABLE authorities (
email VARCHAR(50) NOT NULL,
authority VARCHAR(50) NOT NULL,
FOREIGN KEY (email) REFERENCES bael_users(email)
);
CREATE UNIQUE INDEX ix_auth_email on authorities (email,authority);
Finalmente, nosso scriptdata.sql também será adaptado para esta mudança:
-- User [email protected]/pass
INSERT INTO bael_users (name, email, password, enabled)
values ('user',
'[email protected]',
'$2a$10$8.UnVuG9HHgffUDAlk8qfOuVGkqRzgVymGe07xd00DMxs.AQubh4a',
1);
INSERT INTO authorities (email, authority)
values ('[email protected]', 'ROLE_USER');
4.2. Executando o aplicativo com o novo esquema
Vamos lançar nosso aplicativo. Inicializa corretamente, o que faz sentido, pois nosso esquema está correto.
Agora, se tentarmos fazer o login, descobriremos que um erro é solicitado ao apresentar as credenciais.
O Spring Security ainda está procurando por um campousername no banco de dados. Para nossa sorte, a configuração de autenticação JDBC oferece a possibilidade decustomizing the queries used to retrieve user details in the authentication process.
4.3. Personalizando as consultas de pesquisa
Adaptar as consultas é bastante fácil. Simplesmente temos que fornecer nossas próprias instruções SQL ao configurar oAuthenticationManagerBuilder:
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery("select email,password,enabled "
+ "from bael_users "
+ "where email = ?")
.authoritiesByUsernameQuery("select email,authority "
+ "from authorities "
+ "where email = ?");
}
Podemos iniciar o aplicativo mais uma vez e acessar o endpoint/principal usando as novas credenciais.
5. Conclusão
Como podemos ver, essa abordagem é muito mais simples do que ter que criar nossa própria simplificaçãoUserDetailService , o que implica um processo árduo; criando entidades e classes implementando a sinterfaceUserDetail e adicionando repositórios ao nosso projeto.
The drawback is, of course, the little flexibility it offers when our database or our logic differs from the default strategy fornecido pela solução Spring Security.
Por fim, podemos dar uma olhada nos exemplos completos emour Github repository. Incluímos até um exemplo usando PostgreSQL que não mostramos neste tutorial, apenas para manter as coisas simples.