对于管理系统或其他需要用户登录的系统,登录验证都是必不可少的环节,在 SpringBoot 开发的项目中,通过实现拦截器来实现用户登录拦截并验证。
SpringBoot 通过实现HandlerInterceptor接口实现拦截器,通过实现WebMvcConfigurer接口实现一个配置类,在配置类中注入拦截器,最后再通过 @Configuration 注解注入配置.
实现HandlerInterceptor接口需要实现 3 个方法:preHandle、postHandle、afterCompletion.
3 个方法各自的功能如下:
public class UserLoginInterceptor implements HandlerInterceptor { /*** * 在请求处理之前进行调用(Controller方法调用之前) */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("执行了拦截器的preHandle方法"); try { HttpSession session = request.getSession(); //统一拦截(查询当前session是否存在user)(这里user会在每次登录成功后,写入session) User user = (User) session.getAttribute(USER_LOGIN_STATE); if (user != null) { return true; } //重定向登录页面 response.sendRedirect(request.getContextPath() + "/user/login"); } catch (Exception e) { e.printStackTrace(); } return false; //如果设置为false时,被请求时,拦截器执行到此处将不会继续操作 //如果设置为true时,请求将会继续执行后面的操作 } /*** * 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后) */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("执行了拦截器的postHandle方法"); } /*** * 整个请求结束之后被调用,也就是在DispatchServlet渲染了对应的视图之后执行(主要用于进行资源清理工作) */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("执行了拦截器的afterCompletion方法"); } }
preHandle在 Controller 之前执行,因此拦截器的功能主要就是在这个部分实现:
就是通过这个拦截器,使得 Controller 在执行之前,都执行一遍preHandle.
实现WebMvcConfigurer接口来实现一个配置类,将上面实现的拦截器的一个对象注册到这个配置类中.
@Configuration public class LoginConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { //注册TestInterceptor拦截器 InterceptorRegistration registration = registry.addInterceptor(new UserLoginInterceptor()); //所有路径都被拦截 registration.addPathPatterns("/**"); //添加不拦截路径 registration.excludePathPatterns( "/user/login", "/user/register", "/**/*.html", "/**/*.js", "/**/*.css" ); } }
将拦截器注册到了拦截器列表中,并且指明了拦截哪些访问路径,不拦截哪些访问路径,不拦截哪些资源文件;最后再以 @Configuration 注解将配置注入。
只需一次登录,如果登录过,下一次再访问的时候就无需再次进行登录拦截,可以直接访问网站里面的内容了。
在正确登录之后,就将user保存到session中,再次访问页面的时候,登录拦截器就可以找到这个user对象,就不需要再次拦截到登录界面了.
UserController
@Slf4j @RestController @RequestMapping("/user") public class UserController { @Resource private UserService userService; /** * 发送邮箱验证码 * @return */ @PostMapping("/sendCode") public BaseResponsesendCode(@RequestBody String email) { // 发送短信验证码并保存验证码 String code = userService.sendCode(email); return ResultUtils.success(code); } /** * 注册功能 * @param userRegisterRequest * @return */ @PostMapping("/register") public BaseResponse register(@RequestBody UserRegisterRequest userRegisterRequest){ if(userRegisterRequest==null){ throw new BusinessException(ErrorCode.PARAMS_ERROR,"请求参数为空"); } String email = userRegisterRequest.getEmail(); String userpassword = userRegisterRequest.getUserPassword(); String checkpassword = userRegisterRequest.getCheckPassword(); String userName = userRegisterRequest.getName(); String code = userRegisterRequest.getCode(); if(StringUtils.isAnyBlank(email,userpassword,checkpassword,userName,code)){ return null; } long result = userService.userRegister(email, userpassword, checkpassword, userName, code); return ResultUtils.success(result); } /** * 登录功能 * @param userLoginRequest * @param request * @return */ @PostMapping("/login") public BaseResponse userdoLogin(@RequestBody UserLoginRequest userLoginRequest, HttpServletRequest request){ if(userLoginRequest==null){ throw new BusinessException(ErrorCode.PARAMS_ERROR,"请求参数为空"); } String email = userLoginRequest.getEmail(); String password = userLoginRequest.getPassword(); if (StringUtils.isAnyBlank(email,password)){ return null; } User result = userService.userdoLogin(email, password,request); return ResultUtils.success(result); } /** * 登出功能 * @param request * @return */ @PostMapping("/logout") public BaseResponse userlogout(HttpServletRequest request){ if(request==null){ throw new BusinessException(ErrorCode.NOT_LOGIN,"该用户没有登录"); } int result = userService.userLogout(request); return ResultUtils.success(result); }
UserService
public interface UserService extends IService{ /** * 发送验证码 * @param email * @return */ String sendCode(String email); /** * 用户注册 * * @param userEmail 用户邮箱 * @param userPassword 用户密码 * @param checkPassword 用户检验密码 * * @return */ long userRegister(String userEmail,String userPassword,String checkPassword,String userName,String code); /** * 用户登录 * @param email * @param password * @param request * @return */ User userdoLogin(String email, String password, HttpServletRequest request); /** * 用户登出 * @param request * @return */ int userLogout(HttpServletRequest request);
UserServiceImpl
@Service @Slf4j public class UserServiceImpl extends ServiceImplimplements UserService{ @Resource private UserMapper userMapper; @Resource private StringRedisTemplate stringRedisTemplate; public static final String SALT = "qgc"; /** * 发送邮箱验证码 * @param email * @return */ @Override public String sendCode(String email) { //1.生成验证码 String code = RandomUtil.randomNumbers(6); //2.保存验证码到redis中 //set key value ex stringRedisTemplate.opsForValue().set(code + LOGIN_CODE_KEY, code, LOGIN_CODE_TTL, TimeUnit.MINUTES); //3.发送验证码 log.debug("发送邮箱验证码成功,验证码:{}", code); return code; } /** * 用户注册 * @param email 邮箱 * @param userPassword 用户密码 * @param checkPassword 用户检验密码 * * @param userName 用户名字 * @param code 验证码 * @return */ @Override public long userRegister(String email,String userPassword,String checkPassword,String userName,String code) { //1.校验 if(StringUtils.isAnyBlank(email,userPassword,checkPassword,userName,code)){ throw new BusinessException(PARAMS_ERROR,"请求参数为空"); } if(userPassword.length() < 8 ||checkPassword.length() < 8){ throw new BusinessException(PARAMS_ERROR,"密码小于8位"); } if(userName.length()> 10){ throw new BusinessException(PARAMS_ERROR,"名字大于10位"); } if(code.length() != 6){ throw new BusinessException(PARAMS_ERROR,"验证码长度应该为6位"); } //密码和校验密码相同 if(!userPassword.equals(checkPassword)){ throw new BusinessException(PARAMS_ERROR); } //账户邮箱不能重复 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("email",email); Long count = userMapper.selectCount(queryWrapper); if (count>0){ throw new BusinessException(PARAMS_ERROR); } //昵称不能重复 queryWrapper = new QueryWrapper<>(); queryWrapper.eq("name",userName); count = userMapper.selectCount(queryWrapper); if (count>0){ throw new BusinessException(PARAMS_ERROR); } //判断验证码是否正确 String cachecode = stringRedisTemplate.opsForValue().get(code + LOGIN_CODE_KEY); if(cachecode==null||!cachecode.equals(code)){ //不一致,报错 throw new BusinessException(PARAMS_ERROR); } //2.加密 String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes(StandardCharsets.UTF_8)); //3.插入数据 User user = new User(); user.setEmail(email); user.setPassword(encryptPassword); user.setName(userName); boolean res = this.save(user); if(!res){ return -1; } return user.getId(); } /** * 用户登录 * @param email * @param password * @param request * @return */ @Override public User userdoLogin(String email, String password,HttpServletRequest request) { //1.校验 if(StringUtils.isAnyBlank(email,password)){ return null; } if (RegexUtils.isEmailInvalid(email)) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "邮箱格式错误"); } if(password.length() < 8 ){ return null; } //2.加密 String encryptPassword = DigestUtils.md5DigestAsHex((SALT + password).getBytes(StandardCharsets.UTF_8)); //判断账户是否存在 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("email",email); queryWrapper.eq("password",encryptPassword); User user = userMapper.selectOne(queryWrapper); if(user==null){ log.info("user login failed"); return null; } //用户脱敏 User safeUser = getSafeUser(user); //4.记录用户登录状态 request.getSession().setAttribute(USER_LOGIN_STATE,safeUser); return safeUser; } /** * 登出功能 * @param request * @return */ @Override public int userLogout(HttpServletRequest request) { request.getSession().removeAttribute(USER_LOGIN_STATE); return 1; }
最近在springboot项目里需要配置个拦截器白名单,用excludePathPatterns方法配置些url,让拦截器不拦截这些url;
本来这是个很简单的东西,但是配置完毕后就是没有生效;
在此记录下这个坑的解决方法。
1.例如,想让以下url不被拦截器拦截:
http://localhost:8080/api/department/add
2.拦截器配置代码如下:
@Configuration public class LoginConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { //注册TestInterceptor拦截器 InterceptorRegistration registration = registry.addInterceptor(new UserLoginInterceptor()); //所有路径都被拦截 registration.addPathPatterns("/**"); //添加不拦截路径 registration.excludePathPatterns( "/user/login", "/user/register", "/api/department/add" "/**/*.html", "/**/*.js", "/**/*.css" ); } }
3.看起来没有问题,但是当访问上方url的时候,还是会被拦截器拦截,就很坑。
1.通过排查发现,原来,在application.yml中,是这样配置的:
server: port: 8080 servlet: context-path: /api
2.所以,还是拦截器的url配置错了,想不拦截的话,需要这样配置:
@Configuration public class LoginConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { //注册TestInterceptor拦截器 InterceptorRegistration registration = registry.addInterceptor(new UserLoginInterceptor()); //所有路径都被拦截 registration.addPathPatterns("/**"); //添加不拦截路径 registration.excludePathPatterns( "/user/login", "/user/register", "/department/add" "/**/*.html", "/**/*.js", "/**/*.css" ); } }
3.这样,访问这个url,才能不被拦截器拦截:
http://localhost:8080/survey-project/download/special
1.配置拦截器时,如果excludePathPatterns没有生效,可能是url配置有问题。
2.可以检查下application.yml的context-path,或者其它类似的地方,配置拦截器的url不应该包含这些路径,只要从Controller的路径开始配置即可。
使用response对象的sendRedirect()方法将用户的请求重定向到指定路径,这个路径由request对象的getContextPath()方法获取,再加上字符串 “/” 组成。getContextPath()方法返回当前web应用程序的上下文路径,此处加的字符串路径也是从Controller的路径开始配置即可
//重定向登录页面 response.sendRedirect(request.getContextPath() + "/user/login");
会被重定向到
http://127.0.0.1:8080/user/login
//重定向登录页面 response.sendRedirect(request.getContextPath() + "/login");
http://127.0.0.1:8080/login
"/user/login"也是从Controller的路径开始配置
参考博文:SpringBoot实现登录拦截器(实战版)
上一篇:数据库实验七 存储过程实验