SpringBoot實現(xiàn)登錄攔截器超詳細(xì)教程分享
SpringBoot實現(xiàn)登錄攔截器
對于管理系統(tǒng)或其他需要用戶登錄的系統(tǒng),登錄驗證都是必不可少的環(huán)節(jié),在 SpringBoot 開發(fā)的項目中,通過實現(xiàn)攔截器來實現(xiàn)用戶登錄攔截并驗證。
SpringBoot 實現(xiàn)登錄攔截的原理
SpringBoot 通過實現(xiàn)HandlerInterceptor接口實現(xiàn)攔截器,通過實現(xiàn)WebMvcConfigurer接口實現(xiàn)一個配置類,在配置類中注入攔截器,最后再通過 @Configuration 注解注入配置.
1.實現(xiàn)HandlerInterceptor接口
實現(xiàn)HandlerInterceptor接口需要實現(xiàn) 3 個方法:preHandle、postHandle、afterCompletion.
3 個方法各自的功能如下:
public class UserLoginInterceptor implements HandlerInterceptor { /*** * 在請求處理之前進(jìn)行調(diào)用(Controller方法調(diào)用之前) */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("執(zhí)行了攔截器的preHandle方法"); try { HttpSession session = request.getSession(); //統(tǒng)一攔截(查詢當(dāng)前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; //如果設(shè)置為false時,被請求時,攔截器執(zhí)行到此處將不會繼續(xù)操作 //如果設(shè)置為true時,請求將會繼續(xù)執(zhí)行后面的操作 } /*** * 請求處理之后進(jìn)行調(diào)用,但是在視圖被渲染之前(Controller方法調(diào)用之后) */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("執(zhí)行了攔截器的postHandle方法"); } /*** * 整個請求結(jié)束之后被調(diào)用,也就是在DispatchServlet渲染了對應(yīng)的視圖之后執(zhí)行(主要用于進(jìn)行資源清理工作) */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("執(zhí)行了攔截器的afterCompletion方法"); } }
preHandle在 Controller 之前執(zhí)行,因此攔截器的功能主要就是在這個部分實現(xiàn):
1.檢查 session 中是否有user對象存在;
2.如果存在,就返回true,那么 Controller 就會繼續(xù)后面的操作;
3.如果不存在,就會重定向到登錄界面
就是通過這個攔截器,使得 Controller 在執(zhí)行之前,都執(zhí)行一遍preHandle.
2.實現(xiàn)WebMvcConfigurer接口,注冊攔截器
實現(xiàn)WebMvcConfigurer接口來實現(xiàn)一個配置類,將上面實現(xiàn)的攔截器的一個對象注冊到這個配置類中.
@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 注解將配置注入。
3.保持登錄狀態(tài)
只需一次登錄,如果登錄過,下一次再訪問的時候就無需再次進(jìn)行登錄攔截,可以直接訪問網(wǎng)站里面的內(nèi)容了。
在正確登錄之后,就將user保存到session中,再次訪問頁面的時候,登錄攔截器就可以找到這個user對象,就不需要再次攔截到登錄界面了.
UserController
@Slf4j @RestController @RequestMapping("/user") public class UserController { @Resource private UserService userService; /** * 發(fā)送郵箱驗證碼 * @return */ @PostMapping("/sendCode") public BaseResponse<String> sendCode(@RequestBody String email) { // 發(fā)送短信驗證碼并保存驗證碼 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,"請求參數(shù)為空"); } 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,"請求參數(shù)為空"); } 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> { /** * 發(fā)送驗證碼 * @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"; /** * 發(fā)送郵箱驗證碼 * @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.發(fā)送驗證碼 log.debug("發(fā)送郵箱驗證碼成功,驗證碼:{}", 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,"請求參數(shù)為空"); } 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,"驗證碼長度應(yīng)該為6位"); } //密碼和校驗密碼相同 if(!userPassword.equals(checkPassword)){ throw new BusinessException(PARAMS_ERROR); } //賬戶郵箱不能重復(fù) QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("email",email); Long count = userMapper.selectCount(queryWrapper); if (count>0){ throw new BusinessException(PARAMS_ERROR); } //昵稱不能重復(fù) 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.插入數(shù)據(jù) 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.記錄用戶登錄狀態(tài) 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.看起來沒有問題,但是當(dāng)訪問上方url的時候,還是會被攔截器攔截,就很坑。
解決方法
1.通過排查發(fā)現(xiàn),原來,在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
總結(jié)
1.配置攔截器時,如果excludePathPatterns沒有生效,可能是url配置有問題。
2.可以檢查下application.yml的context-path,或者其它類似的地方,配置攔截器的url不應(yīng)該包含這些路徑,只要從Controller的路徑開始配置即可。
擴(kuò)展
使用response對象的sendRedirect()方法將用戶的請求重定向到指定路徑,這個路徑由request對象的getContextPath()方法獲取,再加上字符串 “/” 組成。getContextPath()方法返回當(dāng)前web應(yīng)用程序的上下文路徑,此處加的字符串路徑也是從Controller的路徑開始配置即可
“/user/login”
//重定向登錄頁面 response.sendRedirect(request.getContextPath() + "/user/login");
會被重定向到
http://127.0.0.1:8080/user/login
“/login”
//重定向登錄頁面 response.sendRedirect(request.getContextPath() + "/login");
http://127.0.0.1:8080/login
"/user/login"也是從Controller的路徑開始配置
以上就是SpringBoot實現(xiàn)登錄攔截器超詳細(xì)教程分享的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot登錄攔截器的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java中的for循環(huán)結(jié)構(gòu)及實例
這篇文章主要介紹了Java中的for循環(huán)結(jié)構(gòu)及實例,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01java Spring Boot 配置redis pom文件操作
這篇文章主要介紹了java Spring Boot 配置redis pom文件操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-07-07java求100之內(nèi)的素數(shù)(質(zhì)數(shù))簡單示例
這篇文章主要介紹了java求100之內(nèi)的素數(shù)簡單示例,素數(shù)是一個大于1的自然數(shù),如果除了1和它自身外,不能被其他自然數(shù)整除的數(shù);否則稱為合數(shù)2014-04-04Spring實戰(zhàn)之使用@POSTConstruct和@PreDestroy定制生命周期行為操作示例
這篇文章主要介紹了Spring實戰(zhàn)之使用@POSTConstruct和@PreDestroy定制生命周期行為操作,結(jié)合實例形式詳細(xì)分析了Spring使用@POSTConstruct和@PreDestroy定制生命周期相關(guān)接口定義、配置與功能實現(xiàn)技巧,需要的朋友可以參考下2019-12-12