You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

带REST的Spring MVC集成Apache Mina SSHD服务器及登录场景实现咨询

Great question! Let's tackle this step by step—first integrating Apache Mina SSHD with your Spring MVC project, then walking through the full login flow you described with the JavaFX client.

Integrating Apache Mina SSHD with Spring MVC

First, add the Apache Mina SSHD dependency to your project. If you're using Maven, drop this into your pom.xml:

<dependency>
    <groupId>org.apache.sshd</groupId>
    <artifactId>sshd-core</artifactId>
    <version>2.11.0</version> <!-- Use the latest stable version -->
</dependency>

Next, configure the SSHD server as a Spring-managed bean so it starts/stops alongside your application. Create a configuration class:

import org.apache.sshd.server.SshServer;
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@Configuration
public class SshServerConfig {

    private SshServer sshServer;

    @Bean
    public SshServer sshServer() {
        sshServer = SshServer.setUpDefaultServer();
        sshServer.setPort(2222); // Pick a non-standard port to avoid conflicts
        // Persist this host key in production (don't regenerate on every startup!)
        sshServer.setKeyPairProvider(new SimpleGeneratorHostKeyProvider("hostkey.ser"));
        
        // We'll add custom auth and command logic next
        return sshServer;
    }

    @PostConstruct
    public void startSshServer() throws Exception {
        sshServer.start();
    }

    @PreDestroy
    public void stopSshServer() throws Exception {
        sshServer.stop();
    }
}

This ties the SSH server to your Spring context. Now let's build the custom logic for your login flow.


Implementing the Full Login Flow

Your flow has three core steps: SSH private key authentication, retrieve SSL certificate over SSH, REST HTTPS password validation. Let's break down server and client implementations.

Server-Side Setup

1. Private Key Authentication

Implement a PublicKeyAuthenticator to validate the client's private key against your user database:

import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
import org.apache.sshd.server.session.ServerSession;
import java.security.PublicKey;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class CustomPublicKeyAuthenticator implements PublickeyAuthenticator {

    @Autowired
    private UserService userService; // Your existing user management service

    @Override
    public boolean authenticate(String username, PublicKey publicKey, ServerSession session) {
        // Fetch user by username and verify their stored public key matches the provided one
        User user = userService.findByUsername(username);
        if (user == null) return false;
        
        // Adjust this based on how you store public keys (e.g., encoded bytes)
        return user.getPublicKey().equals(publicKey.getEncoded());
    }
}

Update the SshServerConfig to use this authenticator and disable password auth for the initial SSH step:

@Autowired
private CustomPublicKeyAuthenticator publicKeyAuthenticator;

@Bean
public SshServer sshServer() {
    // ... existing config ...
    sshServer.setPublickeyAuthenticator(publicKeyAuthenticator);
    sshServer.setPasswordAuthenticator(null); // Disable password auth for SSH step
    
    // Add a custom shell to handle certificate requests
    sshServer.setShellFactory(session -> new CertificateProvidingShell());
    return sshServer;
}

2. Serve SSL Certificate Over SSH

Create a custom shell that responds to a specific command to return your Spring MVC SSL certificate:

import org.apache.sshd.server.shell.Shell;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Scanner;

public class CertificateProvidingShell implements Shell {

    @Override
    public void start(InputStream in, OutputStream out, OutputStream err) {
        PrintWriter writer = new PrintWriter(out, true);
        Scanner scanner = new Scanner(in);
        
        writer.println("Connected. Send 'GET_CERT' to retrieve SSL certificate.");
        
        String command = scanner.nextLine();
        if ("GET_CERT".equals(command.trim())) {
            String certContent = loadSslCertificate();
            writer.println("CERT_START");
            writer.println(certContent);
            writer.println("CERT_END");
        }
        
        scanner.close();
        writer.close();
    }

    private String loadSslCertificate() {
        // Load your server's SSL certificate (e.g., from a .pem file in resources)
        try (Scanner scanner = new Scanner(getClass().getResourceAsStream("/server.crt"))) {
            return scanner.useDelimiter("\\Z").next();
        } catch (Exception e) {
            throw new RuntimeException("Failed to load SSL certificate", e);
        }
    }

    @Override
    public void destroy() {}
}

3. Spring MVC REST Login Endpoint

Create a secure HTTPS endpoint to validate the user's password (ensure your Spring app uses the same SSL certificate provided via SSH):

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AuthController {

    @Autowired
    private UserService userService;

    @PostMapping("/api/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest request) {
        User user = userService.findByUsername(request.getUsername());
        if (user == null || !userService.verifyPassword(request.getPassword(), user.getPasswordHash())) {
            return ResponseEntity.status(401).body("Invalid credentials");
        }
        // Generate JWT/session token here if needed
        return ResponseEntity.ok("Login successful");
    }

    // DTO class for login request
    public static class LoginRequest {
        private String username;
        private String password;
        // Getters and setters
    }
}

Client-Side (JavaFX) Implementation

1. Basic Login UI

Build a simple JavaFX UI with fields for private key path, username, password, and a login button:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class LoginApp extends Application {

    @Override
    public void start(Stage stage) {
        TextField privateKeyPathField = new TextField();
        privateKeyPathField.setPromptText("Path to private key (.pem)");
        
        TextField usernameField = new TextField();
        usernameField.setPromptText("Username");
        
        PasswordField passwordField = new PasswordField();
        passwordField.setPromptText("Password");
        
        Button loginButton = new Button("Login");
        loginButton.setOnAction(e -> handleLogin(
            privateKeyPathField.getText(),
            usernameField.getText(),
            passwordField.getText()
        ));
        
        VBox root = new VBox(10, privateKeyPathField, usernameField, passwordField, loginButton);
        stage.setScene(new Scene(root, 300, 200));
        stage.setTitle("SSH + REST Login");
        stage.show();
    }

    private void handleLogin(String privateKeyPath, String username, String password) {
        // Implement login flow here
    }

    public static void main(String[] args) {
        launch(args);
    }
}

2. Full Login Flow Logic

Implement the SSH connection, certificate retrieval, and REST call in the handleLogin method:

import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.client.channel.ChannelShell;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Scanner;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.ByteArrayInputStream;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import org.springframework.web.client.RestTemplate;
import org.apache.http.impl.client.HttpClientBuilder;

private void handleLogin(String privateKeyPath, String username, String password) {
    // Step 1: Load private key from file
    PrivateKey privateKey = loadPrivateKey(privateKeyPath);
    if (privateKey == null) {
        // Show error dialog to user
        return;
    }

    // Step 2: Establish SSH connection with private key auth
    try (SshClient client = SshClient.setUpDefaultClient()) {
        client.start();
        try (ClientSession session = client.connect(username, "localhost", 2222)
                .verify()
                .getSession()) {
            session.addPublicKeyIdentity(privateKey);
            session.auth().verify();

            // Step 3: Request and retrieve SSL certificate
            try (ChannelShell channel = session.createShellChannel()) {
                channel.open().verify();
                OutputStreamWriter writer = new OutputStreamWriter(channel.getInvertedIn());
                InputStreamReader reader = new InputStreamReader(channel.getOut());
                Scanner scanner = new Scanner(reader);

                // Skip initial prompt and send command
                scanner.nextLine();
                writer.write("GET_CERT\n");
                writer.flush();

                // Parse certificate from response
                StringBuilder certBuilder = new StringBuilder();
                boolean inCert = false;
                while (scanner.hasNextLine()) {
                    String line = scanner.nextLine();
                    if ("CERT_START".equals(line)) inCert = true;
                    else if ("CERT_END".equals(line)) break;
                    else if (inCert) certBuilder.append(line).append("\n");
                }

                // Step 4: Configure SSL context with retrieved certificate
                SSLContext sslContext = createSslContext(certBuilder.toString());
                RestTemplate restTemplate = new RestTemplate();
                restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(
                    HttpClientBuilder.create().setSSLContext(sslContext).build()
                ));

                // Step 5: Call REST login endpoint
                LoginRequest loginRequest = new LoginRequest(username, password);
                ResponseEntity<String> response = restTemplate.postForEntity(
                    "https://localhost:8443/api/login",
                    loginRequest,
                    String.class
                );

                if (response.getStatusCode().is2xxSuccessful()) {
                    // Navigate to main application UI
                    System.out.println("Login successful!");
                } else {
                    // Show invalid credentials error
                    System.out.println("Login failed: " + response.getBody());
                }
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
        // Show connection/error dialog to user
    }
}

private PrivateKey loadPrivateKey(String path) {
    try {
        String keyContent = new String(Files.readAllBytes(Paths.get(path)))
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replaceAll("\\s", "");
        byte[] keyBytes = Base64.getDecoder().decode(keyContent);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory kf = KeyFactory.getInstance("RSA"); // Adjust based on your key type
        return kf.generatePrivate(spec);
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

private SSLContext createSslContext(String certContent) throws Exception {
    CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
    X509Certificate cert = (X509Certificate) certFactory.generateCertificate(
        new ByteArrayInputStream(certContent.getBytes())
    );

    TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    java.security.KeyStore ks = java.security.KeyStore.getInstance(java.security.KeyStore.getDefaultType());
    ks.load(null);
    ks.setCertificateEntry("server-cert", cert);
    tmf.init(ks);

    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(null, tmf.getTrustManagers(), null);
    return sslContext;
}

// DTO matching server's LoginRequest
private static class LoginRequest {
    private String username;
    private String password;
    public LoginRequest(String username, String password) {
        this.username = username;
        this.password = password;
    }
    // Getters
}

Production Considerations

  • Persist Host Keys: Don't regenerate host keys on every server start—store them securely to avoid client trust warnings.
  • Secure Certificate Management: Use a vault or key management system instead of hardcoding certificates.
  • Error Handling: Add user-friendly dialogs in JavaFX for connection failures, invalid keys, or login errors.
  • Rate Limiting: Protect both SSH and REST endpoints from brute-force attacks with rate limiting.
  • Audit Logging: Log all authentication attempts for security auditing.

内容的提问来源于stack exchange,提问作者LeCram

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:55:39