目录
一、引入依赖及原理简述
二、多用户维护配置
三、基础自定义配置
四、前后端分离及登录结果管理 <— 你在这里 ( •̀ ω •́ )y
五、角色权限管理基础
从此处开始,为新的原创内容,相关数据结构代码换了一套新的,与之前的代码关系不大了。
建议新建一个项目,将配置文件复制过来,然后按照步骤走。
六、RBAC 结构实现
七、自定义响应式登录与 JWT 配置
八、集成 Redis
Spring Security(四)前后端分离及登录结果管理
博主前言:本以为这个就是代替传统 jwt 的插件,没想到复杂程度如此之高。Spring Security 本身是个高度自定义化的组件,必须花时间重点学习一下。以下为个人配置学习的流程,从零到权限管理、redis嵌入等步骤。
本文基于尚硅谷的 Spring Security 教程学习,文章与原教程有不小出入,仅供参考。
B站视频链接:尚硅谷Java项目SpringSecurity+OAuth2权限管理实战教程
现在的项目基本上都是前后端分离(包括我个人的一些开源项目),前后端分离的项目,后端验证后要向前端返回json
数据,而不是跳转链接,所以要另行配置。
本文使用 fastjson 来处理序列化问题,读者亦可使用其他的方式处理,本文仅作参考。
一、定义并整合登录结果管理器
这个东西实际上可以当作LoginController
+ LoginExceptionHandler
等控制类。
我通常不习惯手写返回体,习惯定义主返回体结构,再往里面放数据,这是我定义的通用返回结果类:
我使用的返回体为黑马的几个实战项目常用的返回体,并稍加改造。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
|
@Data public class Result<T> {
private Integer code;
private String msg;
private T data;
public static <T> Result<T> success(T data) { var result = new Result<T>(); result.data = data; result.msg = "success"; result.code = 200; return result; }
public static <T> Result<T> success() { var result = new Result<T>(); result.data = null; result.msg = "success"; result.code = 200; return result; }
public static <T> Result<T> error(int code, String msg) { var result = new Result<T>(); result.code = code; result.msg = msg; return result; } }
|
对于登录结果管理,我们要实现五件事:
- 登录成功之后,处理返回
json
数据
- 登录失败之后,处理返回
json
数据 * 2
- 注销成功之后,处理返回
json
数据 * 3
- 请求未认证后,处理返回
json
数据 * 4
- 会话并发处理,处理返回
json
数据 * 5
╮(╯▽╰)╭ 五次啊五次 ╮(╯▽╰)╭
会话并发处理:简单来讲就是后登录的账号会使先登录的账号失效
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
|
public class AuthenticationHandler implements AuthenticationSuccessHandler, AuthenticationFailureHandler, LogoutSuccessHandler, AuthenticationEntryPoint, SessionInformationExpiredStrategy {
@Override public void onAuthenticationSuccess( HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { var resultToken = "登录成功!这是登录后的 token 数据...";
var result = Result.success(resultToken); var resultJSON = JSON.toJSONString(result);
response.setContentType("application/json;charset=UTF-8"); response.getWriter().println(resultJSON); }
@Override public void onAuthenticationFailure( HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException { var result = Result.error(-1, exception.getLocalizedMessage()); var resultJSON = JSON.toJSONString(result);
response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().println(resultJSON); }
@Override public void onLogoutSuccess( HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { var resultData = "注销成功!这是注销后的数据..."; var result = Result.success(resultData); var resultJSON = JSON.toJSONString(result);
response.setContentType("application/json;charset=UTF-8"); response.getWriter().println(resultJSON); }
@Override public void commence( HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { var result = Result.error(-1, "登录已过期,请重新登录"); var resultJSON = JSON.toJSONString(result);
response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.getWriter().println(resultJSON); }
@Override public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException { var result = Result.error(-1, "该账号已从其他设备登录"); var resultJSON = JSON.toJSONString(result);
HttpServletResponse response = event.getResponse(); response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpServletResponse.SC_CONFLICT); response.getWriter().println(resultJSON); } }
|
在这之后还没完,要在Config
类中配置好,不然是不会生效的。
因为是前后端分离,不要忘了跨域问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| .cors(Customizer.withDefaults())
.formLogin(form -> { form.loginPage("/login").permitAll() .successHandler(new AuthenticationHandler()) .failureHandler(new AuthenticationHandler()); })
.logout(logout -> logout.logoutSuccessHandler(new AuthenticationHandler()))
.exceptionHandling(exception -> exception.authenticationEntryPoint(new AuthenticationHandler()));
.sessionManagement(session -> { session.maximumSessions(1) .expiredSessionStrategy(new AuthenticationHandler()); });
|
二、用户认证信息分析
上述登录结果管理器代码中,我们可以调用Authentication
中的各项数据,其中的名称貌似跟用户没什么关系,它们实际上是 Spring Security 的一套新的概念。
在 Spring Security 框架中,SecurityContextHolder
、SecurityContext
、Authentication
、Principal
和Credential
是一些与身份验证和授权相关的重要概念。它们之间的关系如下:
SecurityContextHolder
:SecurityContextHolder
是 Spring Security 存储已认证用户详细信息的地方。
SecurityContext
:SecurityContext
是从SecurityContextHolder
获取的内容,包含当前已认证用户的Authentication
信息。
Authentication
:Authentication
表示用户的身份认证信息。它包含了用户的Principal
、Credential
和Authority
信息。
Principal
:表示用户的身份标识。它通常是一个表示用户的实体对象,例如用户名。Principal
可以通过Authentication
对象的getPrincipal()
方法获取。
Credentials
:表示用户的凭证信息,例如密码、证书或其他认证凭据。Credential
可以通过Authentication
对象的getCredentials()
方法获取。
GrantedAuthority
:表示用户被授予的权限