spring-boot-jwt/
│
├── src/main/java/
│ └── murraco
│ ├── configuration
│ │ └── SwaggerConfig.java
│ │
│ ├── controller
│ │ └── UserController.java
│ │
│ ├── dto
│ │ ├── UserDataDTO.java
│ │ └── UserResponseDTO.java
│ │
│ ├── exception
│ │ ├── CustomException.java
│ │ └── GlobalExceptionController.java
│ │
│ ├── model
│ │ ├── AppUserRole.java
│ │ └── AppUser.java
│ │
│ ├── repository
│ │ └── UserRepository.java
│ │
│ ├── security
│ │ ├── JwtTokenFilter.java
│ │ ├── JwtTokenFilterConfigurer.java
│ │ ├── JwtTokenProvider.java
│ │ ├── MyUserDetails.java
│ │ └── WebSecurityConfig.java
│ │
│ ├── service
│ │ └── UserService.java
│ │
│ └── JwtAuthServiceApp.java
│
├── src/main/resources/
│ └── application.yml
│
├── .gitignore
├── LICENSE
├── mvnw/mvnw.cmd
├── README.md
└── pom.xml
Só para dar uma ideia, temos uma introdução maravilhosa, cortesia de jwt.io ! Vamos dar uma olhada:
JSON Web Token (JWT) é um padrão aberto (RFC 7519) que define uma maneira compacta e independente de transmitir informações com segurança entre as partes como um objeto JSON. Essas informações podem ser verificadas e confiáveis porque são assinadas digitalmente. Os JWTs podem ser assinados usando um segredo (com o algoritmo HMAC) ou um par de chaves pública/privada usando RSA.
Vamos explicar melhor alguns conceitos desta definição.
Compacto : devido ao seu tamanho menor, os JWTs podem ser enviados por meio de uma URL, parâmetro POST ou dentro de um cabeçalho HTTP. Além disso, o tamanho menor significa que a transmissão é rápida.
Autônomo : o payload contém todas as informações necessárias sobre o usuário, evitando a necessidade de consultar o banco de dados mais de uma vez.
Aqui estão alguns cenários em que JSON Web Tokens são úteis:
Autenticação : Este é o cenário mais comum para usar JWT. Depois que o usuário estiver logado, cada solicitação subsequente incluirá o JWT, permitindo que o usuário acesse rotas, serviços e recursos permitidos com aquele token. Single Sign On é um recurso que usa amplamente o JWT hoje em dia, devido à sua pequena sobrecarga e à capacidade de ser facilmente usado em diferentes domínios.
Troca de informações : JSON Web Tokens são uma boa maneira de transmitir informações com segurança entre as partes. Como os JWTs podem ser assinados (por exemplo, usando pares de chaves públicas/privadas), você pode ter certeza de que os remetentes são quem dizem ser. Além disso, como a assinatura é calculada usando o cabeçalho e a carga útil, você também pode verificar se o conteúdo não foi adulterado.
JSON Web Tokens consistem em três partes separadas por pontos (.) , que são:
Portanto, um JWT normalmente se parece com o seguinte.
xxxxx
. yyyyy
. zzzzz
Vamos dividir as diferentes partes.
Cabeçalho
O cabeçalho normalmente consiste em duas partes: o tipo de token, que é JWT, e o algoritmo de hash usado, como HMAC SHA256 ou RSA.
Por exemplo:
{
"alg" : " HS256 " ,
"typ" : " JWT "
}
Então, este JSON é codificado em Base64Url para formar a primeira parte do JWT.
Carga útil
A segunda parte do token é a carga útil, que contém as declarações. As declarações são declarações sobre uma entidade (normalmente, o usuário) e metadados adicionais. Existem três tipos de reivindicações: reivindicações reservadas, públicas e privadas.
Observe que os nomes das declarações têm apenas três caracteres, pois o JWT deve ser compacto.
Declarações públicas : podem ser definidas à vontade por aqueles que usam JWTs. Mas, para evitar colisões, eles devem ser definidos no IANA JSON Web Token Registry ou como um URI que contém um namespace resistente a colisões.
Reivindicações privadas : são declarações personalizadas criadas para compartilhar informações entre as partes que concordam em usá-las.
Um exemplo de carga útil poderia ser:
{
"sub" : " 1234567890 " ,
"name" : " John Doe " ,
"admin" : true
}
A carga útil é então codificada em Base64Url para formar a segunda parte do JSON Web Token.
Assinatura
Para criar a parte da assinatura, você deve pegar o cabeçalho codificado, a carga útil codificada, um segredo, o algoritmo especificado no cabeçalho e assiná-lo.
Por exemplo, se você quiser usar o algoritmo HMAC SHA256, a assinatura será criada da seguinte forma:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
A assinatura é usada para verificar se o remetente do JWT é quem diz ser e para garantir que a mensagem não foi alterada ao longo do caminho. Juntando tudo
A saída são três strings Base64 separadas por pontos que podem ser facilmente passadas em ambientes HTML e HTTP, sendo mais compactas quando comparadas a padrões baseados em XML, como SAML.
O exemplo a seguir mostra um JWT que possui o cabeçalho e a carga anteriores codificados e é assinado com um segredo. JWT codificado
Na autenticação, quando o usuário fizer login com sucesso usando suas credenciais, um JSON Web Token será retornado e deverá ser salvo localmente (normalmente no armazenamento local, mas cookies também podem ser usados), em vez da abordagem tradicional de criação de uma sessão no servidor e retornando um cookie.
Sempre que o usuário desejar acessar uma rota ou recurso protegido, o agente do usuário deverá enviar o JWT, normalmente no cabeçalho Authorization usando o esquema Bearer. O conteúdo do cabeçalho deve ser semelhante ao seguinte:
Authorization: Bearer <token>
Este é um mecanismo de autenticação sem estado, pois o estado do usuário nunca é salvo na memória do servidor. As rotas protegidas do servidor verificarão um JWT válido no cabeçalho de autorização e, se estiver presente, o usuário terá permissão para acessar recursos protegidos. Como os JWTs são independentes, todas as informações necessárias estão lá, reduzindo a necessidade de consultar o banco de dados diversas vezes.
Isso permite que você confie totalmente em APIs de dados sem estado e até mesmo faça solicitações para serviços downstream. Não importa quais domínios estão servindo suas APIs, então o Cross-Origin Resource Sharing (CORS) não será um problema, pois não usa cookies.
O diagrama a seguir mostra esse processo:
Os esquemas de autenticação baseados em token tornaram-se imensamente populares nos últimos tempos, pois fornecem benefícios importantes quando comparados a sessões/cookies:
Algumas compensações devem ser feitas com esta abordagem:
O fluxo de autenticação JWT é muito simples
É importante observar que as reivindicações de autorização serão incluídas no token de acesso. Por que isso é importante? Bem, digamos que as reivindicações de autorização (por exemplo, privilégios de usuário no banco de dados) sejam alteradas durante a vida útil do token de acesso. Essas alterações não entrarão em vigor até que um novo token de acesso seja emitido. Na maioria dos casos, isso não é um grande problema, porque os tokens de acesso têm vida curta. Caso contrário, escolha o padrão de token opaco.
Vamos ver como podemos implementar a autenticação baseada em token JWT usando Java e Spring, enquanto tentamos reutilizar o comportamento padrão de segurança do Spring sempre que possível. O framework Spring Security vem com classes de plug-ins que já tratam de mecanismos de autorização como: cookies de sessão, HTTP Basic e HTTP Digest. No entanto, falta suporte nativo para JWT e precisamos colocar a mão na massa para fazê-lo funcionar.
Esta demonstração está usando atualmente um banco de dados H2 chamado test_db para que você possa executá-lo rapidamente e pronto para uso, sem muita configuração. Se você deseja se conectar a um banco de dados diferente, você deve especificar a conexão no arquivo application.yml
dentro do diretório de recursos. Observe que hibernate.hbm2ddl.auto=create-drop
irá descartar e criar um banco de dados limpo cada vez que implantarmos (você pode querer alterá-lo se estiver usando isso em um projeto real). Aqui está o exemplo do projeto, veja como é fácil trocar comentários nas propriedades url
e dialect
para usar seu próprio banco de dados MySQL:
spring :
datasource :
url : jdbc:h2:mem:test_db;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
# url: jdbc:mysql://localhost:3306/user_db
username : root
password : root
tomcat :
max-wait : 20000
max-active : 50
max-idle : 20
min-idle : 15
jpa :
hibernate :
ddl-auto : create-drop
properties :
hibernate :
dialect : org.hibernate.dialect.H2Dialect
# dialect: org.hibernate.dialect.MySQL8Dialect
format_sql : true
id :
new_generator_mappings : false
JwtTokenFilter
JwtTokenFilterConfigurer
JwtTokenProvider
MyUserDetails
WebSecurityConfig
Filtro JwtToken
O filtro JwtTokenFilter
é aplicado a cada API ( /**
) com exceção do endpoint do token de login ( /users/signin
) e do endpoint singup ( /users/signup
).
Este filtro tem as seguintes responsabilidades:
JwtTokenProvider
caso contrário, lançará uma exceção de autenticação Certifique-se de que chain.doFilter(request, response)
seja invocado após a autenticação bem-sucedida. Você deseja que o processamento da solicitação avance para o próximo filtro, porque o último filtro FilterSecurityInterceptor#doFilter é responsável por realmente invocar o método em seu controlador que está manipulando o recurso de API solicitado.
String token = jwtTokenProvider . resolveToken (( HttpServletRequest ) req );
if ( token != null && jwtTokenProvider . validateToken ( token )) {
Authentication auth = jwtTokenProvider . getAuthentication ( token );
SecurityContextHolder . getContext (). setAuthentication ( auth );
}
filterChain . doFilter ( req , res );
JwtTokenFilterConfigurer
Adiciona o JwtTokenFilter
ao DefaultSecurityFilterChain
da segurança de inicialização Spring.
JwtTokenFilter customFilter = new JwtTokenFilter ( jwtTokenProvider );
http . addFilterBefore ( customFilter , UsernamePasswordAuthenticationFilter . class );
Fornecedor JwtToken
O JwtTokenProvider
tem as seguintes responsabilidades:
Meus detalhes do usuário
Implementa UserDetailsService
para definir nossa própria função loadUserbyUsername personalizada. A interface UserDetailsService
é usada para recuperar dados relacionados ao usuário. Ele possui um método chamado loadUserByUsername que encontra uma entidade de usuário com base no nome de usuário e pode ser substituído para personalizar o processo de localização do usuário.
É usado pelo DaoAuthenticationProvider
para carregar detalhes sobre o usuário durante a autenticação.
WebSecurityConfig
A classe WebSecurityConfig
estende WebSecurityConfigurerAdapter
para fornecer configuração de segurança personalizada.
Os seguintes beans são configurados e instanciados nesta classe:
JwtTokenFilter
PasswordEncoder
Além disso, dentro do método WebSecurityConfig#configure(HttpSecurity http)
configuraremos padrões para definir endpoints de API protegidos/desprotegidos. Observe que desativamos a proteção CSRF porque não estamos usando cookies.
// Disable CSRF (cross site request forgery)
http . csrf (). disable ();
// No session will be created or used by spring security
http . sessionManagement (). sessionCreationPolicy ( SessionCreationPolicy . STATELESS );
// Entry points
http . authorizeRequests () //
. antMatchers ( "/users/signin" ). permitAll () //
. antMatchers ( "/users/signup" ). permitAll () //
// Disallow everything else..
. anyRequest (). authenticated ();
// If a user try to access a resource without having enough permissions
http . exceptionHandling (). accessDeniedPage ( "/login" );
// Apply JWT
http . apply ( new JwtTokenFilterConfigurer ( jwtTokenProvider ));
// Optional, if you want to test the API from a browser
// http.httpBasic();
Certifique-se de ter Java 8 e Maven instalados
Bifurque este repositório e clone-o
$ git clone https://github.com/<your-user>/spring-boot-jwt
$ cd spring-boot-jwt
$ mvn install
$ mvn spring-boot:run
http://localhost:8080/swagger-ui.html
em seu navegador para verificar se tudo está funcionando corretamente. Você pode alterar a porta padrão no arquivo application.yml
server :
port : 8080
/users/me
para verificar se você não está autenticado. Você deverá receber uma resposta com 403
com uma mensagem Access Denied
, pois ainda não definiu seu token JWT válido $ curl -X GET http://localhost:8080/users/me
/users/signin
com o usuário administrador padrão que criamos programaticamente para obter um token JWT válido $ curl -X POST 'http://localhost:8080/users/signin?username=admin&password=admin'
/users/me
novamente $ curl -X GET http://localhost:8080/users/me -H 'Authorization: Bearer <JWT_TOKEN>'
{
"id" : 1 ,
"username" : "admin" ,
"email" : "[email protected]" ,
"roles" : [
"ROLE_ADMIN"
]
}