Version: Next

自定义登录验证结果处理

需要灵活适应一些场景,例如:

  • 希望不同角色的用户看到不同的定制化主页
  • 采用前后端分离架构,希望返回JSON数据而不是视图

就需要自定义登录验证结果处理

  • 定义异常类型枚举类
public enum CustomExceptionType {
USER_INPUT_ERROR(400,"用户输入异常"),
SYSTEM_ERROR (500,"系统服务异常"),
OTHER_ERROR(999,"其他未知异常");
CustomExceptionType(int code, String typeDesc) {
this.code = code;
this.typeDesc = typeDesc;
}
private String typeDesc;//异常类型中文描述
private int code; //code
public String getTypeDesc() {
return typeDesc;
}
public int getCode() {
return code;
}
}
  • 自定义异常类
public class CustomException extends RuntimeException {
//异常错误编码
private int code ;
//异常信息
private String message;
private CustomException(){}
public CustomException(CustomExceptionType exceptionTypeEnum,
String message) {
this.code = exceptionTypeEnum.getCode();
this.message = message;
}
public int getCode() {
return code;
}
@Override
public String getMessage() {
return message;
}
}
  • 自定义一个后端给前端的统一响应类
/**
* 接口数据请求统一响应数据结构
*/
@Data
public class AjaxResponse {
private boolean isok;
private int code;
private String message;
private Object data;
private AjaxResponse() {}
//请求出现异常时的响应数据封装
public static AjaxResponse error(CustomException e) {
AjaxResponse resultBean = new AjaxResponse();
resultBean.setIsok(false);
resultBean.setCode(e.getCode());
if(e.getCode() == CustomExceptionType.USER_INPUT_ERROR.getCode()){
resultBean.setMessage(e.getMessage());
}else if(e.getCode() == CustomExceptionType.SYSTEM_ERROR.getCode()){
resultBean.setMessage(e.getMessage() + ",请将该异常信息发送给管理员!");
}else{
resultBean.setMessage("系统出现未知异常,请联系管理员!");
}
//TODO 这里最好将异常信息持久化
return resultBean;
}
//请求出现异常时的响应数据封装
public static AjaxResponse error(CustomExceptionType customExceptionType,
String errorMessage) {
AjaxResponse resultBean = new AjaxResponse();
resultBean.setIsok(false);
resultBean.setCode(customExceptionType.getCode());
resultBean.setMessage(errorMessage);
return resultBean;
}
//请求处理成功时的数据响应
public static AjaxResponse success() {
AjaxResponse resultBean = new AjaxResponse();
resultBean.setIsok(true);
resultBean.setCode(200);
resultBean.setMessage("success");
return resultBean;
}
//请求处理成功,并响应结果数据
public static AjaxResponse success(Object data) {
AjaxResponse resultBean = AjaxResponse.success();
resultBean.setData(data);
return resultBean;
}
}

自定义登录成功的结果处理

新建/auth/MyAuthenticationSuccessHandler类

  • 本质上应当实现AuthenticationSuccessHandler接口,但实际中采用集成它的实现类SavedRequestAwareAuthenticationSuccessHandler
  • 重写onAuthenticationSuccess方法
  • 继承它的好处:在未登录时访问一个页面,被拦截,登陆后会自动帮我们跳转到之前访问的页面
@Component
public class MyAuthenticationSuccessHandler
extends SavedRequestAwareAuthenticationSuccessHandler {
//在application配置文件中配置登陆的类型是JSON数据响应还是做页面响应
@Value("${spring.security.logintype}")
private String loginType;
private static ObjectMapper objectMapper = new ObjectMapper();
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication)
throws ServletException, IOException {
if (loginType.equalsIgnoreCase("JSON")) {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(AjaxResponse.success()));
} else {
// 会帮我们跳转到上一次请求的页面上
super.onAuthenticationSuccess(request, response, authentication);
}
}
}
  • 配置了从配置文件读取登录模式,只需要在application.yaml中添加对应设置字段
spring:
security:
loginType: JSON

自定义登录失败的结果处理

/auth/MyAuthenticationFailureHandler类,继承SimpleUrlAuthenticationFailureHandler,它是AuthenticationFailureHandler接口的实现类

  • 继承它的好处:登录失败会自动放回到登录页
@Component
public class MyAuthenticationFaliureHandler extends SimpleUrlAuthenticationFailureHandler {
//在application配置文件中配置登陆的类型是JSON数据响应还是做页面响应
@Value("${spring.security.loginType}")T
private String loginType;
private static ObjectMapper objectMapper = new ObjectMapper();
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception)
throws IOException, ServletException {
if (loginType.equalsIgnoreCase("JSON")) {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(
objectMapper.writeValueAsString(
AjaxResponse.error(
new CustomException(
CustomExceptionType.USER_INPUT_ERROR,
"用户名或密码存在错误,请检查后再次登录"))));
} else {
response.setContentType("text/html;charset=UTF-8");
super.onAuthenticationFailure(request, response, exception);
}
}
}

将自定义结果处理配置进SecurityConfig

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
@Resource
private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() //禁用跨站csrf攻击防御,后面的章节会专门讲解
.formLogin()
.successHandler(myAuthenticationSuccessHandler)
.failureHandler(myAuthenticationFailureHandler)
//.defaultSuccessUrl("/index")//登录认证成功后默认转跳的路径
//.failureUrl("/login.html") //登录认证是被跳转页面
}

JSON方式的登录页

使用Ajax请求的方式,在回调中判断是否跳转

  • 可以在成功响应中,携带让前端跳转的路由地址
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(AjaxResponse.success("/index"))); // 跳转到/index
  • 前端从响应中取出data字段,进行跳转
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
<script src="https://cdn.staticfile.org/jquery/1.12.3/jquery.min.js"></script>
</head>
<body>
<h1>字母哥业务系统登录</h1>
<form action="/login" method="post">
<span>用户名称</span><input type="text" name="username" id="username"/> <br>
<span>用户密码</span><input type="password" name="password" id="password" /> <br>
<input type="button" onclick="login()" value="登陆">
</form>
<script>
function login() {
var username = $("#username").val();
var password = $("#password").val();
if (username === "" || password === "") {
alert('用户名或密码不能为空');
return;
}
$.ajax({
type: "POST",
url: "/login",
data: {
"username": username,
"password": password
},
success: function (json) {
if (json.isok) {
// json.href = '/index';
window.location.href = json.data //根据data字段的路由进行跳转
} else {
alert(json.message)
}
},
error: function (e) {
console.log(e.responseText);
}
});
}
</script>
</body>
</html>