Spring Security(第五篇):从单体到前后端分离 —— JSON 响应与处理器实战

欢迎来到我的博客,代码的世界里,每一行都是一个故事

🎏:你只管努力,剩下的交给时间

🏠 :小破站

Spring Security(第五篇):从单体到前后端分离 ------ JSON 响应与处理器实战

我们把"怎么进来"(认证来源:配置/内存/数据库)讲清楚了。进入系统之后,单体应用(服务端渲染)和前后端分离的最大差异并不是"如何认证",而是"响应形式":

  • 单体:表单提交,重定向/渲染页面
  • 分离:JSON API 请求,返回 JSON(不重定向,不渲染页面)

本篇聚焦"响应处理器(Handler)"在前后端分离场景下的落地:登录成功、登录失败、未登录访问、权限不足、注销成功,都返回一致的 API JSON。


功能清单与路径一览

  • 登录(API):POST /api/login(表单键 username/password)
  • 注销(API):POST /api/logout
  • 公开接口:GET /api/public(无需登录)
  • 用户接口:GET /api/user、GET /api/me(需登录)
  • 管理员接口:GET /api/admin(需 ADMIN 角色)
  • 经理接口:GET /api/manager(需 MANAGER 角色)
  • 演示页面:GET /api-demo(可视化操作与查看 JSON 响应)

安全配置采用两条过滤器链:

  • /api/** 使用 JSON 响应(JsonAuthenticationHandler/JsonAccessDeniedHandler)
  • 其他路径沿用页面流(登录页、控制台等)

关键实现(与代码严格对应)

  • API 过滤器链(只匹配 /api/**)

    java 复制代码
    http.securityMatcher("/api/**")
       .authorizeHttpRequests(a->a.requestMatchers("/api/public").permitAll()
          .requestMatchers("/api/admin").hasRole("ADMIN")
          .requestMatchers("/api/manager").hasRole("MANAGER")
          .anyRequest().authenticated())
       .formLogin(f->f.loginProcessingUrl("/api/login")
          .successHandler(jsonAuthenticationHandler)
          .failureHandler(jsonAuthenticationHandler))
  • 统一 JSON 返回结构

    java 复制代码
    @Data @Builder
    public class ApiResponse<T> {
      private int code; private String message; private T data; private long timestamp;
    }
  • 登录成功/失败/未认证/注销的 JSON 处理器(节选)

    java 复制代码
    @Override public void onAuthenticationSuccess(..., Authentication auth){
      var info = Map.of("username",auth.getName(),"loginTime",System.currentTimeMillis());
      writeJsonResponse(resp,200, ApiResponse.success("登录成功", info));
    }
  • 权限不足(403)处理(节选)

    java 复制代码
    @Override public void handle(...){
      write403(ApiResponse.forbidden("权限不足,无法访问该资源"));
    }

可视化演示页:左右布局便于截图

  • 页面:/api-demo
  • 左侧:API 登录表单与说明;右侧:JSON 响应结果实时显示

页面片段(布局 CSS 已调整为"左侧操作,右侧响应"):

html 复制代码
<div class="login-response-container">
  <div class="login-section">...</div>
  <div class="response-section"><div id="responseArea"></div></div>
</div>

实操指南(含你的测试过程)

前置:启动应用(端口 18080),使用数据库认证模式(已内置三类账户):

  • dbuser/db123(USER)
  • dbadmin/dbadmin123(USER,ADMIN)
  • dbmanager/dbmanager123(USER,MANAGER)

登录成功(JSON,不重定向)

  • 打开 /api-demo

  • 左侧输入:用户名 dbuser,密码 db123

  • 点击"API 登录"

  • 预期:右侧显示

    { "code":200, "message":"登录成功", "data":{"username":"dbuser", ...} }

登录失败(用户名或密码错误)

  • 仍在 /api-demo

  • 输入:用户名 dbuser,密码 wrong

  • 点击"API 登录"

  • 预期:右侧显示

    { "code":401, "message":"用户名或密码错误" }

访问受保护接口(已登录)

  • 点击"调用用户接口"或"获取当前用户信息"

  • 预期:右侧显示 200 JSON,包含当前用户名与权限集合

权限不足(403)

  • 以 dbuser 登录(无 ADMIN)

  • 点击"调用管理员接口"

  • 预期:右侧显示

    { "code":403, "message":"权限不足,无法访问该资源" }

注销成功(JSON)

  • 点击"API 注销"

  • 预期:右侧显示

    { "code":200, "message":"注销成功" }


单体 vs 前后端分离:Handler 层面的差异对照

  • 单体(页面流)
    • 登录成功:302 重定向到 /dashboard
    • 登录失败:302 重定向到 /login?error
    • 未认证:302 到 /login
    • 权限不足:渲染 403 页面
    • 注销成功:302 到首页
  • 分离(JSON 流,本篇 /api/**)
    • 登录成功:200 JSON(不重定向)
    • 登录失败:401 JSON
    • 未认证:401 JSON
    • 权限不足:403 JSON
    • 注销成功:200 JSON

常见问题与排查

  • 401 但你"自认为登录了":确认是否在 /api/login 登录;/login 与 /api/login 分属两条链
  • 403 但你觉得自己是管理员:核对登录账号是否具备 ROLE_ADMIN(dbadmin)
  • 跨域(CORS):本项目演示同源访问,若前端独立域名,请在 API 链路上配置 CORS
  • CSRF:/api/** 关闭 CSRF;如采用 Cookie 场景,请按需启用并前后配合

总结

  • 本篇实现了"相同认证模型,不同响应形态"的演示:/api/** 走 JSON,不影响原页面流
  • 通过可视化页面 /api-demo,你可以一屏操作并截图:登录成功、失败、权限不足、注销成功

感谢

感谢你读到这里,说明你已经成功地忍受了我的文字考验!🎉

希望这篇文章没有让你想砸电脑,也没有让你打瞌睡。

如果有一点点收获,那我就心满意足了。

未来的路还长,愿你
遇见难题不慌张,遇见bug不抓狂,遇见好内容常回访。

记得给自己多一点耐心,多一点幽默感,毕竟生活已经够严肃了。

如果你有想法、吐槽或者想一起讨论的,欢迎留言,咱们一起玩转技术,笑对人生!😄

祝你代码无bug,生活多彩,心情常青!🚀