Spring Boot - OAuth2 con JWT
In questo capitolo imparerai in dettaglio i meccanismi di Spring Boot Security e OAuth2 con JWT.
Server di autorizzazione
Authorization Server è un componente architettonico supremo per Web API Security. Il server di autorizzazione funge da punto di autorizzazione di centralizzazione che consente alle app e agli endpoint HTTP di identificare le funzionalità della tua applicazione.
Resource Server
Resource Server è un'applicazione che fornisce il token di accesso ai client per accedere agli endpoint HTTP di Resource Server. È una raccolta di librerie che contiene gli endpoint HTTP, le risorse statiche e le pagine Web dinamiche.
OAuth2
OAuth2 è un framework di autorizzazione che consente all'applicazione Web Security di accedere alle risorse dal client. Per creare un'applicazione OAuth2, dobbiamo concentrarci sul tipo di concessione (codice di autorizzazione), sull'ID client e sul segreto del client.
Token JWT
Il token JWT è un token Web JSON, utilizzato per rappresentare le rivendicazioni protette tra due parti. Puoi saperne di più sul token JWT su www.jwt.io/ .
Ora, costruiremo un'applicazione OAuth2 che abiliti l'uso di Authorization Server, Resource Server con l'aiuto di un token JWT.
È possibile utilizzare i seguenti passaggi per implementare Spring Boot Security con token JWT accedendo al database.
Innanzitutto, dobbiamo aggiungere le seguenti dipendenze nel nostro file di configurazione della build.
Gli utenti di Maven possono aggiungere le seguenti dipendenze nel file pom.xml.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
Gli utenti Gradle possono aggiungere le seguenti dipendenze nel file build.gradle.
compile('org.springframework.boot:spring-boot-starter-security')
compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile('org.springframework.security:spring-security-test')
compile("org.springframework.security.oauth:spring-security-oauth2")
compile('org.springframework.security:spring-security-jwt')
compile("org.springframework.boot:spring-boot-starter-jdbc")
compile("com.h2database:h2:1.4.191")
dove,
Spring Boot Starter Security - Implementa la Spring Security
Spring Security OAuth2 - Implementa la struttura OAUTH2 per abilitare il server di autorizzazione e il server di risorse.
Spring Security JWT - Genera il token JWT per la sicurezza Web
Spring Boot Starter JDBC - Accede al database per assicurarsi che l'utente sia disponibile o meno.
Spring Boot Starter Web - Scrive endpoint HTTP.
H2 Database - Memorizza le informazioni dell'utente per l'autenticazione e l'autorizzazione.
Di seguito viene fornito il file di configurazione completo della build.
<?xml version = "1.0" encoding = "UTF-8"?>
<project xmlns = "http://maven.apache.org/POM/4.0.0"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tutorialspoint</groupId>
<artifactId>websecurityapp</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>websecurityapp</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Gradle – build.gradle
buildscript {
ext {
springBootVersion = '1.5.9.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
group = 'com.tutorialspoint'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-security')
compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile('org.springframework.security:spring-security-test')
compile("org.springframework.security.oauth:spring-security-oauth2")
compile('org.springframework.security:spring-security-jwt')
compile("org.springframework.boot:spring-boot-starter-jdbc")
compile("com.h2database:h2:1.4.191")
}
Ora, nell'applicazione Spring Boot principale, aggiungi l'annotazione @EnableAuthorizationServer e @EnableResourceServer per agire come server di autenticazione e server di risorse nella stessa applicazione.
Inoltre, puoi utilizzare il codice seguente per scrivere un semplice endpoint HTTP per accedere all'API con Spring Security utilizzando il token JWT.
package com.tutorialspoint.websecurityapp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@EnableAuthorizationServer
@EnableResourceServer
@RestController
public class WebsecurityappApplication {
public static void main(String[] args) {
SpringApplication.run(WebsecurityappApplication.class, args);
}
@RequestMapping(value = "/products")
public String getProductName() {
return "Honey";
}
}
Utilizzare il codice seguente per definire la classe POJO per memorizzare le informazioni sull'utente per l'autenticazione.
package com.tutorialspoint.websecurityapp;
import java.util.ArrayList;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
public class UserEntity {
private String username;
private String password;
private Collection<GrantedAuthority> grantedAuthoritiesList = new ArrayList<>();
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Collection<GrantedAuthority> getGrantedAuthoritiesList() {
return grantedAuthoritiesList;
}
public void setGrantedAuthoritiesList(Collection<GrantedAuthority> grantedAuthoritiesList) {
this.grantedAuthoritiesList = grantedAuthoritiesList;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
Utilizzare ora il codice seguente e definire la classe CustomUser che estende la classe org.springframework.security.core.userdetails.User per l'autenticazione Spring Boot.
package com.tutorialspoint.websecurityapp;
import org.springframework.security.core.userdetails.User;
public class CustomUser extends User {
private static final long serialVersionUID = 1L;
public CustomUser(UserEntity user) {
super(user.getUsername(), user.getPassword(), user.getGrantedAuthoritiesList());
}
}
È possibile creare la classe @Repository per leggere le informazioni sull'utente dal database e inviarle al servizio utente personalizzato e aggiungere anche l'autorizzazione concessa "ROLE_SYSTEMADMIN".
package com.tutorialspoint.websecurityapp;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Repository;
@Repository
public class OAuthDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public UserEntity getUserDetails(String username) {
Collection<GrantedAuthority> grantedAuthoritiesList = new ArrayList<>();
String userSQLQuery = "SELECT * FROM USERS WHERE USERNAME=?";
List<UserEntity> list = jdbcTemplate.query(userSQLQuery, new String[] { username },
(ResultSet rs, int rowNum) -> {
UserEntity user = new UserEntity();
user.setUsername(username);
user.setPassword(rs.getString("PASSWORD"));
return user;
});
if (list.size() > 0) {
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_SYSTEMADMIN");
grantedAuthoritiesList.add(grantedAuthority);
list.get(0).setGrantedAuthoritiesList(grantedAuthoritiesList);
return list.get(0);
}
return null;
}
}
È possibile creare una classe del servizio dettagli utente personalizzato che estende org.springframework.security.core.userdetails.UserDetailsService per chiamare la classe del repository DAO come mostrato.
package com.tutorialspoint.websecurityapp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class CustomDetailsService implements UserDetailsService {
@Autowired
OAuthDao oauthDao;
@Override
public CustomUser loadUserByUsername(final String username) throws UsernameNotFoundException {
UserEntity userEntity = null;
try {
userEntity = oauthDao.getUserDetails(username);
CustomUser customUser = new CustomUser(userEntity);
return customUser;
} catch (Exception e) {
e.printStackTrace();
throw new UsernameNotFoundException("User " + username + " was not found in the database");
}
}
}
Quindi, creare una classe @configuration per abilitare Web Security, definendo il codificatore di password (BCryptPasswordEncoder) e definendo il bean AuthenticationManager. La classe di configurazione Security dovrebbe estendere la classe WebSecurityConfigurerAdapter.
package com.tutorialspoint.websecurityapp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private CustomDetailsService customDetailsService;
@Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
@Override
@Autowired
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customDetailsService).passwordEncoder(encoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.NEVER);
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
Ora, definire la classe di configurazione OAuth2 per aggiungere l'ID client, il segreto client, definire il JwtAccessTokenConverter, la chiave privata e la chiave pubblica per la chiave del firmatario del token e della chiave del verificatore e configurare ClientDetailsServiceConfigurer per la validità del token con gli ambiti.
package com.tutorialspoint.websecurityapp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
@Configuration
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
private String clientid = "tutorialspoint";
private String clientSecret = "my-secret-key";
private String privateKey = "private key";
private String publicKey = "public key";
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Bean
public JwtAccessTokenConverter tokenEnhancer() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(privateKey);
converter.setVerifierKey(publicKey);
return converter;
}
@Bean
public JwtTokenStore tokenStore() {
return new JwtTokenStore(tokenEnhancer());
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager).tokenStore(tokenStore())
.accessTokenConverter(tokenEnhancer());
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient(clientid).secret(clientSecret).scopes("read", "write")
.authorizedGrantTypes("password", "refresh_token").accessTokenValiditySeconds(20000)
.refreshTokenValiditySeconds(20000);
}
}
Ora, crea una chiave privata e una chiave pubblica utilizzando openssl.
È possibile utilizzare i seguenti comandi per generare la chiave privata.
openssl genrsa -out jwt.pem 2048
openssl rsa -in jwt.pem
È possibile utilizzare Per la generazione della chiave pubblica utilizzare i comandi seguenti.
openssl rsa -in jwt.pem -pubout
Per la versione di Spring Boot successiva alla 1.5, aggiungi la proprietà seguente nel tuo file application.properties per definire l'ordine del filtro delle risorse OAuth2.
security.oauth2.resource.filter-order=3
Gli utenti del file YAML possono aggiungere la seguente proprietà nel file YAML.
security:
oauth2:
resource:
filter-order: 3
Ora, crea il file schema.sql e data.sql nelle risorse del percorso di classe src/main/resources/directory per connettere l'applicazione al database H2.
Il file schema.sql è come mostrato -
CREATE TABLE USERS (ID INT PRIMARY KEY, USERNAME VARCHAR(45), PASSWORD VARCHAR(60));
Il file data.sql è come mostrato -
INSERT INTO USERS (ID, USERNAME,PASSWORD) VALUES (
1, '[email protected]','$2a$08$fL7u5xcvsZl78su29x1ti.dxI.9rYO8t0q5wk2ROJ.1cdR53bmaVG');
INSERT INTO USERS (ID, USERNAME,PASSWORD) VALUES (
2, '[email protected]','$2a$08$fL7u5xcvsZl78su29x1ti.dxI.9rYO8t0q5wk2ROJ.1cdR53bmaVG');
Note - La password deve essere memorizzata nel formato di Bcrypt Encoder nella tabella del database.
È possibile creare un file JAR eseguibile ed eseguire l'applicazione Spring Boot utilizzando i seguenti comandi Maven o Gradle.
Per Maven, puoi usare il comando indicato di seguito:
mvn clean install
Dopo "BUILD SUCCESS", è possibile trovare il file JAR nella directory di destinazione.
Per Gradle, puoi usare il comando come mostrato -
gradle clean build
Dopo "BUILD SUCCESSFUL", è possibile trovare il file JAR nella directory build / libs.
Ora, esegui il file JAR usando il comando mostrato qui -
java –jar <JARFILE>
L'applicazione viene avviata sulla porta Tomcat 8080.
Ora premi l'URL del metodo POST tramite POSTMAN per ottenere il token OAUTH2.
http://localhost:8080/oauth/token
Ora aggiungi le intestazioni della richiesta come segue:
Authorization - Autenticazione di base con il tuo ID cliente e segreto cliente.
Content Type - application / x-www-form-urlencoded
Ora aggiungi i parametri di richiesta come segue:
- grant_type = password
- username = il tuo nome utente
- password = la tua password
Ora premi l'API e ottieni access_token come mostrato -
Ora, premi l'API del server delle risorse con il token di accesso al portatore nell'intestazione della richiesta come mostrato.
Quindi puoi vedere l'output come mostrato di seguito -