Points Clés
- La sécurité en Java
- L’API Jakarta Security
- Tips concernant la sécurité en Java
- Cloud
- PaaS
Source: https://www.freepik.com/fotos-vetores-gratis/tecnologia
En dépit d'être un aspect crucial, la sécurité est un sujet peu discuté dans l'industrie du développement logiciel. En conséquence, de nombreuses décisions sont prises sans tenir compte de ce sujet. Il s'agit d'une série d'articles qui parleront de la sécurité dans le monde Jakarta EE avec Jakarta Security et des microservices dans le cloud. Dans cette première partie, nous allons créer un «Hello world» de la sécurité; nous discuterons un peu de son importance, des erreurs les plus courantes, en plus de créer notre premier contact avec l'API Jakarta Security.
L'information sûre est liée à la protection des données des utilisateurs et de l'entreprise elle-même, sur la base des piliers de l'ISO / CEI 17799: 2005 :
- Confidentialité : propriété qui limite l'accès à l'information uniquement aux entités légitimes, c'est-à-dire celles autorisées par le propriétaire de l'information
- Intégrité : propriété qui garantit que les informations manipulées conservent toutes les caractéristiques originales établies par le propriétaire des informations, y compris le contrôle des modifications et une garantie de son cycle de vie (actuel, intermédiaire et permanent
- Disponibilité : propriété qui garantit que les informations sont toujours disponibles pour un usage légitime, c'est-à-dire par les utilisateurs autorisés par le propriétaire des informations
- Authenticité : propriété qui garantit que les informations proviennent de la source annoncée et qu'elles n'ont pas fait l'objet de modifications au cours d'un processus
Les problèmes de sécurité entraînent d'énormes pertes pour les entreprises, que ce soit à court, moyen ou long terme. Après tout, même après que la correction a été effectuée par l'entreprise, il faut du temps pour revenir au niveau de confiance et de crédibilité que l'entreprise avait avant l'événement, ce qui donne un grand avantage à la concurrence.
Premiers conseils de sécurité Java
Une excellente façon de commencer à parler de sécurité en Java commence par quelques conseils simples pour éviter les brèches dans notre système. Donc, nous listons ici les plus grandes erreurs de sécurité :
- Encapsulation : il est impossible de commencer par des problèmes de sécurité et de ne pas mentionner le plus grand défi des applications Java : les problèmes d'encapsulation. En effet, en plus d'être un problème de type code smell laissant toutes les classes et tous les attributs publics, cette attitude génère plusieurs problèmes d'intégrité des données. Après tout, le gros point concernant la POO, selon les principes de garder un code «propre», est le fait de cacher les données pour exposer le comportement. Il est essentiel de toujours penser à avoir une API protégée.
- Requêtes concaténées : un problème répandu est d'utiliser des requêtes créés par concaténation de chaînes. Cela pourrait entraîner des injections SQL.
- Méfiez-vous des journaux : le journal est un point important, à la fois pour vérifier le comportement et pour vérifier les bogues. Il est impératif de faire attention aux données qui seront exposées, par exemple via la méthode «toString()».
- Attention lors de l'exposition de données sensibles : ce qui est très fréquent dans les microservices. Il est important de savoir quelles informations seront exposées et à qui. Il existe plusieurs façons d'éviter cela en ne pensant qu'aux microservices. Par exemple, utiliser la notation qui ignore un champ à sérialiser ou précise les données qui seront exposées en créant une couche DTO.
- Évitez la sérialisation Java : il est essentiel de faire attention à l'utilisation de cette fonctionnalité. Elle contient plusieurs problèmes de sécurité, utilisez donc l'interface Serializable uniquement si nécessaire. N'oubliez pas que, dans la grande majorité des cas, son utilisation n'est pas recommandée.
- Attention aux algorithmes de chiffrement ou de hachage : de toute évidence, l'utilisation du chiffrement est importante, alors soyez prudent avec la mise en œuvre.
- Attention à vos dépendances : plusieurs études estiment qu'environ 90% du code mis en production est lié à des projets tiers. Ainsi, il est impératif d'avoir toute l'attention requise lors de la mise à jour du logiciel, y compris la JVM elle-même. Gardez à l'esprit qu'en plus des nouvelles fonctionnalités et des améliorations de performances, les mises à jour aident à résoudre divers problèmes de sécurité si nous le faisons assez régulièrement.
- Mot de passe de la base de données : c'est l'un des plus gros problèmes de sécurité. Évitez de mettre le mot de passe dans le code. The Twelve Factor App parle beaucoup des avantages de la configuration. De plus, NoSQL ne signifie pas NoSecurity, il est donc important de saisir un mot de passe ou de restreindre l'accès à ce type de base de données. Selon une étude, environ 75% des serveurs Redis exposés au public n'utilisent pas de mot de passe.
- Accès aux serveurs et aux bases de données : ce problème est beaucoup plus lié aux opérations. Cependant, il est crucial de vérifier l'accès aux serveurs et aux bases de données. C'est-à-dire que le serveur accède aux bases de données dont il a besoin et que seuls les serveurs d'accès public et les ports nécessaires doivent être exposés.
- L'utilisation d'outils de sécurité n'est pas une mauvaise chose. Il existe maintenant des outils qui gèrent la sécurité, et les experts se concentrent sur cela. Bien pire que de payer pour une solution coûteuse et mature, c'est réinventer la roue avec une solution qui pose plusieurs problèmes de sécurité, sans parler du fait que cela se traduit par une perte de temps et de travail, faisant en sorte que l'attention se détourne de notre activité.
Une excellente astuce pour éviter un grand nombre de ces erreurs de sécurité est de consulter les 10 meilleures pratiques de sécurité Java écrites par Snyk.
Hello world
Après avoir expliqué les concepts de sécurité, créons une application Hello World à l'aide de Payara et de l'API Security fournie par Jakarta EE. Nous allons créer plusieurs ressources, et chacune d'elles renverra un texte simple. Cependant, chaque service disponible aura sa propre règle d'autorisation, étant donné que nous avons trois règles d'accès (gestionnaire, utilisateur et administrateur) et que nous aurons les services suivants :
- Celui auquel tout le monde peut accéder sans aucun problème
- Celui auquel seul l'administrateur accède
- Celui auquel seuls le gestionnaire et l'administrateur peuvent accéder
- Celui auquel l'utilisateur accède
- Celui auquel personne n'a accès. Mais quel est le but de cet exemple ? C'est une fonctionnalité qui n'est pas encore disponible pour aucun utilisateur
Si nous travaillons déjà avec JAX-RS, nous sommes habitués à créer une classe qui héritera d'Application et sera annotée avec ApplicationPath
. Dans ce cas, nous définirons les règles qui seront utilisées dans l'application. Dans notre cas, nous utiliserons également l'un des mécanismes d'authentification déjà fournis par l'API.
Par défaut, l'API de sécurité Jakarta EE fournit les mécanismes d'authentification suivants :
BasicAuthenticationMechanismDefinition
FormAuthenticationMechanismDefinition
CustomFormAuthenticationMechanismDefinition
Dans ce cas, nous utiliserons le mécanisme Basic
, que nous n'expliquerons pas dans cet article car nous aurons un article entièrement dédié à ce mécanisme.
import javax.annotation.security.DeclareRoles;
import javax.enterprise.context.ApplicationScoped;
import javax.security.enterprise.authentication.mechanism.http.BasicAuthenticationMechanismDefinition;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
@ApplicationPath("")
@BasicAuthenticationMechanismDefinition(realmName = "userRealm")
@ApplicationScoped
@DeclareRoles({"ADMIN", "MANAGER", "USER"}) // You need to indicate all roles that are used by the app
public class ApplicationConfig extends Application {
}
Dès que l'utilisateur envoie les informations via Basic
, nous devrons valider les informations d'identification. Autrement dit, l'utilisateur s'authentifie d'abord afin de pouvoir les vérifier. Avec Jakarta EE Security, ce processus de validation est effectué grâce à un IdentityStore
, qui peut avoir plusieurs implémentations, telles que des base de données, des fichiers ou LDAP. Dans notre exemple, ce sera vraiment simple et tout sera géré en mémoire avec le nom d'utilisateur.
import javax.enterprise.context.ApplicationScoped;
import javax.security.enterprise.credential.Credential;
import javax.security.enterprise.credential.UsernamePasswordCredential;
import javax.security.enterprise.identitystore.CredentialValidationResult;
import javax.security.enterprise.identitystore.IdentityStore;
import java.util.Collections;
import static javax.security.enterprise.identitystore.CredentialValidationResult.INVALID_RESULT;
@ApplicationScoped
public class InMemoryIdentityStore implements IdentityStore {
@Override
public int priority() {
return 10;
}
@Override
public CredentialValidationResult validate(Credential credential) {
if (credential instanceof UsernamePasswordCredential) {
UsernamePasswordCredential user = UsernamePasswordCredential
.class.cast(credential);
switch (user.getCaller()) {
case "admin":
return new CredentialValidationResult("admin", Collections.singleton("ADMIN"));
case "manager":
return new CredentialValidationResult("admin", Collections.singleton("MANAGER"));
case "user":
return new CredentialValidationResult("admin", Collections.singleton("USER"));
default:
return INVALID_RESULT;
}
}
return INVALID_RESULT;
}
}
Après ce mécanisme de validation, la dernière étape consiste à créer les services et à les identifier selon la règle d'autorisation à appliquer.
import javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.enterprise.context.RequestScoped;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
@Path("")
@RequestScoped
public class HelloWorldResource {
@GET
@PermitAll
@Produces("text/plain")
public String doGet() {
return "hello from everyone";
}
@Path("admin")
@GET
@RolesAllowed("ADMIN")
@Produces("text/plain")
public String admin() {
return "hello from admin";
}
@Path("manager")
@GET
@RolesAllowed({"MANAGER", "ADMIN"})
@Produces("text/plain")
public String manager() {
return "hello from manager";
}
@Path("user")
@GET
@RolesAllowed({"MANAGER", "ADMIN", "USER"})
@Produces("text/plain")
public String user() {
return "hello from user";
}
@Path("nobody")
@GET
@DenyAll
@Produces("text/plain")
public String nobody() {
return "hello from nobody";
}
}
L'application est prête à être utilisée ! Une chose intéressante à propos de cet exemple est que nous pouvons tester les retours et les codes respectifs, comme dans les situations 401 et 403.
Passer au cloud
J'ai eu le plaisir de parler à plusieurs reprises des avantages de l'utilisation du cloud computing sans commenter son risque, selon le type de service choisi. L'un des avantages importants du PaaS est l'abstraction de la couche d'infrastructure, réduisant considérablement le risque de migration vers le cloud, y compris la sécurité. Avoir une équipe de support qui sera chargée de contrôler l'accès aux serveurs et aux conteneurs au sein du cluster, en plus du processus de mise à jour de la base de données et de l'automatisation de la sauvegarde, sont des points critiques et très abstraits qu'un PaaS peut offrir.
Pour faciliter la migration du code local vers un environnement cloud, nous utiliserons un PaaS, dans ce cas, Platform.sh. En un mot, c'est la deuxième génération de PaaS qui gérera toutes les ressources pour nous en suivant le concept d'infrastructure as code. Poussez notre référentiel Git et le PaaS sera responsable de la création des conteneurs, de la configuration des autorisations d'accès au sein du cluster et de la finalisation du déploiement en production.
Pour effectuer le déploiement, nous avons besoin de trois fichiers : un pour définir les informations de l'application, un autre pour les services dont l'application a besoin et le dernier pour définir les itinéraires. Pour que nous ayons les éléments suivants :
"https://{default}/":
type: upstream
upstream: "app:http"
"https://www.{default}/":
type: redirect
to: "https://{default}/"
Pour configurer l'application :
name: app
type: "java:11"
disk: 1024
hooks:
build: mvn clean package payara-micro:bundle
web:
commands:
start: java -jar -Xmx$(jq .info.limits.memory /run/config.json)m -XX:+ExitOnOutOfMemoryError target/microprofile-microbundle.jar --port $PORT
Comme nous utilisons tout en mémoire, il n'est pas nécessaire d'utiliser le fichier de service.
Dans cet article, nous parlons un peu de l'aspect sécurité, de l'importance d'y penser. Une chose importante au niveau du code esyque nous avons pu faire cet exemple pratique avec seulement trois classes. Cela démontre clairement qu'il y a eu une amélioration considérable de l'API Security dans les spécifications Java. Dans la deuxième partie, nous parlerons un peu de BASIC
, de ses avantages et inconvénients.
Pour voir l'application finale de cette série, jetez un œil à ce lien.
A propos de l'auteur
Otávio Santana est un ingénieur logiciel avec une vaste expérience dans le développement open source, avec plusieurs contributions à JBoss Weld, Hibernate, Apache Commons et à d'autres projets. Axé sur le développement multilingue et les applications haute performance, Otávio a travaillé sur de grands projets dans les domaines de la finance, du gouvernement, des médias sociaux et du commerce électronique. Membre du comité exécutif du JCP et de plusieurs groupes d'experts JSR, il est également un champion Java et a reçu le JCP Outstanding Award et le Duke's Choice Award.