Spring Security 前后端分离实现登录
前后端分离的数据交互
在前后端分离这样的开发架构下,前后端的交互都是通过 JSON 来进行,无论登录成功还是失败,后端只返回json,不做页面跳转。页面跳转是前端的事情。
登录成功了,服务端就返回一段登录成功的提示 JSON 给前端,前端收到之后,由前端自己决定页面跳转,和后端没有关系了。
登录失败了,服务端就返回一段登录失败的提示 JSON 给前端,前端收到之后,由前端自己决定页面跳转,和后端没有关系了。
登录成功
之前我们配置登录成功的处理是通过如下两个方法来配置的:
- defaultSuccessUrl
- successForwardUrl
这两个都是配置跳转地址的,适用于前后端不分的开发。除了这两个方法之外,还有一个必杀技,那就是 successHandler。
successHandler 的功能十分强大,甚至已经囊括了 defaultSuccessUrl 和 successForwardUrl 的功能。我们来看一下:
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,Authentication authentication) throws IOException,ServletException {
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write(new ObjectMapper().writeValueAsString(authentication.getPrincipal()));
out.flush();
out.close();
}
})
successHandler 方法的参数是一个 AuthenticationSuccessHandler 对象,这个对象要我们实现onAuthenticationSuccess方法
onAuthenticationSuccess 方法有三个参数,分别是:
- HttpServletRequest:利用 HttpServletRequest 我们可以做服务端跳转,
- HttpServletResponse:利用 HttpServletResponse 我们可以做客户端跳转,当然,也可以返回 JSON 数据。
- Authentication:Authentication 参数保存了我们刚刚登录成功的用户信息。
配置完成后,我们再去登录,就可以看到登录成功的用户信息通过 JSON 返回到前端了,如下:
登录失败也有一个类似的回调,如下:
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
out.write(new ObjectMapper().writeValueAsString(e.getMessage()));
out.flush();
out.close();
}
})
失败的回调也是三个参数,前两个就不用说了,第三个是一个 Exception,对于登录失败,会有不同的原因,Exception 中则保存了登录失败的原因,我们可以将之通过 JSON 返回到前端。
启动项目,用postman访问登录接口,故意输错用户名,或密码。无论是用户名还是密码错误都返回如下结果:
当然大家也可以根据不同的异常类型,我们可以给用户一个更加明确的提示:
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
RespBean respBean = RespBean.error(e.getMessage());
if (e instanceof LockedException) {
respBean.setMsg("账户被锁定,请联系管理员!");
} else if (e instanceof CredentialsExpiredException) {
respBean.setMsg("密码过期,请联系管理员!");
} else if (e instanceof AccountExpiredException) {
respBean.setMsg("账户过期,请联系管理员!");
} else if (e instanceof DisabledException) {
respBean.setMsg("账户被禁用,请联系管理员!");
} else if (e instanceof BadCredentialsException) {
respBean.setMsg("用户名或者密码输入错误,请重新输入!");
}
out.write(new ObjectMapper().writeValueAsString(respBean));
out.flush();
out.close();
这里有一个需要注意的点。当用户登录时,用户名或者密码输入错误,我们一般只给一个模糊的提示,即用户名或者密码输入错误,请重新输入,而不会给一个明确的诸如“用户名输入错误”或“密码输入错误”这样精确的提示,这会给系统带来风险。
3. 未认证处理方案
那未认证又怎么办呢?系统默认的行为是直接重定向到登录页面。
但是在前后端分离中,这个逻辑明显是有问题的,如果用户没有登录就访问一个需要认证后才能访问的页面,这个时候,我们不应该让用户重定向到登录页面,而是给用户一个尚未登录的提示,前端收到提示之后,再自行决定页面跳转。
具体配置如下:
.exceptionHandling().authenticationEntryPoint(new AuthenticationEntryPoint() {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
out.write(new ObjectMapper().writeValueAsString("尚未登录,请先登录"));
out.flush();
out.close();
}
});
在 Spring Security 的配置中加上自定义的 AuthenticationEntryPoint
处理方法,该方法中直接返回相应的 JSON 提示即可。这样,如果用户再去直接访问一个需要认证之后才可以访问的请求,就不会发生重定向操作了,服务端会直接给浏览器一个 JSON 提示,浏览器收到 JSON 之后,该干嘛干嘛。
运行结果:
4. 注销登录
注销登录之后,系统自动跳转到登录页面,这也是不合适的,如果是前后端分离项目,注销登录成功后返回 JSON 即可,配置如下:
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Authentication authentication) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
out.write(new ObjectMapper().writeValueAsString("注销成功"));
out.flush();
out.close();
}
})
.permitAll()
这样,注销成功之后,前端收到的也是 JSON 了: