对于管理系统或其他需要用户登录的系统,登录验证都是必不可少的环节,在 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(&#64;RequestBody String email) {
// 发送短信验证码并保存验证码
String code &#61; userService.sendCode(email);
return ResultUtils.success(code);
}
/**
* 注册功能
* &#64;param userRegisterRequest
* &#64;return
*/
&#64;PostMapping("/register")
public BaseResponse<Long> register(&#64;RequestBody UserRegisterRequest userRegisterRequest){
if(userRegisterRequest&#61;&#61;null){
throw new BusinessException(ErrorCode.PARAMS_ERROR,"请求参数为空");
}
String email &#61; userRegisterRequest.getEmail();
String userpassword &#61; userRegisterRequest.getUserPassword();
String checkpassword &#61; userRegisterRequest.getCheckPassword();
String userName &#61; userRegisterRequest.getName();
String code &#61; userRegisterRequest.getCode();
if(StringUtils.isAnyBlank(email,userpassword,checkpassword,userName,code)){
return null;
}
long result &#61; userService.userRegister(email, userpassword, checkpassword, userName, code);
return ResultUtils.success(result);
}
/**
* 登录功能
* &#64;param userLoginRequest
* &#64;param request
* &#64;return
*/
&#64;PostMapping("/login")
public BaseResponse<User> userdoLogin(&#64;RequestBody UserLoginRequest userLoginRequest, HttpServletRequest request){
if(userLoginRequest&#61;&#61;null){
throw new BusinessException(ErrorCode.PARAMS_ERROR,"请求参数为空");
}
String email &#61; userLoginRequest.getEmail();
String password &#61; userLoginRequest.getPassword();
if (StringUtils.isAnyBlank(email,password)){
return null;
}
User result &#61; userService.userdoLogin(email, password,request);
return ResultUtils.success(result);
}
/**
* 登出功能
* &#64;param request
* &#64;return
*/
&#64;PostMapping("/logout")
public BaseResponse<Integer> userlogout(HttpServletRequest request){
if(request&#61;&#61;null){
throw new BusinessException(ErrorCode.NOT_LOGIN,"该用户没有登录");
}
int result &#61; userService.userLogout(request);
return ResultUtils.success(result);
}
UserService
public interface UserService extends IService<User> {
/**
* 发送验证码
* &#64;param email
* &#64;return
*/
String sendCode(String email);
/**
* 用户注册
*
* &#64;param userEmail 用户邮箱
* &#64;param userPassword 用户密码
* &#64;param checkPassword 用户检验密码
*
* &#64;return
*/
long userRegister(String userEmail,String userPassword,String checkPassword,String userName,String code);
/**
* 用户登录
* &#64;param email
* &#64;param password
* &#64;param request
* &#64;return
*/
User userdoLogin(String email, String password, HttpServletRequest request);
/**
* 用户登出
* &#64;param request
* &#64;return
*/
int userLogout(HttpServletRequest request);
UserServiceImpl
&#64;Service
&#64;Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper,User>
implements UserService{
&#64;Resource
private UserMapper userMapper;
&#64;Resource
private StringRedisTemplate stringRedisTemplate;
public static final String SALT &#61; "qgc";
/**
* 发送邮箱验证码
* &#64;param email
* &#64;return
*/
&#64;Override
public String sendCode(String email) {
//1.生成验证码
String code &#61; RandomUtil.randomNumbers(6);
//2.保存验证码到redis中 //set key value ex
stringRedisTemplate.opsForValue().set(code &#43; LOGIN_CODE_KEY, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);
//3.发送验证码
log.debug("发送邮箱验证码成功&#xff0c;验证码&#xff1a;{}", code);
return code;
}
/**
* 用户注册
* &#64;param email 邮箱
* &#64;param userPassword 用户密码
* &#64;param checkPassword 用户检验密码
*
* &#64;param userName 用户名字
* &#64;param code 验证码
* &#64;return
*/
&#64;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() !&#61; 6){
throw new BusinessException(PARAMS_ERROR,"验证码长度应该为6位");
}
//密码和校验密码相同
if(!userPassword.equals(checkPassword)){
throw new BusinessException(PARAMS_ERROR);
}
//账户邮箱不能重复
QueryWrapper<User> queryWrapper &#61; new QueryWrapper<>();
queryWrapper.eq("email",email);
Long count &#61; userMapper.selectCount(queryWrapper);
if (count>0){
throw new BusinessException(PARAMS_ERROR);
}
//昵称不能重复
queryWrapper &#61; new QueryWrapper<>();
queryWrapper.eq("name",userName);
count &#61; userMapper.selectCount(queryWrapper);
if (count>0){
throw new BusinessException(PARAMS_ERROR);
}
//判断验证码是否正确
String cachecode &#61; stringRedisTemplate.opsForValue().get(code &#43; LOGIN_CODE_KEY);
if(cachecode&#61;&#61;null||!cachecode.equals(code)){
//不一致&#xff0c;报错
throw new BusinessException(PARAMS_ERROR);
}
//2.加密
String encryptPassword &#61; DigestUtils.md5DigestAsHex((SALT &#43; userPassword).getBytes(StandardCharsets.UTF_8));
//3.插入数据
User user &#61; new User();
user.setEmail(email);
user.setPassword(encryptPassword);
user.setName(userName);
boolean res &#61; this.save(user);
if(!res){
return -1;
}
return user.getId();
}
/**
* 用户登录
* &#64;param email
* &#64;param password
* &#64;param request
* &#64;return
*/
&#64;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 &#61; DigestUtils.md5DigestAsHex((SALT &#43; password).getBytes(StandardCharsets.UTF_8));
//判断账户是否存在
QueryWrapper<User> queryWrapper &#61; new QueryWrapper<>();
queryWrapper.eq("email",email);
queryWrapper.eq("password",encryptPassword);
User user &#61; userMapper.selectOne(queryWrapper);
if(user&#61;&#61;null){
log.info("user login failed");
return null;
}
//用户脱敏
User safeUser &#61; getSafeUser(user);
//4.记录用户登录状态
request.getSession().setAttribute(USER_LOGIN_STATE,safeUser);
return safeUser;
}
/**
* 登出功能
* &#64;param request
* &#64;return
*/
&#64;Override
public int userLogout(HttpServletRequest request) {
request.getSession().removeAttribute(USER_LOGIN_STATE);
return 1;
}
springboot拦截器excludePathPatterns方法不生效的坑与解决方法
最近在springboot项目里需要配置个拦截器白名单&#xff0c;用excludePathPatterns方法配置些url&#xff0c;让拦截器不拦截这些url&#xff1b;
本来这是个很简单的东西&#xff0c;但是配置完毕后就是没有生效&#xff1b;
在此记录下这个坑的解决方法。
1.例如&#xff0c;想让以下url不被拦截器拦截&#xff1a;
http://localhost:8080/api/department/add
2.拦截器配置代码如下&#xff1a;
&#64;Configuration
public class LoginConfig implements WebMvcConfigurer {
&#64;Override
public void addInterceptors(InterceptorRegistry registry) {
//注册TestInterceptor拦截器
InterceptorRegistration registration &#61; registry.addInterceptor(new UserLoginInterceptor());
//所有路径都被拦截
registration.addPathPatterns("/**");
//添加不拦截路径
registration.excludePathPatterns(
"/user/login",
"/user/register",
"/api/department/add"
"/**/*.html",
"/**/*.js",
"/**/*.css"
);
}
}
3.看起来没有问题&#xff0c;但是当访问上方url的时候&#xff0c;还是会被拦截器拦截&#xff0c;就很坑。
1.通过排查发现&#xff0c;原来&#xff0c;在application.yml中&#xff0c;是这样配置的&#xff1a;
server:
port: 8080
servlet:
context-path: /api
2.所以&#xff0c;还是拦截器的url配置错了&#xff0c;想不拦截的话&#xff0c;需要这样配置&#xff1a;
&#64;Configuration
public class LoginConfig implements WebMvcConfigurer {
&#64;Override
public void addInterceptors(InterceptorRegistry registry) {
//注册TestInterceptor拦截器
InterceptorRegistration registration &#61; registry.addInterceptor(new UserLoginInterceptor());
//所有路径都被拦截
registration.addPathPatterns("/**");
//添加不拦截路径
registration.excludePathPatterns(
"/user/login",
"/user/register",
"/department/add"
"/**/*.html",
"/**/*.js",
"/**/*.css"
);
}
}
3.这样&#xff0c;访问这个url&#xff0c;才能不被拦截器拦截&#xff1a;
http://localhost:8080/survey-project/download/special
1.配置拦截器时&#xff0c;如果excludePathPatterns没有生效&#xff0c;可能是url配置有问题。
2.可以检查下application.yml的context-path&#xff0c;或者其它类似的地方&#xff0c;配置拦截器的url不应该包含这些路径&#xff0c;只要从Controller的路径开始配置即可。
使用response对象的sendRedirect()方法将用户的请求重定向到指定路径&#xff0c;这个路径由request对象的getContextPath()方法获取&#xff0c;再加上字符串 “/” 组成。getContextPath()方法返回当前web应用程序的上下文路径&#xff0c;此处加的字符串路径也是从Controller的路径开始配置即可
//重定向登录页面
response.sendRedirect(request.getContextPath() &#43; "/user/login");
会被重定向到
http://127.0.0.1:8080/user/login
//重定向登录页面
response.sendRedirect(request.getContextPath() &#43; "/login");
http://127.0.0.1:8080/login
"/user/login"也是从Controller的路径开始配置
参考博文&#xff1a;SpringBoot实现登录拦截器&#xff08;实战版&#xff09;