Jakarta EE 8-beveiligings-API

1. Overzicht

De Jakarta EE 8 Security API is de nieuwe standaard en een draagbare manier om beveiligingsproblemen in Java-containers af te handelen.

In dit artikel, we zullen de drie kernfuncties van de API bekijken:

  1. HTTP-authenticatiemechanisme
  2. Identiteitswinkel
  3. Beveiligingscontext

We zullen eerst begrijpen hoe u de geleverde implementaties configureert en vervolgens hoe u een aangepaste implementeert.

2. Maven afhankelijkheden

Om de Jakarta EE 8 Security API in te stellen, hebben we een door de server geleverde implementatie of een expliciete implementatie nodig.

2.1. Met behulp van de serverimplementatie

Servers die voldoen aan Jakarta EE 8 bieden al een implementatie voor de Jakarta EE 8 Security API, en daarom hebben we alleen het Jakarta EE Web Profile API Maven-artefact nodig:

  javax javaee-web-api 8.0 voorzien 

2.2. Met behulp van een expliciete implementatie

Eerst specificeren we het Maven-artefact voor de Jakarta EE 8 Security API:

  javax.security.enterprise javax.security.enterprise-api 1.0 

En dan voegen we een implementatie toe, bijvoorbeeld Soteria - de referentie-implementatie:

  org.glassfish.soteria javax.security.enterprise 1.0 

3. HTTP-authenticatiemechanisme

Voorafgaand aan Jakarta EE 8 hebben we authenticatiemechanismen declaratief geconfigureerd via het web.xml het dossier.

In deze versie heeft de Jakarta EE 8 Security API de nieuwe HttpAuthenticationMechanism interface als vervanging. Daarom kunnen webtoepassingen nu verificatiemechanismen configureren door implementaties van deze interface te bieden.

Gelukkig biedt de container al een implementatie voor elk van de drie authenticatiemethoden die zijn gedefinieerd door de Servlet-specificatie: Basic HTTP-authenticatie, formuliergebaseerde authenticatie en aangepaste formuliergebaseerde authenticatie.

Het biedt ook een annotatie om elke implementatie te activeren:

  1. @BasicAuthenticationMechanismDefinition
  2. @FormAuthenticationMechanismDefinition
  3. @CustomFormAuthenrticationMechanismDefinition

3.1. Basis HTTP-verificatie

Zoals hierboven vermeld, kan een webtoepassing de basis HTTP-verificatie configureren door gewoon de @BasicAuthenticationMechanismDefinition annotatie op een CDI-bean:

@BasicAuthenticationMechanismDefinition (realmName = "userRealm") @ApplicationScoped openbare klasse AppConfig {}

Op dit punt zoekt en instantieert de Servlet-container de geleverde implementatie van HttpAuthenticationMechanism koppel.

Bij ontvangst van een ongeautoriseerd verzoek, daagt de container de klant uit om geschikte authenticatie-informatie te verstrekken via de WWW-Authenticatie antwoord header.

WWW-Authenticate: Basic realm = "userRealm"

De client stuurt vervolgens de gebruikersnaam en het wachtwoord, gescheiden door een dubbele punt ":" en gecodeerd in Base64, via de Autorisatie verzoek header:

// gebruiker = baeldung, wachtwoord = baeldung Autorisatie: Basic YmFlbGR1bmc6YmFlbGR1bmc = 

Houd er rekening mee dat het dialoogvenster voor het verstrekken van inloggegevens afkomstig is van de browser en niet van de server.

3.2. Formuliergebaseerde HTTP-verificatie

De @FormAuthenticationMechanismDefinition annotatie activeert een op formulieren gebaseerde authenticatie zoals gedefinieerd door de Servlet-specificatie.

Dan hebben we de mogelijkheid om de login- en foutpagina's te specificeren of de standaard redelijke te gebruiken /Log in en /login fout:

@FormAuthenticationMechanismDefinition (loginToContinue = @LoginToContinue (loginPage = "/login.html", errorPage = "/login-error.html")) @ApplicationScoped openbare klasse AppConfig {}

Als gevolg van een beroep login pagina, de server moet het formulier naar de klant sturen:

De klant moet het formulier vervolgens naar een vooraf gedefinieerd back-authenticatieproces sturen dat door de container wordt geleverd.

3.3. Aangepast formuliergebaseerde HTTP-verificatie

Een webtoepassing kan de op maat gemaakte, op formulieren gebaseerde authenticatie-implementatie activeren door de annotatie te gebruiken @CustomFormAuthenticationMechanismDefinition:

@CustomFormAuthenticationMechanismDefinition (loginToContinue = @LoginToContinue (loginPage = "/login.xhtml")) @ApplicationScoped openbare klasse AppConfig {}

Maar in tegenstelling tot de standaard op formulieren gebaseerde authenticatie, configureren we een aangepaste inlogpagina en roepen we de SecurityContext.authenticate () methode als een ondersteunend authenticatieproces.

Laten we de achterkant eens bekijken LoginBean ook, die de inloglogica bevat:

@Named @RequestScoped openbare klasse LoginBean {@Inject private SecurityContext securityContext; @NotNull private String gebruikersnaam; @NotNull privé String-wachtwoord; openbare ongeldige login () {Credential credential = new UsernamePasswordCredential (gebruikersnaam, nieuw wachtwoord (wachtwoord)); AuthenticationStatus status = securityContext .authenticate (getHttpRequestFromFacesContext (), getHttpResponseFromFacesContext (), withParams (). Referentie (referentie)); // ...} // ...}

Als gevolg van het inroepen van de gewoonte login.xhtml pagina, verzendt de klant het ontvangen formulier naar de LoginBean 's Log in() methode:

//... 

3.4. Aangepast authenticatiemechanisme

De HttpAuthenticationMechanism interface definieert drie methoden. Het belangrijkste is de validateRequest () die we voor een implementatie moeten zorgen.

Het standaardgedrag voor de andere twee methoden, secureResponse () en cleanSubject (), is in de meeste gevallen voldoende.

Laten we eens kijken naar een voorbeeldimplementatie:

@ApplicationScoped openbare klasse CustomAuthentication implementeert HttpAuthenticationMechanism {@Override openbare AuthenticationStatus validateRequest (HttpServletRequest-verzoek, HttpServletResponse-antwoord, HttpMessageContext httpMsgContext) gooit AuthenticationException {String) gebruikersnaam = request.gebruikersnaamParameter; String wachtwoord = response.getParameter ("wachtwoord"); // UserDetail bespotten, maar in het echte leven kunnen we het verkrijgen uit een database UserDetail userDetail = findByUserNameAndPassword (gebruikersnaam, wachtwoord); if (userDetail! = null) {return httpMsgContext.notifyContainerAboutLogin (nieuwe CustomPrincipal (userDetail), nieuwe HashSet (userDetail.getRoles ())); } retourneer httpMsgContext.responseUnauthorized (); } // ...}

Hier biedt de implementatie de bedrijfslogica van het validatieproces, maar in de praktijk wordt aanbevolen om te delegeren naar het IdentityStore door het IdentityStoreHandler by aanroepen valideren.

We hebben de implementatie ook geannoteerd met @ApplicationScoped annotatie omdat we het CDI-enabled moeten maken.

Na een geldige verificatie van de legitimatie en het uiteindelijk ophalen van gebruikersrollen, de implementatie moet de container dan op de hoogte stellen:

HttpMessageContext.notifyContainerAboutLogin (Principal principal, Set groups)

3.5. Servlet-beveiliging afdwingen

Een webtoepassing kan beveiligingsbeperkingen afdwingen door de @ te gebruikenServletSecurity annotatie op een Servlet-implementatie:

@WebServlet ("/ secure") @ServletSecurity (value = @HttpConstraint (roleAllowed = {"admin_role"}), httpMethodConstraints = {@HttpMethodConstraint (value = "GET", rollsAllowed = {"user_role"}), @HttpMeth = "POST", rollsAllowed = {"admin_role"})}) openbare klasse SecuredServlet breidt HttpServlet uit {}

Deze annotatie heeft twee attributen - httpMethodConstraints en waarde; httpMethodConstraints wordt gebruikt om een ​​of meer beperkingen op te geven, die elk een toegangscontrole tot een HTTP-methode vertegenwoordigen door middel van een lijst met toegestane rollen.

De container controleert dan voor elke url-patroon en HTTP-methode, als de verbonden gebruiker de geschikte rol heeft voor toegang tot de bron.

4. Identiteitsopslag

Deze functie wordt geabstraheerd door de IdentityStore interface, en het wordt gebruikt om inloggegevens te valideren en uiteindelijk het groepslidmaatschap op te halen. Met andere woorden, het kan mogelijkheden bieden voor authenticatie, autorisatie of beide.

IdentityStore is bedoeld en wordt aangemoedigd om te worden gebruikt door de HttpAuthenticationMecanism via een gebeld IdentityStoreHandler koppel. Een standaardimplementatie van het IdentityStoreHandler wordt geleverd door de Servletcontainer.

Een applicatie kan de implementatie van het IdentityStore of gebruikt een van de twee ingebouwde implementaties van de container voor Database en LDAP.

4.1. Ingebouwde identiteitsarchieven

De Jakarta EE-compatibele server zou implementaties moeten bieden voor de twee identiteitsarchieven: database en LDAP.

De databank IdentityStore implementatie wordt geïnitialiseerd door configuratiegegevens door te geven aan het @DataBaseIdentityStoreDefinition annotatie:

@DatabaseIdentityStoreDefinition (dataSourceLookup = "java: comp / env / jdbc / securityDS", callerQuery = "selecteer wachtwoord van gebruikers waar gebruikersnaam =?", GroupsQuery = "selecteer GROEPSNAAM uit groepen waar gebruikersnaam =?", Prioriteit = 30) @ApplicationScoped openbaar class AppConfig {}

Als configuratiegegevens, we hebben een JNDI-gegevensbron nodig voor een externe database, twee JDBC-instructies voor het controleren van de beller en zijn groepen en tenslotte een prioriteitsparameter die wordt gebruikt in het geval van een meervoudige opslag zijn geconfigureerd.

IdentityStore met hoge prioriteit wordt later verwerkt door de IdentityStoreHandler.

Net als de database, De implementatie van LDAP IdentityStore wordt geïnitialiseerd via het @LdapIdentityStoreDefinition door configuratiegegevens door te geven:

@LdapIdentityStoreDefinition (url = "ldap: // localhost: 10389", callerBaseDn = "ou = beller, dc = baeldung, dc = com", groupSearchBase = "ou = groep, dc = baeldung, dc = com", groupSearchFilter = " (& (member =% s) (objectClass = groupOfNames)) ") @ApplicationScoped openbare klasse AppConfig {}

Hier hebben we de URL van een externe LDAP-server nodig, hoe de beller in de LDAP-directory kan worden gezocht en hoe zijn groepen kunnen worden opgehaald.

4.2. Implementeren van een Custom IdentityStore

De IdentityStore interface definieert vier standaardmethoden:

default CredentialValidationResult validate (Credential referentie) default Set getCallerGroups (CredentialValidationResult validationResult) default int prioriteit () default Set validationTypes ()

De prioriteit() methode retourneert een waarde voor de volgorde van iteratie waarmee deze implementatie wordt verwerkt IdentityStoreHandler. Een IdentityStore met lagere prioriteit wordt als eerste behandeld.

Standaard is een IdentityStore verwerkt beide inloggegevens validatie (Validatietype.VALIDATE) en het ophalen van groepen (Validatietype.PROVIDE_GROUPS). We kunnen dit gedrag opheffen zodat het slechts één mogelijkheid kan bieden.

Zo kunnen we het IdentityStore alleen te gebruiken voor validatie van inloggegevens:

@Override public Set validationTypes () {return EnumSet.of (ValidationType.VALIDATE); }

In dit geval moeten we zorgen voor een implementatie voor het valideren () methode:

@ApplicationScoped openbare klasse InMemoryIdentityStore implementeert IdentityStore {// init vanuit een bestand of harcoded privékaartgebruikers = nieuwe HashMap (); @Override public int priority () {return 70; } @Override public Set validationTypes () {return EnumSet.of (ValidationType.VALIDATE); } openbare CredentialValidationResult valideren (UsernamePasswordCredential referentie) {UserDetails user = users.get (credential.getCaller ()); if (credential.compareTo (user.getLogin (), user.getPassword ())) {retourneer nieuwe CredentialValidationResult (user.getLogin ()); } retourneer INVALID_RESULT; }}

Of we kunnen ervoor kiezen om het IdentityStore zodat het alleen kan worden gebruikt voor het ophalen van groepen:

@Override public Set validationTypes () {return EnumSet.of (ValidationType.PROVIDE_GROUPS); }

We moeten dan zorgen voor een implementatie voor het getCallerGroups () methoden:

@ApplicationScoped openbare klasse InMemoryIdentityStore implementeert IdentityStore {// init vanuit een bestand of harcoded privékaartgebruikers = nieuwe HashMap (); @Override public int priority () {return 90; } @Override public Set validationTypes () {return EnumSet.of (ValidationType.PROVIDE_GROUPS); } @Override public Set getCallerGroups (CredentialValidationResult validationResult) {UserDetails user = users.get (validationResult.getCallerPrincipal (). GetName ()); retourneer nieuwe HashSet (user.getRoles ()); }}

Omdat IdentityStoreHandler verwacht dat de implementatie een CDI-boon is, waarmee we decoreren ApplicationScoped annotatie.

5. Beveiligingscontext-API

De Jakarta EE 8 Security API biedt een toegangspunt tot programmatische beveiliging via de SecurityContext koppel. Het is een alternatief wanneer het declaratieve beveiligingsmodel dat door de container wordt afgedwongen, niet voldoende is.

Een standaardimplementatie van het SecurityContext interface moet tijdens runtime worden geleverd als een CDI-bean, en daarom moeten we deze injecteren:

@Inject SecurityContext securityContext;

Op dit punt kunnen we de gebruiker verifiëren, een geverifieerde gebruiker ophalen, zijn rollidmaatschap controleren en toegang tot webbronnen verlenen of weigeren via de vijf beschikbare methoden.

5.1. Beller-gegevens ophalen

In eerdere versies van Jakarta EE haalden we het Opdrachtgever of controleer het rollidmaatschap in elke container anders.

Terwijl we de getUserPrincipal () en isUserInRole () methoden van de HttpServletRequest in een servlet-container, een vergelijkbare methode getCallerPrincipal () en isCallerInRole () - methoden van de EJBContext worden gebruikt in EJB Container.

De nieuwe Jakarta EE 8 Security API heeft dit gestandaardiseerd door het verstrekken van een vergelijkbare methode via de SecurityContext koppel:

HoofdgetCallerPrincipal (); boolean isCallerInRole (String-rol); GetPrincipalsByType (klassetype) instellen;

De getCallerPrincipal () methode retourneert een containerspecifieke representatie van de geverifieerde beller terwijl de getPrincipalsByType () methode haalt alle principals van een bepaald type op.

Het kan handig zijn als de applicatiespecifieke beller anders is dan de containerbeller.

5.2. Testen voor toegang tot webbronnen

Eerst moeten we een beschermde bron configureren:

@WebServlet ("/ protectedServlet") @ServletSecurity (@HttpConstraint (roleAllowed = "USER_ROLE")) openbare klasse ProtectedServlet breidt HttpServlet uit {// ...}

En dan, om de toegang tot deze beschermde bron te controleren, moeten we de hasAccessToWebResource () methode:

securityContext.hasAccessToWebResource ("/ protectedServlet", "GET");

In dit geval retourneert de methode true als de gebruiker een rol heeft GEBRUIKERSROL.

5.3. De beller programmatisch verifiëren

Een toepassing kan het authenticatieproces programmatisch activeren door een beroep te doen op authenticeren ():

AuthenticationStatus authenticate (HttpServletRequest-verzoek, HttpServletResponse-antwoord, AuthenticationParameters-parameters);

De container wordt vervolgens op de hoogte gebracht en zal op zijn beurt het authenticatiemechanisme aanroepen dat voor de applicatie is geconfigureerd. AuthenticationParameters parameter biedt een referentie voor HttpAuthenticationMechanism:

withParams (). referentie (referentie)

De SUCCES en SEND_FAILURE waarden van de Authenticatiestatus ontwerp een succesvolle en mislukte authenticatie terwijl SEND_CONTINUE signaleert een lopende status van het authenticatieproces.

6. De voorbeelden uitvoeren

Om deze voorbeelden te benadrukken, hebben we de laatste ontwikkelversie van de Open Liberty Server gebruikt die Jakarta EE 8 ondersteunt. Deze wordt gedownload en geïnstalleerd dankzij de liberty-maven-plug-in die ook de applicatie kan inzetten en de server kan starten.

Om de voorbeelden uit te voeren, gaat u gewoon naar de corresponderende module en roept u deze opdracht op:

mvn schoon pakket liberty: run

Als gevolg hiervan zal Maven de server downloaden, de applicatie bouwen, implementeren en uitvoeren.

7. Conclusie

In dit artikel hebben we de configuratie en implementatie van de belangrijkste functies van de nieuwe Jakarta EE 8 Security API besproken.

Allereerst hebben we laten zien hoe u de standaard ingebouwde authenticatiemechanismen configureert en hoe u een aangepast authenticatiemechanisme implementeert. Later hebben we gezien hoe we de ingebouwde Identity Store kunnen configureren en hoe we een aangepaste store kunnen implementeren. En tot slot hebben we gezien hoe we methoden van de SecurityContext.

Zoals altijd zijn de codevoorbeelden voor dit artikel beschikbaar op GitHub.