在之前Servlet开发中,对登陆校验有两种方法:
从上述可以看出这样做比较复杂,因为如果有许多地方需要进行登陆验证的话,就需要写重复的校验代码或者重复调用判断登陆的方法
对此我们提供Spring拦截器来实现用户的统一登陆验证,拦截器的实现分为以下两个步骤:
HandlerInterceptor
接口并重写preHandle
方法@Configuration
注解WebMvcConfigurer
接口addInterceptors
方法注意:一个项目中可以配置多个拦截器
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
if(session != null && session.getAttribute("user") != null){
return true;
}
return false;
}
}
preHandle方法的返回值为boolean类型:
如果返回为false,前端展示的为空白,对于登陆校验,当拦截未通过时,我们希望返回到登陆界面,所以可以在返回false之前重定向到登陆页面
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
if(session != null && session.getAttribute("user") != null){
return true;
}
//当代码进行到这一步时,说明拦截未通过,防止前端显示空白,重定向到登陆页面
response.sendRedirect("/login.html");
return false;
}
}
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()).
addPathPatterns("/**"). //拦截所有url
excludePathPatterns("/user/login"). //排除登陆接口
excludePathPatterns("/user/register"). //排除注册接口
//排除所有的静态页面
excludePathPatterns("/login.html").
excludePathPatterns("/register.html").
excludePathPatterns("/**/*.js").
excludePathPatterns("/**/*.css").
excludePathPatterns("/**/*.jpg");
}
}
说明:addPathPatterns表示需要拦截的URL,excludePathPatterns表示需要排除的URL
@RequestMapping("/login")
public String login(String username, String password, HttpServletRequest req){
if(username==null || password==null){
return "请输入用户名和密码";
}
if(username.equals("abc") && password.equals("123")){
HttpSession session = req.getSession(true);
session.setAttribute("user","user");
return "登陆成功";
}
//登录失败也要返回前端失败的数据,否则前端会展示空白
response.setContentType("application/json; charset=utf8");
Map<String,Object> result = new HashMap<>();
result.put("code",-1);
result.put("msg","未登录不允许访问");
result.put("data",null);
response.getWriter().write(objectMapper.writeValueAsString(result));
return "登陆失败";
}
@RequestMapping("/content")
public String content(){
return "已经登陆成功";
}
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()).
addPathPatterns("/**"). //拦截所有url
excludePathPatterns("/user/login"). //排除登陆接口
excludePathPatterns("/login.html");
}
}
经过上述步骤,我们的登陆拦截器验证成功,满足我们需求
给所有的请求url添加前缀
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
//c -> true意思是给所有的controller都添加api前缀
configurer.addPathPrefix("api",c -> true);
}
server:
servlet:
context-path: /api
启动程序,用之前的url访问登陆接口,发现找不到资源
添加api前缀访问,访问成功
注意:如果添加了拦截规则,也必须给拦截规则添加前缀,否则会被拦截器拦截到,前端将显示空白
excludePathPatterns("/api/user/login")
统一异常的步骤:
@ControllerAdvice
表示当前类是针对controller的通知类(增强类),@ExceptionHandler
是异常处理器,两个结合表示当出现异常的时候执行某个通知
//返回json格式数据,将@Controller与@ResponseBody注解合成一个注解@RestController
@RestControllerAdvice
public class MyException {
@ExceptionHandler(Exception.class)
public Object handler(Exception e){
Map<String,Object> map = new HashMap<>();
map.put("state",-1);
map.put("data",null);
map.put("msg",e.getMessage());
return map;
}
}
对异常进行统一处理后,当服务器出现500错误时,会给前端返回自定义的错误信息,不会再直接返回给前端500状态码了
统一数据返回格式步骤:
@RestControllerAdvice
public class MyResponse implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
//返回true表示返回数据之前对数据进行重写,也就是会进入beforeBodyWrite方法再返回
//返回false表示对返回数据不进行任何处理,不会进入beforeBodyWrite方法,直接返回
return true;
}
@Override //controller返回结果之前,进行格式重写
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
//假设标准返回格式为hashmap
if(body instanceof HashMap){
return body; //如果格式正确,直接返回
}
//如果格式不正确,进行格式重写
Map<String,Object> result = new HashMap<>();
result.put("state",1);
result.put("data",body);
result.put("message","");
return request;
}
}
上述是保底策略,也就是当程序返回的格式不正确时,才会进行数据格式重写,我们在写代码的时候,往往都是返回自定义数据格式
如果程序在controller中返回的类型为String,此时,程序会出现类型转换异常
所以我们可以在重写格式里,对String类单独判断一下
if(body instanceof String){
return objectMapper.writeValueAsString(body);
}