Kerberos Integration in Java

Overview

Kerberos is a network authentication protocol that provides strong authentication for client/server applications. Java provides comprehensive support for Kerberos through the Java Authentication and Authorization Service (JAAS) and the Generic Security Services API (GSS-API).

Core Components

  • LoginContext: JAAS entry point for authentication
  • Krb5LoginModule: Kerberos login module
  • GSSContext: GSS-API context for secure communication
  • Subject: Represents authenticated user
  • Principal: Kerberos principal identity

Setup and Dependencies

Maven Dependencies

<dependencies> <!-- Kerberos support is included in Java SE --> <!-- Additional dependencies for specific use cases --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-kerberos</artifactId> <version>1.0.1.RELEASE</version> </dependency> </dependencies>

Kerberos Configuration

krb5.conf File

[libdefaults] default_realm = EXAMPLE.COM dns_lookup_realm = false dns_lookup_kdc = true ticket_lifetime = 24h renew_lifetime = 7d forwardable = true default_tkt_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 default_tgs_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 permitted_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96

[realms]

EXAMPLE.COM = { kdc = kdc.example.com admin_server = kdc.example.com default_domain = example.com }

[domain_realm]

.example.com = EXAMPLE.COM example.com = EXAMPLE.COM

JAAS Configuration File (jaas.conf)

KerberosClient { com.sun.security.auth.module.Krb5LoginModule required useKeyTab=false useTicketCache=true doNotPrompt=false principal="[email protected]" debug=true; }; KerberosClientWithKeytab { com.sun.security.auth.module.Krb5LoginModule required useKeyTab=true keyTab="/path/to/user.keytab" principal="service/[email protected]" storeKey=true doNotPrompt=true debug=true; }; KerberosServer { com.sun.security.auth.module.Krb5LoginModule required useKeyTab=true keyTab="/path/to/service.keytab" principal="HTTP/[email protected]" storeKey=true doNotPrompt=true debug=true; };

Basic Kerberos Authentication

Example 1: Client-Side Kerberos Authentication

import javax.security.auth.Subject; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import java.security.Principal; import java.security.PrivilegedAction; import java.util.Set; public class KerberosClientAuth { public static void main(String[] args) { // Set Kerberos configuration System.setProperty("java.security.krb5.conf", "/path/to/krb5.conf"); System.setProperty("java.security.auth.login.config", "/path/to/jaas.conf"); System.setProperty("sun.security.krb5.debug", "true"); try { // Perform Kerberos authentication LoginContext loginContext = new LoginContext("KerberosClient"); loginContext.login(); Subject subject = loginContext.getSubject(); System.out.println("Authentication successful!"); // Display subject information displaySubjectInfo(subject); // Perform actions as authenticated subject performAuthenticatedAction(subject); // Logout loginContext.logout(); System.out.println("Logged out successfully"); } catch (LoginException e) { System.err.println("Authentication failed: " + e.getMessage()); e.printStackTrace(); } } private static void displaySubjectInfo(Subject subject) { System.out.println("Subject: " + subject); System.out.println("Principals:"); Set<Principal> principals = subject.getPrincipals(); for (Principal principal : principals) { System.out.println(" - " + principal.getName()); } System.out.println("Public Credentials: " + subject.getPublicCredentials().size()); System.out.println("Private Credentials: " + subject.getPrivateCredentials().size()); } private static void performAuthenticatedAction(Subject subject) { Subject.doAs(subject, new PrivilegedAction<Void>() { @Override public Void run() { System.out.println("Performing action as: " + Subject.getSubject(AccessController.getContext())); // Here you can make Kerberos-authenticated requests return null; } }); } }

Example 2: Service Authentication with Keytab

import javax.security.auth.Subject; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; public class KerberosServiceAuth { private Subject serviceSubject; public KerberosServiceAuth() throws LoginException { // Set Kerberos configuration System.setProperty("java.security.krb5.conf", "/etc/krb5.conf"); System.setProperty("java.security.auth.login.config", "/path/to/jaas.conf"); System.setProperty("sun.security.krb5.debug", "true"); // Authenticate service using keytab LoginContext loginContext = new LoginContext("KerberosServer"); loginContext.login(); this.serviceSubject = loginContext.getSubject(); System.out.println("Service authenticated successfully"); System.out.println("Service Principal: " + serviceSubject.getPrincipals().iterator().next().getName()); } public Subject getServiceSubject() { return serviceSubject; } public void performServiceAction() { Subject.doAs(serviceSubject, new PrivilegedAction<Void>() { @Override public Void run() { try { // Service can now accept Kerberos authentication System.out.println("Service is ready to accept Kerberos requests"); // Implement service logic here } catch (Exception e) { System.err.println("Service action failed: " + e.getMessage()); } return null; } }); } public static void main(String[] args) { try { KerberosServiceAuth service = new KerberosServiceAuth(); service.performServiceAction(); } catch (LoginException e) { System.err.println("Service authentication failed: " + e.getMessage()); e.printStackTrace(); } } }

GSS-API for Secure Communication

Example 3: Client-Server Authentication with GSS-API

import org.ietf.jgss.*; import java.security.PrivilegedAction; public class GSSClientServerExample { // Client-side GSS-API public static class GSSClient { public byte[] authenticate(Subject clientSubject, String servicePrincipal) throws GSSException { return Subject.doAs(clientSubject, new PrivilegedAction<byte[]>() { @Override public byte[] run() { try { GSSManager manager = GSSManager.getInstance(); GSSName serverName = manager.createName( servicePrincipal, GSSName.NT_HOSTBASED_SERVICE); Oid krb5Mech = new Oid("1.2.840.113554.1.2.2"); // Kerberos OID GSSContext context = manager.createContext( serverName, krb5Mech, null, GSSContext.DEFAULT_LIFETIME); context.requestMutualAuth(true); context.requestConf(true); context.requestInteg(true); // Initiate security context byte[] token = new byte[0]; token = context.initSecContext(token, 0, token.length); System.out.println("Client context established: " + context.isEstablished()); return token; } catch (GSSException e) { throw new RuntimeException("GSS Client error", e); } } }); } } // Server-side GSS-API public static class GSSServer { private GSSContext context; public byte[] acceptSecurityContext(byte[] clientToken) throws GSSException { GSSManager manager = GSSManager.getInstance(); Oid krb5Mech = new Oid("1.2.840.113554.1.2.2"); context = manager.createContext((GSSCredential) null); byte[] serverToken = context.acceptSecContext(clientToken, 0, clientToken.length); System.out.println("Server context established: " + context.isEstablished()); if (context.isEstablished()) { System.out.println("Client Principal: " + context.getSrcName()); System.out.println("Server Principal: " + context.getTargName()); } return serverToken; } public void verifyAuthentication() throws GSSException { if (context != null && context.isEstablished()) { System.out.println("Mutual authentication: " + context.getMutualAuthState()); System.out.println("Confidentiality: " + context.getConfState()); System.out.println("Integrity: " + context.getIntegState()); } } public byte[] wrapMessage(byte[] message) throws GSSException { if (context != null && context.isEstablished()) { return context.wrap(message, 0, message.length, new MessageProp(true, true)); } throw new GSSException(GSSException.NO_CONTEXT); } public byte[] unwrapMessage(byte[] wrappedMessage) throws GSSException { if (context != null && context.isEstablished()) { MessageProp prop = new MessageProp(false); byte[] unwrapped = context.unwrap(wrappedMessage, 0, wrappedMessage.length, prop); System.out.println("Message QOP: " + prop.getQOP()); System.out.println("Privacy: " + prop.getPrivacy()); return unwrapped; } throw new GSSException(GSSException.NO_CONTEXT); } } public static void main(String[] args) { try { // Setup System.setProperty("java.security.krb5.conf", "/path/to/krb5.conf"); System.setProperty("java.security.auth.login.config", "/path/to/jaas.conf"); // Client authentication LoginContext clientLogin = new LoginContext("KerberosClient"); clientLogin.login(); Subject clientSubject = clientLogin.getSubject(); // Create client and server GSSClient client = new GSSClient(); GSSServer server = new GSSServer(); // Perform authentication handshake String servicePrincipal = "HTTP/[email protected]"; byte[] clientToken = client.authenticate(clientSubject, servicePrincipal); byte[] serverToken = server.acceptSecurityContext(clientToken); // Verify authentication server.verifyAuthentication(); // Test message protection String originalMessage = "Secret message from client"; byte[] wrappedMessage = clientSubject.doAs(new PrivilegedAction<byte[]>() { @Override public byte[] run() { try { GSSManager manager = GSSManager.getInstance(); GSSName serverName = manager.createName( servicePrincipal, GSSName.NT_HOSTBASED_SERVICE); Oid krb5Mech = new Oid("1.2.840.113554.1.2.2"); GSSContext clientContext = manager.createContext( serverName, krb5Mech, null, GSSContext.DEFAULT_LIFETIME); clientContext.initSecContext(new byte[0], 0, 0); MessageProp prop = new MessageProp(true, true); return clientContext.wrap( originalMessage.getBytes(), 0, originalMessage.getBytes().length, prop); } catch (GSSException e) { throw new RuntimeException(e); } } }); byte[] unwrappedMessage = server.unwrapMessage(wrappedMessage); String receivedMessage = new String(unwrappedMessage); System.out.println("Original: " + originalMessage); System.out.println("Received: " + receivedMessage); System.out.println("Match: " + originalMessage.equals(receivedMessage)); clientLogin.logout(); } catch (Exception e) { e.printStackTrace(); } } }

HTTP SPNEGO Authentication

Example 4: Kerberos HTTP Client with SPNEGO

import org.ietf.jgss.*; import sun.security.jgss.GSSUtil; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.security.PrivilegedAction; public class KerberosHttpClient { public static class SpnegoAuthenticator { public String authenticateWithSpnego(Subject subject, String urlString) throws Exception { return Subject.doAs(subject, new PrivilegedAction<String>() { @Override public String run() { try { URL url = new URL(urlString); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); // Perform SPNEGO authentication String spnegoToken = generateSpnegoToken(url.getHost()); if (spnegoToken != null) { connection.setRequestProperty("Authorization", "Negotiate " + spnegoToken); } connection.setRequestMethod("GET"); connection.setConnectTimeout(10000); connection.setReadTimeout(10000); int responseCode = connection.getResponseCode(); System.out.println("HTTP Response Code: " + responseCode); if (responseCode == HttpURLConnection.HTTP_OK) { try (BufferedReader reader = new BufferedReader( new InputStreamReader(connection.getInputStream()))) { StringBuilder response = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { response.append(line); } return response.toString(); } } else if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) { // Handle WWW-Authenticate challenge String authHeader = connection.getHeaderField("WWW-Authenticate"); if (authHeader != null && authHeader.startsWith("Negotiate")) { System.out.println("Received Negotiate challenge"); // Extract server token and continue authentication } throw new RuntimeException("HTTP Unauthorized: " + responseCode); } else { throw new RuntimeException("HTTP Error: " + responseCode); } } catch (Exception e) { throw new RuntimeException("HTTP request failed", e); } } }); } private String generateSpnegoToken(String serviceHost) { try { GSSManager manager = GSSManager.getInstance(); String servicePrincipal = "HTTP/" + serviceHost; GSSName serverName = manager.createName( servicePrincipal, GSSName.NT_HOSTBASED_SERVICE); Oid spnegoOid = new Oid("1.3.6.1.5.5.2"); // SPNEGO OID GSSContext context = manager.createContext( serverName, spnegoOid, null, GSSContext.DEFAULT_LIFETIME); context.requestMutualAuth(true); byte[] token = context.initSecContext(new byte[0], 0, 0); if (token != null) { return java.util.Base64.getEncoder().encodeToString(token); } } catch (GSSException e) { System.err.println("SPNEGO token generation failed: " + e.getMessage()); } return null; } } public static void main(String[] args) { try { // Configure Kerberos System.setProperty("java.security.krb5.conf", "/path/to/krb5.conf"); System.setProperty("java.security.auth.login.config", "/path/to/jaas.conf"); System.setProperty("sun.security.krb5.debug", "true"); // Authenticate client LoginContext loginContext = new LoginContext("KerberosClient"); loginContext.login(); Subject clientSubject = loginContext.getSubject(); // Make SPNEGO authenticated HTTP request SpnegoAuthenticator authenticator = new SpnegoAuthenticator(); String response = authenticator.authenticateWithSpnego( clientSubject, "https://kerberos-protected-service.example.com/api/data"); System.out.println("SPNEGO Authenticated Response: " + response); loginContext.logout(); } catch (Exception e) { e.printStackTrace(); } } }

Example 5: Kerberos-Protected HTTP Server

import com.sun.net.httpserver.*; import org.ietf.jgss.*; import javax.security.auth.Subject; import javax.security.auth.login.LoginContext; import java.io.*; import java.net.InetSocketAddress; import java.security.Principal; import java.security.PrivilegedAction; import java.util.Base64; public class KerberosHttpServer { private HttpServer server; private Subject serviceSubject; public KerberosHttpServer(int port) throws Exception { // Authenticate service System.setProperty("java.security.krb5.conf", "/etc/krb5.conf"); System.setProperty("java.security.auth.login.config", "/path/to/jaas.conf"); LoginContext loginContext = new LoginContext("KerberosServer"); loginContext.login(); this.serviceSubject = loginContext.getSubject(); // Create HTTP server this.server = HttpServer.create(new InetSocketAddress(port), 0); server.createContext("/api", new KerberosHandler()); server.setExecutor(null); } public void start() { server.start(); System.out.println("Kerberos HTTP Server started on port " + server.getAddress().getPort()); } public void stop() { server.stop(0); System.out.println("Server stopped"); } class KerberosHandler implements HttpHandler { @Override public void handle(HttpExchange exchange) throws IOException { Subject.doAs(serviceSubject, new PrivilegedAction<Void>() { @Override public Void run() { try { // Check for SPNEGO authentication String authHeader = exchange.getRequestHeaders().getFirst("Authorization"); if (authHeader != null && authHeader.startsWith("Negotiate")) { // Extract SPNEGO token String spnegoToken = authHeader.substring("Negotiate".length()).trim(); byte[] token = Base64.getDecoder().decode(spnegoToken); // Authenticate client GSSContext context = authenticateClient(token); if (context != null && context.isEstablished()) { // Client authenticated successfully String clientPrincipal = context.getSrcName().toString(); System.out.println("Authenticated client: " + clientPrincipal); // Process request String response = "Hello, " + clientPrincipal + "! You are authenticated via Kerberos."; sendResponse(exchange, 200, response); context.dispose(); return null; } } // Authentication required exchange.getResponseHeaders().set("WWW-Authenticate", "Negotiate"); sendResponse(exchange, 401, "Authentication Required"); } catch (Exception e) { System.err.println("Request handling failed: " + e.getMessage()); try { sendResponse(exchange, 500, "Internal Server Error"); } catch (IOException ex) { ex.printStackTrace(); } } return null; } }); } private GSSContext authenticateClient(byte[] clientToken) throws GSSException { GSSManager manager = GSSManager.getInstance(); GSSContext context = manager.createContext((GSSCredential) null); byte[] serverToken = context.acceptSecContext(clientToken, 0, clientToken.length); if (serverToken != null) { // If server token is generated, client needs to continue authentication // For simplicity, we're handling single-step authentication here System.out.println("Server token generated, continuing authentication..."); } return context; } private void sendResponse(HttpExchange exchange, int statusCode, String response) throws IOException { exchange.getResponseHeaders().set("Content-Type", "text/plain"); exchange.sendResponseHeaders(statusCode, response.length()); try (OutputStream os = exchange.getResponseBody()) { os.write(response.getBytes()); } } } public static void main(String[] args) { try { KerberosHttpServer server = new KerberosHttpServer(8080); server.start(); // Keep server running System.out.println("Press Enter to stop the server..."); System.in.read(); server.stop(); } catch (Exception e) { e.printStackTrace(); } } }

Spring Security Kerberos Integration

Example 6: Spring Boot with Kerberos

import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.kerberos.authentication.KerberosAuthenticationProvider; import org.springframework.security.kerberos.authentication.KerberosServiceAuthenticationProvider; import org.springframework.security.kerberos.authentication.sun.SunJaasKerberosClient; import org.springframework.security.kerberos.authentication.sun.SunJaasKerberosTicketValidator; import org.springframework.security.kerberos.web.authentication.SpnegoAuthenticationProcessingFilter; import org.springframework.security.kerberos.web.authentication.SpnegoEntryPoint; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; @SpringBootApplication public class SpringKerberosApplication { public static void main(String[] args) { SpringApplication.run(SpringKerberosApplication.class, args); } @EnableWebSecurity public static class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/", "/public/**").permitAll() .anyRequest().authenticated() .and() .exceptionHandling() .authenticationEntryPoint(spnegoEntryPoint()) .and() .addFilterBefore( spnegoAuthenticationProcessingFilter(), BasicAuthenticationFilter.class); } @Bean public SpnegoEntryPoint spnegoEntryPoint() { return new SpnegoEntryPoint(); } @Bean public SpnegoAuthenticationProcessingFilter spnegoAuthenticationProcessingFilter() throws Exception { SpnegoAuthenticationProcessingFilter filter = new SpnegoAuthenticationProcessingFilter(); filter.setAuthenticationManager(authenticationManager()); return filter; } @Bean public KerberosAuthenticationProvider kerberosAuthenticationProvider() { KerberosAuthenticationProvider provider = new KerberosAuthenticationProvider(); provider.setKerberosClient(sunJaasKerberosClient()); provider.setUserDetailsService(dummyUserDetailsService()); return provider; } @Bean public SunJaasKerberosClient sunJaasKerberosClient() { SunJaasKerberosClient client = new SunJaasKerberosClient(); client.setDebug(true); return client; } @Bean public SunJaasKerberosTicketValidator sunJaasKerberosTicketValidator() { SunJaasKerberosTicketValidator ticketValidator = new SunJaasKerberosTicketValidator(); ticketValidator.setServicePrincipal("HTTP/[email protected]"); ticketValidator.setKeyTabLocation("classpath:service.keytab"); ticketValidator.setDebug(true); return ticketValidator; } @Bean public DummyUserDetailsService dummyUserDetailsService() { return new DummyUserDetailsService(); } } } // Simple user details service for demonstration class DummyUserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService { @Override public org.springframework.security.core.userdetails.UserDetails loadUserByUsername( String username) { return org.springframework.security.core.userdetails.User.builder() .username(username) .password("") // Password not used in Kerberos .authorities("ROLE_USER") .build(); } }

Advanced Kerberos Features

Example 7: Kerberos Delegation

import org.ietf.jgss.*; public class KerberosDelegation { public static class DelegationClient { public byte[] authenticateWithDelegation(Subject clientSubject, String servicePrincipal) throws Exception { return Subject.doAs(clientSubject, new PrivilegedAction<byte[]>() { @Override public byte[] run() { try { GSSManager manager = GSSManager.getInstance(); GSSName serverName = manager.createName( servicePrincipal, GSSName.NT_HOSTBASED_SERVICE); Oid krb5Mech = new Oid("1.2.840.113554.1.2.2"); GSSContext context = manager.createContext( serverName, krb5Mech, null, GSSContext.DEFAULT_LIFETIME); // Request delegation context.requestCredDeleg(true); context.requestMutualAuth(true); byte[] token = context.initSecContext(new byte[0], 0, 0); if (context.isEstablished()) { System.out.println("Delegation state: " + context.getCredDelegState()); if (context.getCredDelegState()) { GSSCredential delegatedCreds = context.getDelegCred(); System.out.println("Delegated credentials obtained"); // Use delegated credentials for further authentication } } return token; } catch (GSSException e) { throw new RuntimeException("Delegation failed", e); } } }); } } public static class DelegationService { public void useDelegatedCredentials(GSSCredential delegatedCreds, String backendServicePrincipal) throws GSSException { GSSManager manager = GSSManager.getInstance(); GSSName backendServiceName = manager.createName( backendServicePrincipal, GSSName.NT_HOSTBASED_SERVICE); Oid krb5Mech = new Oid("1.2.840.113554.1.2.2"); GSSContext context = manager.createContext( backendServiceName, krb5Mech, delegatedCreds, GSSContext.DEFAULT_LIFETIME); context.requestMutualAuth(true); byte[] token = context.initSecContext(new byte[0], 0, 0); if (context.isEstablished()) { System.out.println("Backend service authenticated using delegated credentials"); System.out.println("Original client: " + context.getSrcName()); } context.dispose(); } } }

Troubleshooting and Debugging

Example 8: Kerberos Debug Utilities

import javax.security.auth.kerberos.KerberosPrincipal; import javax.security.auth.kerberos.KerberosTicket; import java.security.Principal; import java.util.Set; public class KerberosDebugUtils { public static void printKerberosTicketInfo(Subject subject) { Set<KerberosTicket> tickets = subject.getPrivateCredentials(KerberosTicket.class); System.out.println("Number of Kerberos tickets: " + tickets.size()); for (KerberosTicket ticket : tickets) { System.out.println("=== Kerberos Ticket ==="); System.out.println("Client: " + ticket.getClient()); System.out.println("Server: " + ticket.getServer()); System.out.println("Auth Time: " + ticket.getAuthTime()); System.out.println("Start Time: " + ticket.getStartTime()); System.out.println("End Time: " + ticket.getEndTime()); System.out.println("Renew Till: " + ticket.getRenewTill()); System.out.println("Client Addresses: " + ticket.getClientAddresses()); System.out.println("Session Key Type: " + ticket.getSessionKeyType()); System.out.println("Flags: " + ticket.getFlags()); System.out.println("=== End Ticket ==="); } } public static void printSubjectPrincipals(Subject subject) { Set<Principal> principals = subject.getPrincipals(); System.out.println("Subject Principals (" + principals.size() + "):"); for (Principal principal : principals) { System.out.println(" - " + principal.getClass().getSimpleName() + ": " + principal.getName()); if (principal instanceof KerberosPrincipal) { KerberosPrincipal kprincipal = (KerberosPrincipal) principal; System.out.println(" Realm: " + kprincipal.getRealm()); } } } public static void checkKerberosConfiguration() { System.out.println("=== Kerberos Configuration Check ==="); System.out.println("java.security.krb5.conf: " + System.getProperty("java.security.krb5.conf")); System.out.println("java.security.auth.login.config: " + System.getProperty("java.security.auth.login.config")); System.out.println("sun.security.krb5.debug: " + System.getProperty("sun.security.krb5.debug")); System.out.println("Default Realm: " + javax.security.auth.kerberos.KerberosPrincipal.getDefaultRealm()); System.out.println("=== End Configuration Check ==="); } public static void enableDebug() { System.setProperty("sun.security.krb5.debug", "true"); System.setProperty("sun.security.jgss.debug", "true"); System.setProperty("javax.security.auth.debug", "true"); } }

Best Practices

  1. Secure Keytab Files: Store keytab files securely with proper permissions
  2. Principal Management: Use appropriate service principals (HTTP/hostname@REALM)
  3. Ticket Lifetime: Configure appropriate ticket lifetimes
  4. Delegation Control: Use delegation only when necessary
  5. Error Handling: Implement comprehensive error handling and logging
  6. Configuration Management: Externalize Kerberos configuration
  7. Monitoring: Monitor authentication failures and ticket expiration
  8. Testing: Test with different Kerberos configurations
  9. Documentation: Document Kerberos setup and troubleshooting procedures
  10. Security Updates: Keep Java and Kerberos libraries updated

These examples provide a comprehensive foundation for integrating Kerberos authentication in Java applications, covering client authentication, server-side protection, HTTP SPNEGO, and advanced features like delegation.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper