Benutzerverwaltung mit Okta SDK und Spring Boot
In diesem Beitrag werde ich zeigen, wie wir Benutzerverwaltung und -authentifizierung mit Okta SDK und Spring Boot aufbauen können.
Einführung
Als Teil jeder Anwendung müssen Entwickler darauf achten, wie sie die Authentifizierung erstellen. Obwohl wir seit langem die formularbasierte Authentifizierung verwenden, ist sie nicht die sicherste. In diesem Beitrag möchte ich die formularbasierte Authentifizierung zeigen, bei der Benutzer nicht unbedingt authentifiziert werden, indem ihr verschlüsseltes Passwort mit dem in einer Datenbank gespeicherten Passwort validiert wird. Wenn Sie mehr über Spring Security mit verschiedenen Authentifizierungsabläufen erfahren möchten, habe ich kürzlich ein Buch Simplifying Spring Security veröffentlicht. Sie können das Buch hier kaufen.
Okta ist ein Identitätsanbieter. Es ist eine Anwendung, die Benutzerverwaltung und -authentifizierung mit verschiedenen Protokollen bereitstellt.
Okta SDK-APIs
Okta bietet zwei Bibliotheken okta-sdk-java
und okta-auth-java
für Benutzerverwaltungs-APIs und Authentifizierung.
Sind diese Bibliotheken das Richtige für Sie? Dies hängt von Ihrem Anwendungsfall ab. Okta bietet auch okta-spring-boot-starter
an Bibliothek, um Okta für verschiedene OAuth-Flows in Ihrer Spring Boot-Anwendung zu verwenden. Wir werden diese Bibliothek in dieser Demo nicht verwenden.
Weitere Einzelheiten zu diesen Bibliotheken finden Sie hier und hier.
Binden Sie diese Bibliotheken wie folgt in Ihr Projekt ein:
implementation 'com.okta.authn.sdk:okta-authn-sdk-api:2.0.1'
runtimeOnly 'com.okta.authn.sdk:okta-authn-sdk-impl:2.0.1'
runtimeOnly 'com.okta.sdk:okta-sdk-httpclient:3.0.1'
Benutzerverwaltung mit Okta SDK in der Spring Boot-Anwendung
In dieser Demo habe ich eine Beispielanwendung der Aufgabenliste. Wenn ein Benutzer die Anwendung startet, wird dem Benutzer ein Anmeldebildschirm angezeigt. Es hat eine Anmeldeoption. Wenn der Benutzer in der Anwendung nicht vorhanden ist, muss der Benutzer ein Konto erstellen.
Wenn ein Benutzer auf der Anmeldeseite auf die Schaltfläche „Senden“ klickt, speichern wir den Benutzer in unserer Datenbank und rufen dann die Okta SDK-API auf, um den Benutzer auf Okta-Seite zu erstellen.
Um dies zu erreichen, benötigen wir Okta Client.
@Bean
public Client client()
{
Client clientConfig =
Clients.builder().setOrgUrl("https://oktadomainurl").setClientCredentials(new TokenClientCredentials(secret))
.build();
return clientConfig;
}
Wie Sie oben sehen, erstellen wir einen Client, mit dem wir die Okta-API aufrufen. Das „Geheimnis“ ist das API-Token, das Sie in der Okta-Admin-Benutzeroberfläche finden können. Wenn Sie es nicht finden, haben Sie entweder keine Administratorrechte oder Sie haben das Token noch nicht erstellt. Es gibt eine andere Möglichkeit, diesen Client mit einem Zugriffstoken zu erstellen.
@Bean
public Client client()
{
Client clientConfig =
Clients.builder().setOrgUrl("https://oktadomainurl")
.setAuthorizationMode(AuthorizationMode.PRIVATE_KEY).setClientId("{clientId}")
.setScopes(new HashSet<>(Arrays.asList("okta.users.read", "okta.apps.read")))
.setPrivateKey("/path/to/yourPrivateKey.pem")
return clientConfig;
}
Der Vorteil dieser Client-Konfiguration besteht darin, dass Sie kein API-Zugriffstoken kennen müssen, das basierend auf Administratorrechten erstellt wurde.
Jetzt werde ich auf meiner Controller-Seite diesen Client verwenden, um Benutzer in Okta wie folgt zu erstellen:
UserDto userDto = new UserDto();
userDto.setEmail(email);
userDto.setFirstName(firstname);
userDto.setLastName(lastname);
userDto.setPassword(encodedPassword);
userDto.setRole("ADMIN");
userDto.setEnabled(true);
UserDto returnedUser = usersManager.createUser(userDto);
LOGGER.info("Create the user in Okta");
User oktaUser = UserBuilder.instance().setEmail(returnedUser.getEmail())
.setFirstName(returnedUser.getFirstName())
.setLastName(returnedUser.getLastName())
.buildAndCreate(client);
Das deckt den Teil der Benutzerverwaltung ab. Sie können auf ähnliche Weise GET
aufrufen oder DELETE
API zum Verwalten von Benutzern.
Benutzerauthentifizierung
Jetzt kommt der entscheidende Teil der Authentifizierung. In vielen Unternehmensanwendungen kommt es bei der Verwendung von Identitätsanbietern von Drittanbietern immer zu Problemen mit der Synchronisierung der Benutzerdaten. Beide Anwendungen müssen Benutzerdaten speichern.
Zur Authentifizierung benötigen wir authenticationClient
Bohne. Dieser Client ermöglicht es uns, die Okta-API zur Authentifizierung aufzurufen.
@Bean
public AuthenticationClient authenticationClient()
{
AuthenticationClient authenticationClient =
AuthenticationClients.builder()
.setOrgUrl("https://oktadomainurl")
.build();
return authenticationClient;
}
In unserer Sicherheitskonfiguration überschreibe ich die formularbasierte Anmeldung mit einer benutzerdefinierten Anmeldeseite.
@Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
@Bean(BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception
{
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception
{
httpSecurity.authorizeRequests()
.antMatchers("/js/**","/css/**","/img/**").permitAll()
.antMatchers("/signup","/forgotpassword").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll();
}
Wie Sie im obigen Code sehen, verwende ich customAuthenticationProvider
, verwendet dieser Anbieter authenticationClient
sich bei Okta zu authentifizieren. Dieser AuthenticationProvider sieht wie folgt aus:
package com.betterjavacode.sss.todolist.clients;
import com.betterjavacode.sss.todolist.security.AuthenticationStateHandler;
import com.okta.authn.sdk.client.AuthenticationClient;
import com.okta.authn.sdk.resource.AuthenticationResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider
{
private static final Logger LOGGER = LoggerFactory.getLogger(CustomAuthenticationProvider.class);
@Autowired
private AuthenticationClient authenticationClient;
@Autowired
private AuthenticationStateHandler authenticationStateHandler;
@Override
public Authentication authenticate (Authentication authentication) throws AuthenticationException
{
String username = authentication.getName();
String password = authentication.getCredentials().toString();
String relayState = "/index";
AuthenticationResponse authnResponse = null;
try
{
LOGGER.info("Going to connect to Okta");
authnResponse = authenticationClient.authenticate(username, password.toCharArray(),
relayState,
authenticationStateHandler);
}
catch(com.okta.authn.sdk.AuthenticationException e)
{
LOGGER.error("Unable to authentcate the user", e);
}
if(authnResponse != null)
{
final List grantedAuths = new ArrayList<>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
final UserDetails principal = new User(username, password, grantedAuths);
final Authentication authen = new UsernamePasswordAuthenticationToken(principal,
password, grantedAuths);
return authen;
}
else
{
LOGGER.info("Unable to authenticate");
return null;
}
}
@Override
public boolean supports (Class<?> authentication)
{
return true;
}
}
Wir verwenden authenticationClient
zum Aufrufen der Authentifizierungsmethode. AuthenticationStateHandler
behandelt im Wesentlichen die Statusauthentifizierung. Die Implementierung dieses Handles ist wie folgt:
package com.betterjavacode.sss.todolist.security;
import com.okta.authn.sdk.AuthenticationStateHandlerAdapter;
import com.okta.authn.sdk.resource.AuthenticationResponse;
import com.okta.commons.lang.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class AuthenticationStateHandler extends AuthenticationStateHandlerAdapter
{
private static final Logger LOGGER = LoggerFactory.getLogger(AuthenticationStateHandler.class);
@Override
public void handleUnknown (AuthenticationResponse unknownResponse)
{
// TO DO
}
@Override
public void handleSuccess (AuthenticationResponse successResponse)
{
if (Strings.hasLength(successResponse.getSessionToken()))
{
LOGGER.info("Login successful");
String relayState = successResponse.getRelayState();
String dest = relayState != null ? relayState : "/";
}
}
}
Das ist alles. Dies umfasst die Benutzerauthentifizierung. Denken Sie daran, dass dies immer noch eine formularbasierte Authentifizierung ist, bei der Sie Benutzeranmeldeinformationen auf Ihrer benutzerdefinierten Anmeldeseite und hinter dem Bildschirm eingeben, der die Okta-API zur Authentifizierung aufruft.
In meinem Buch „Simplifying Spring Security“ habe ich auch die Demo für die Anmeldung mit Okta OAuth hinzugefügt.
Schlussfolgerung
In diesem Beitrag habe ich gezeigt, wie Okta SDK für die Benutzerverwaltung und Authentifizierung mit der Spring Boot-Anwendung verwendet wird. Wenn Sie Fragen haben, können Sie mir gerne eine E-Mail senden, indem Sie hier meinen Blog abonnieren.