相关推荐recommended
SpringBoot实现登录拦截器超详细(springboot拦截器excludePathPatterns方法不生效的坑)
作者:mmseoamin日期:2023-12-14

文章目录

  • SpringBoot实现登录拦截器
    • 1、SpringBoot 实现登录拦截的原理
      • 1.1、实现`HandlerInterceptor`接口
      • 1.2、实现`WebMvcConfigurer`接口,注册拦截器
      • 1.3、保持登录状态
      • springboot拦截器excludePathPatterns方法不生效的坑与解决方法
          • 一、前言
          • 二、问题
          • 三、解决方法
          • 四、总结
          • 五、扩展
            • "/user/login"
            • "/login"

              SpringBoot实现登录拦截器

              对于管理系统或其他需要用户登录的系统,登录验证都是必不可少的环节,在 SpringBoot 开发的项目中,通过实现拦截器来实现用户登录拦截并验证。

              1、SpringBoot 实现登录拦截的原理

              SpringBoot 通过实现HandlerInterceptor接口实现拦截器,通过实现WebMvcConfigurer接口实现一个配置类,在配置类中注入拦截器,最后再通过 @Configuration 注解注入配置.

              1.1、实现HandlerInterceptor接口

              实现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 之前执行,因此拦截器的功能主要就是在这个部分实现:

              1. 检查 session 中是否有user对象存在;
              2. 如果存在,就返回true,那么 Controller 就会继续后面的操作;
              3. 如果不存在,就会重定向到登录界面

                就是通过这个拦截器,使得 Controller 在执行之前,都执行一遍preHandle.

              1.2、实现WebMvcConfigurer接口,注册拦截器

              实现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 注解将配置注入。

              1.3、保持登录状态

              只需一次登录,如果登录过,下一次再访问的时候就无需再次进行登录拦截,可以直接访问网站里面的内容了。

              在正确登录之后,就将user保存到session中,再次访问页面的时候,登录拦截器就可以找到这个user对象,就不需要再次拦截到登录界面了.

              UserController

              @Slf4j
              @RestController
              @RequestMapping("/user")
              public class UserController {
                  @Resource
                  private UserService userService;
                  /**
                   * 发送邮箱验证码
                   * @return
                   */
                  @PostMapping("/sendCode")
                  public BaseResponse sendCode(@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 ServiceImpl
                      implements 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方法不生效的坑与解决方法

              一、前言

              最近在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的路径开始配置即可

              “/user/login”
               //重定向登录页面
               response.sendRedirect(request.getContextPath() + "/user/login");
              

              会被重定向到

              http://127.0.0.1:8080/user/login

              SpringBoot实现登录拦截器超详细(springboot拦截器excludePathPatterns方法不生效的坑),image-20230215124957402,第1张

              “/login”
               //重定向登录页面
               response.sendRedirect(request.getContextPath() + "/login");
              

              http://127.0.0.1:8080/login

              SpringBoot实现登录拦截器超详细(springboot拦截器excludePathPatterns方法不生效的坑),image-20230215125144089,第2张

              "/user/login"也是从Controller的路径开始配置

              参考博文:SpringBoot实现登录拦截器(实战版)