如何通过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),可新增登录接口并开放权限:
- 更新Security授权规则:
.authorizeHttpRequests(authorize -> authorize // 原有规则... .requestMatchers(HttpMethod.POST, "/api/login").permitAll() // 开放登录接口 .anyRequest().authenticated() )
- 编写登录接口:
@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; } } }
- 配置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
相关产品推荐
相关产品推荐

