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

如何通过Swing桌面应用实现Spring Boot服务的身份认证

实现Swing桌面端对接Spring Boot Security认证方案

一、调整Spring Security配置

当前配置仅适配Web浏览器的表单登录,桌面端需要额外支持HTTP Basic认证或自定义REST登录接口,两种方案任选其一:

方案1:启用HTTP Basic认证(快速适配)

直接在现有Security配置中添加HTTP Basic支持,无需额外编写接口,桌面端可通过请求头携带用户名密码完成认证:

return http
        // 保留原有配置...
        .httpBasic(Customizer.withDefaults()) // 新增Basic认证支持
        .build();

你的硬编码认证Provider可直接复用,因为Basic认证同样基于UsernamePasswordAuthenticationToken触发认证逻辑。

方案2:自定义REST登录接口(更灵活)

如果需要更定制化的登录响应(如返回会话ID),可新增登录接口并开放权限:

  1. 更新Security授权规则:
.authorizeHttpRequests(authorize -> authorize
        // 原有规则...
        .requestMatchers(HttpMethod.POST, "/api/login").permitAll() // 开放登录接口
        .anyRequest().authenticated()
)
  1. 编写登录接口:
@RestController
@RequestMapping("/api")
public class LoginController {
    private final AuthenticationManager authenticationManager;
    private final HttpSession httpSession;

    public LoginController(AuthenticationManager authenticationManager, HttpSession httpSession) {
        this.authenticationManager = authenticationManager;
        this.httpSession = httpSession;
    }

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest request) {
        try {
            Authentication auth = authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
            );
            SecurityContextHolder.getContext().setAuthentication(auth);
            // 返回会话ID,供桌面端后续请求携带
            return ResponseEntity.ok(Map.of("sessionId", httpSession.getId(), "msg", "登录成功"));
        } catch (BadCredentialsException e) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("用户名/密码错误");
        }
    }

    public static class LoginRequest {
        private String username;
        private String password;
        // Getter & Setter
        public String getUsername() { return username; }
        public void setUsername(String username) { this.username = username; }
        public String getPassword() { return password; }
        public void setPassword(String password) { this.password = password; }
    }
}
  1. 配置AuthenticationManager(确保认证逻辑可被调用):
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
    return config.getAuthenticationManager();
}

二、Swing客户端登录实现

方案1:HTTP Basic认证的Swing代码

点击登录按钮时触发请求,通过请求头携带编码后的用户名密码:

loginButton.addActionListener(e -> {
    String username = usernameField.getText().trim();
    String password = new String(passwordField.getPassword()).trim();

    // 编码为Basic认证格式
    String authStr = username + ":" + password;
    String encodedAuth = Base64.getEncoder().encodeToString(authStr.getBytes(StandardCharsets.UTF_8));
    String authHeader = "Basic " + encodedAuth;

    // 发送请求到受保护接口验证登录
    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("http://localhost:8080/loggedin/test")) // 替换为你的受保护接口
            .header("Authorization", authHeader)
            .build();

    client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
            .thenApply(HttpResponse::statusCode)
            .thenAccept(statusCode -> {
                SwingUtilities.invokeLater(() -> {
                    if (statusCode == 200) {
                        JOptionPane.showMessageDialog(null, "登录成功");
                        // 跳转主窗口逻辑
                    } else {
                        JOptionPane.showMessageDialog(null, "登录失败:用户名或密码错误");
                    }
                });
            })
            .exceptionally(ex -> {
                SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(null, "请求失败:" + ex.getMessage()));
                return null;
            });
});

方案2:自定义REST接口的Swing代码

通过POST请求提交用户名密码,获取会话ID后用于后续请求:

loginButton.addActionListener(e -> {
    String username = usernameField.getText().trim();
    String password = new String(passwordField.getPassword()).trim();

    // 构造请求体
    LoginRequest loginReq = new LoginRequest();
    loginReq.setUsername(username);
    loginReq.setPassword(password);
    String requestBody = null;
    try {
        requestBody = new ObjectMapper().writeValueAsString(loginReq);
    } catch (JsonProcessingException ex) {
        JOptionPane.showMessageDialog(null, "请求格式错误");
        return;
    }

    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("http://localhost:8080/api/login"))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(requestBody))
            .build();

    client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
            .thenApply(resp -> Map.of("status", resp.statusCode(), "body", resp.body()))
            .thenAccept(result -> {
                SwingUtilities.invokeLater(() -> {
                    if ((int) result.get("status") == 200) {
                        JOptionPane.showMessageDialog(null, "登录成功");
                        // 解析会话ID,后续请求携带Cookie: JSESSIONID=xxx
                        try {
                            Map<String, String> respMap = new ObjectMapper().readValue((String) result.get("body"), Map.class);
                            String sessionId = respMap.get("sessionId");
                            // 保存sessionId到全局变量,供后续请求使用
                        } catch (JsonProcessingException ex) {
                            JOptionPane.showMessageDialog(null, "解析响应失败");
                        }
                        // 跳转主窗口逻辑
                    } else {
                        JOptionPane.showMessageDialog(null, "登录失败:" + result.get("body"));
                    }
                });
            })
            .exceptionally(ex -> {
                SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(null, "请求失败:" + ex.getMessage()));
                return null;
            });
});

// 对应后端的LoginRequest内部类
static class LoginRequest {
    private String username;
    private String password;
    // Getter & Setter
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }
}

三、关键注意事项

  • 会话维持:使用REST接口方案时,后续请求需在请求头添加Cookie: JSESSIONID=xxx,才能访问受保护资源。
  • 安全要求:生产环境必须使用HTTPS,避免用户名密码明文传输(HTTP Basic的Base64编码仅为编码,非加密)。
  • 跨域问题:桌面客户端不受浏览器同源策略限制,无需配置CORS。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 16:03:13