目錄
一、今日目標?
二、SpringBoot后端實現
2.1 新增UserLoginParam
2.2 修改UserController
2.3 UserServiceImpl代碼
2.4 創建用戶上下文工具類
2.5?通過token校驗用戶(重要)
2.6 創建WebMvcConfig
2.7 用戶權限校驗攔截器
一、今日目標
上篇文章鏈接:【wiki知識庫】08.添加用戶登錄功能–前端Vue部分修改-CSDN博客
這篇文章主要是實現一下用戶登錄功能的后端部分,登錄功能需要使用redis,不懂redis可以看我之前的一篇文章。
Redis文章鏈接:【Spring】SpringBoot整合Redis,用Redis實現限流(附Redis解壓包)_springboot 限流 redis-CSDN博客
那么為什么要用到Redis呢?
這個問題關系到整個系統的用戶校驗,當我們登錄成功的時候,后端會生成一個用于用戶校驗的token值,然后把這個值傳給前端,每次用戶請求后端的時候都要帶上這個token值,這個token的值當中記錄了當前登錄的用戶是誰,還有過期時間等信息,這樣子就可以防止那些沒有登陸的用戶去直接訪問我們的后端調用接口。所以這個token還是需要妥善保管的,一旦token丟失別人就可能用你的token去發送請求,修改你的數據。
二、SpringBoot后端實現
2.1 新增UserLoginParam
這里也做了校驗,其實這個事情完全可以放到前端實現,但是也要考慮到有直接調用接口的情況,這時也要給出錯誤提示。
@Data public class UserLoginParam {@NotEmpty(message = "【用戶名】不能為空")private String loginName;@NotEmpty(message = "【密碼】不能為空")@Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,32}$", message = "【密碼】規則不正確")private String password;}
2.2 修改UserController
直接上代碼吧。這里拿到了用戶的賬號和用戶的密碼,然后判斷加密后的密碼和數據庫中取出來的用戶密碼是否相同,如果相同那么就可以登陸。登陸后通過工具類生成一個不會重復的Long類型的值作為該用戶的token,然后以token為key,登錄用戶創建的對象作為值,保存到redis當中,以便于后續用戶訪問接口時,通過用戶token來判斷是哪個用戶訪問接口。
@PostMapping("/login")public CommonResp login(@Valid @RequestBody UserLoginParam req) {req.setPassword(DigestUtils.md5DigestAsHex(req.getPassword().getBytes()));System.out.println(req);UserLoginVo userLoginResp = userService.login(req);Long token = snowFlake.nextId();userLoginResp.setToken(token.toString());redisTemplate.opsForValue().set(token.toString(), JSONObject.toJSONString(userLoginResp), 3600 * 24, TimeUnit.SECONDS);return new CommonResp(true,"登錄成功",userLoginResp);}@GetMapping("/logout/{token}")public CommonResp logout(@PathVariable String token) {boolean res = redisTemplate.delete(token);String message = Boolean.TRUE.equals(res) ? "登出成功":"登出失敗";return new CommonResp(true,message,null);}
2.3 UserServiceImpl代碼
這個代碼沒有什么好說的,就是查找一次數據庫進行賬號密碼的匹配。
public UserLoginVo login(UserLoginParam req) {User userDb = selectByLoginName(req.getLoginName());if (ObjectUtils.isEmpty(userDb)) {// 用戶名不存在throw new RuntimeException("用戶名不存在");} else {if (userDb.getPassword().equals(req.getPassword())) {// 登錄成功UserLoginVo userLoginResp = CopyUtil.copy(userDb, UserLoginVo.class);return userLoginResp;} else {// 密碼不對throw new RuntimeException("密碼錯誤");}}}
2.4 創建用戶上下文工具類
這個工具類用戶用戶登錄后保存當前用戶的上下文。
public class LoginUserContext implements Serializable {private static ThreadLocal<UserLoginVo> user = new ThreadLocal<>();public static UserLoginVo getUser() {return user.get();}public static void setUser(UserLoginVo user) {LoginUserContext.user.set(user);}}
2.5通過token校驗用戶(重要)
校驗用戶token需要使用到攔截器或者過濾器,這里我使用攔截器進行用戶token的校驗。整體的校驗流程如下
以下就是登錄攔截器的代碼,
/*** 攔截器:Spring框架特有的,常用于登錄校驗,權限校驗,請求日志打印*/ @Component public class LoginInterceptor implements HandlerInterceptor {private static final Logger LOG = LoggerFactory.getLogger(LoginInterceptor.class);@Resourceprivate RedisTemplate redisTemplate;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// OPTIONS請求不做校驗,// 前后端分離的架構, 前端會發一個OPTIONS請求先做預檢, 對預檢請求不做校驗if (request.getMethod().toUpperCase().equals("OPTIONS")) {return true;}String path = request.getRequestURL().toString();LOG.info("接口登錄攔截:,path:{}", path);//獲取header的token參數String token = request.getHeader("token");LOG.info("登錄校驗開始,token:{}", token);if (token == null || token.isEmpty()) {LOG.info("token為空,請求被攔截");response.setStatus(HttpStatus.UNAUTHORIZED.value());return false;}Object object = redisTemplate.opsForValue().get(token);// 證明redis中的用戶信息過期了,需要重新登陸if (object == null) {LOG.warn("token無效,請求被攔截");response.setStatus(HttpStatus.UNAUTHORIZED.value());return false;} else {LOG.info("已登錄:{}", object);LoginUserContext.setUser(JSON.parseObject((String) object, UserLoginVo.class));return true;}}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {} }
接下來要把這個攔截器注冊到配置當中。
2.6 創建WebMvcConfig
在config包下創建該類。這個類當中配置了兩個攔截器,一個是登錄攔截器,另一個是用戶權限校驗攔截器。用戶校驗攔截器下邊再說。登錄攔截器只需要部分接口進行攔截就可以了,畢竟有的接口不需要登陸用戶就可以訪問。
有一點值得注意的是,在這個配置類中配置的攔截器的順序會影響校驗結果,校驗的流程是根據你配置的攔截器的順序從上往下校驗的,如果你把攔截器配置寫反了就會出錯。
addInterceptor
注冊一個攔截器
addPathPatterns
該攔截器需要攔截的路徑
excludePathPatterns
該攔截器不需要攔截的路徑
@Configuration public class SpringMvcConfig implements WebMvcConfigurer {@ResourceLoginInterceptor loginInterceptor;@ResourceActionInterceptor actionInterceptor;public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/test/**","/redis/**","/user/login","/user/logout/**","/category/all","/ebook/list","/doc/all/**","/doc/find-content/**",);registry.addInterceptor(actionInterceptor).addPathPatterns("/*/save","/*/delete/**","/*/reset-password");} }
2.7 用戶權限校驗攔截器
看到下方的代碼你應該知道了用戶上下文的作用,通過用戶上下文拿到用戶的信息來判斷該用戶是否有訪問該接口的權利,我們拒絕非admin用戶外的用戶進行增刪改操作。
但是這種方法有點不太好不知道你們有沒有感覺到,一旦用戶多了之后,如果你想給用戶分配權限,你就要添加很多的用戶在這里。所以一種更好的方式就是RBAC權限校驗,大家可以自己了解一下,也有更好的權限校驗框架SpringSecurity,但是作為一個比較簡單的項目,引入這個框架的學習成本就太大了。
/*** 攔截器:Spring框架特有的,常用于登錄校驗,權限校驗,請求日志打印*/ @Component public class ActionInterceptor implements HandlerInterceptor {private static final Logger LOG = LoggerFactory.getLogger(ActionInterceptor.class);@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {// OPTIONS請求不做校驗,// 前后端分離的架構, 前端會發一個OPTIONS請求先做預檢, 對預檢請求不做校驗if ("OPTIONS".equals(request.getMethod().toUpperCase())) {return true;}UserLoginVo userLoginResp = LoginUserContext.getUser();if ("admin".equals(userLoginResp.getLoginName())) {// admin用戶不攔截return true;}LOG.info("操作被攔截");response.setStatus(HttpStatus.OK.value());CommonResp commonResp = new CommonResp(false,"普通用戶暫不開放增刪改操作",null);response.setContentType("application/json;charset=UTF-8");response.setCharacterEncoding("UTF-8");response.getWriter().print(JSONObject.toJSON(commonResp));return false;}}