对于管理系统或其他需要用户登录的系统,登录验证都是必不可少的环节,在 SpringBoot 开发的项目中,通过实现拦截器来实现用户登录拦截并验证。
SpringBoot 通过实现HandlerInterceptor
接口实现拦截器,通过实现WebMvcConfigurer
接口实现一个配置类,在配置类中注入拦截器,最后再通过 @Configuration 注解注入配置.
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 之前执行,因此拦截器的功能主要就是在这个部分实现:
user
对象存在;true
,那么 Controller 就会继续后面的操作;preHandle
.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 注解将配置注入。
只需一次登录,如果登录过,下一次再访问的时候就无需再次进行登录拦截,可以直接访问网站里面的内容了。
在正确登录之后,就将user
保存到session
中,再次访问页面的时候,登录拦截器就可以找到这个user
对象,就不需要再次拦截到登录界面了.
UserController
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
/**
* 发送邮箱验证码
* @return
*/
@PostMapping("/sendCode")
public BaseResponse<String> sendCode(@RequestBody String email) {
// 发送短信验证码并保存验证码
String code = userService.sendCode(email);
return ResultUtils.success(code);
}
/**
* 注册功能
* @param userRegisterRequest
* @return
*/
@PostMapping("/register")
public BaseResponse<Long> 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<User> 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<Integer> 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<User> {
/**
* 发送验证码
* @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<UserMapper,User>
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<User> 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<User> 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实现登录拦截器(实战版)