基於註解的使用者許可權攔截Spring HandlerInterceptor
Spring Boot (v2.0.5.RELEASE)
- 程式中有些資源(介面)是需要使用者登入才能夠使用的,或者是具有某種角色的使用者(比如普通登入使用者,或者系統管理員等)才能使用,本篇文章先為大家講解如何控制使用某介面要求使用者必須登入。
- 實現的思路是
@LoginUser @LoginUser @LoginUser
- 定義標註註解
@LoginUser
package com.futao.springmvcdemo.annotation; import com.futao.springmvcdemo.model.enums.Role; import java.lang.annotation.*; /** * @author futao * Created on 2018/9/19-14:39. * 登陸使用者,使用者角色 */ @Target(value = { ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface LoginUser { /** * 要求的使用者角色 * * @return */ Role role() default Role.Normal; }
2。 定義攔截器 LoginUserInterceptor
package com.futao.springmvcdemo.annotation.impl; import com.alibaba.fastjson.JSON; import com.futao.springmvcdemo.annotation.LoginUser; import com.futao.springmvcdemo.model.entity.constvar.ErrorMessage; import com.futao.springmvcdemo.model.system.RestResult; import com.futao.springmvcdemo.model.system.SystemConfig; import com.futao.springmvcdemo.utils.ThreadLocalUtils; import org.apache.commons.lang3.ObjectUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; /** * @author futao * Created on 2018/9/19-14:44. * 對請求標記了LoginUser的方法進行攔截 */ @Component public class LoginUserInterceptor extends HandlerInterceptorAdapter { private static final Logger logger = LoggerFactory.getLogger(LoginUserInterceptor.class); @Resource private ThreadLocalUtils<String> threadLocalUtils; /** * 在請求到達Controller之前進行攔截並處理 * * @param request * @param response * @param handler * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { //註解在方法上 LoginUser loginUserAnnotation = ((HandlerMethod) handler).getMethodAnnotation(LoginUser.class); //註解在類上 LoginUser classLoginUserAnnotation = ((HandlerMethod) handler).getMethod().getDeclaringClass().getAnnotation(LoginUser.class); if (ObjectUtils.anyNotNull(loginUserAnnotation, classLoginUserAnnotation)) { HttpSession session = request.getSession(false); //session不為空 if (ObjectUtils.allNotNull(session)) { String loginUser = (String) session.getAttribute(SystemConfig.LOGIN_USER_SESSION_KEY); if (ObjectUtils.allNotNull(loginUser)) { System.out.println("當前登陸使用者為:" + loginUser); //將當前使用者的資訊存入threadLocal中 threadLocalUtils.set(loginUser); } else { System.out.println("使用者不存在"); return false; } } else {//session為空,使用者未登入 RestResult restResult = new RestResult(false, "-1", ErrorMessage.NOT_LOGIN, ErrorMessage.NOT_LOGIN.substring(6)); response.getWriter().append(JSON.toJSONString(restResult)); return false; } } } return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //釋放threadLocal資源 threadLocalUtils.remove(); } }
- 註冊攔截器
package com.futao.springmvcdemo.annotation; import com.futao.springmvcdemo.annotation.impl.LoginUserInterceptor; import com.futao.springmvcdemo.annotation.impl.RequestLogInterceptor; import com.futao.springmvcdemo.annotation.impl.SignInterceptor; import org.springframework.boot.SpringBootConfiguration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import javax.annotation.Resource; /** * @author futao * Created on 2018/9/18-15:15. */ @SpringBootConfiguration public class WebMvcConfiguration implements WebMvcConfigurer { @Resource private SignInterceptor signInterceptor; @Resource private LoginUserInterceptor loginUserInterceptor; @Resource private RequestLogInterceptor requestLogInterceptor; /** * addInterceptor()的順序需要嚴格按照程式的執行的順序 * * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(requestLogInterceptor).addPathPatterns("/**"); registry.addInterceptor(loginUserInterceptor).addPathPatterns("/**"); //"/**"和"/*"是有區別的 registry.addInterceptor(signInterceptor).addPathPatterns("/**"); } }
- 測試(可分別將註解打在類上和方法上進行測試)
package com.futao.springmvcdemo.controller; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.futao.springmvcdemo.annotation.LoginUser; import com.futao.springmvcdemo.model.entity.User; import com.futao.springmvcdemo.model.system.SystemConfig; import com.futao.springmvcdemo.service.UserService; import org.apache.commons.lang3.ObjectUtils; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.util.List; import java.util.UUID; /** * @author futao * Created on 2018/9/19-15:05. */ @RequestMapping(path = "User", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) @RestController public class UserController { @Resource private UserService userService; /** * 獲取當前的登陸的使用者資訊,其實是從threadLocal中獲取 * * @return */ @LoginUser @GetMapping(path = "my") public JSONObject my() { JSONObject jsonObject = new JSONObject(); jsonObject.put("當前的登陸的使用者是:", userService.currentUser()); return jsonObject; } /** * 模擬登陸介面 * * @param mobile * @param request * @return */ @PostMapping(path = "login") public JSONObject login( @RequestParam("mobile") String mobile, HttpServletRequest request ) { HttpSession session = request.getSession(); session.setAttribute(SystemConfig.LOGIN_USER_SESSION_KEY, String.valueOf(UUID.randomUUID())); session.setMaxInactiveInterval(SystemConfig.SESSION_INVALIDATE_SECOND); return new JSONObject(); } }
-
測試
4.1 未登入情況下呼叫標記了
@LoginUser
的獲取當前登陸使用者資訊介面未登入
4.2 登入
登入操作
4.3 登入之後呼叫呼叫標記了
@LoginUser
的獲取當前登陸使用者資訊介面登陸之後
稍微解釋一下上面登陸和獲取使用者資訊的邏輯:
使用者請求登陸之後,會為該使用者在系統中生成一個 HttpSession
,同時在系統中有一個 Map
來存放所有的 session
資訊,該 Map
的 key
為一個隨機字串, value
為 session
物件在系統中的堆地址,在登陸請求完成之後,系統會將該 sesion
的 key
值以 cookie
(JSESSIONID)的形式寫回瀏覽器。

設定cookie
使用者下次登陸的時候,請求中會自動帶上該 cookie
,所以我們在標記了需要登陸的 @LoginUser
註解的請求到達處理邏輯之前進行攔截,就是從 cookie
中(JSESSIONID)取出 session
的 key
值,如果沒有該 cookie
,則代表使用者沒有登陸,如果有該 cookie
,再在存放 cookie
的 map
中取,如果沒有取到,則代表使用者的 session
已經過期了,需要重新登陸,或者 cookie
是偽造的。
session
之後,我們去
Map
中獲取對應的值,一般是使用者的
id
,在通過這個使用者
id
,可以去資料庫查該使用者的資訊,查到使用者的資訊之後將使用者資訊放入
threadLocal
中,然後就可以在任何地方
get()
到當前登陸的使用者資訊了,非常方便。
使用上面的基於註解的攔截器可以實現很多功能,比如動態的第三方介面驗籤,和系統日誌記錄(不需要註解)等

日誌系統