Spring MVC學習總結(19)——SpringMVC許可權註解攔截
阿新 • • 發佈:2019-02-03
HandlerInterceptor(處理器攔截器)
常見使用場景
日誌記錄: 記錄請求資訊的日誌, 以便進行資訊監控, 資訊統計, 計算PV(page View)等
效能監控:
許可權檢查:
通用行為:
使用自定義註解實現許可權攔截
首先HandlerInterceptor瞭解
在HandlerInterceptor中有三個方法:
public interface HandlerInterceptor {
// 在執行目標方法之前執行
boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler)throws Exception;
// 執行目標方法之後執行
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception;
// 在請求已經返回之後執行
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception;
}
在以上註釋中已經寫明執行順序, 測試及測試結果如下:
public class TestInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandler");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandler");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion");
}
}
結果:
preHandler
postHandler
afterCompletion
如何配置攔截器後面講:
下面來講講這個攔截器是攔截的什麼?
方法攔截器HandlerInterceptor
我們使用SpringMVC都知道SpringMVC是基於方法的請求處理, 和Struts2有明顯區別(我是這麼理解的), 並且Controller是單例模式, 所有請求都是從DispatcherServlet來呼叫請求url對應的方法的(處理器對映器的功能), 那麼它是一個方法攔截器, 如何知道呢?
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(handler.getClass());
return true;
}
執行結果:
class org.springframework.web.method.HandlerMethod
已經看到攔截器如何工作, 並且知道它是方法級別的攔截器, 那麼接下來看看如何配置攔截器, 讓它在伺服器執行期間起作用
實現HandlerInterceptor
實現HandlerInterceptor介面或者繼承HandlerInterceptorAdapter類
HandlerInterceptorAdapter介面卡是對HandlerInterceptor介面做了預設實現, 這種介面卡模式, 是為了方便開發者只去想要複寫方法, 其他方法採取預設措施.
上面的TestInterceptor就是一個Demo, 具體可以按需求在我們想要攔截的位置進行相應的邏輯處理
配置攔截器
從Spring4.x開始, 就支援註解配置具體是使用@Configuration註解標註一個普通類, 使該類成為配置類, 可以在該類中定義Bean, 以及注入一些配置引數等.
首先, 我們要配置攔截器,就需要想SpringMvc的攔截鏈中取注入我們的攔截器, 這樣才能請求進來時去攔截我們想要攔截的請求, 所以, 需要我們新建一個普通類, 使用@Configuration註解標註在類名上, 並且讓該類繼承WebMvcConfigurerAdapter,為什麼不實現WebMvcConfigurer介面呢? 因為方法太多,我們不需要複寫其中所有方法...
下面讓我們新建一個配置類, 來配置我們的攔截器
@Configuration
public class InterceptorConfig extends WebMvcConfigurerAdapter {
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TestInterceptor()).addPathPatterns("/**");
}
}
複寫addInterceptors之後, 當我們的伺服器啟動時, 會自動呼叫這個方法, 引數怎麼傳我們都不用管(目前是java初級階段, 沒空研究Spring深層原理), 我們只要知道這個方法一定會被呼叫就行了.
我們直接new一個自定義攔截器, 註冊到整個攔截鏈中, 並且制定攔截路徑, 這樣當滿足請求url攔截配置時, 我們的自定義攔截器就會執行相應的方法了.
攔截方法是非常靈的, 除了攔截配置的, 也可以攔截非匹配的, 也可以根據正則表示式攔截請求
至此, 我們的攔截器就完成了, 接下來自定義許可權相關注解
自定義許可權註解
定義一個@interface類
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Access {
String[] value() default {};
String[] authorities() default {};
String[] roles() default {};
}
@Target註解是標註這個類它可以標註的位置:
常用的元素型別(ElementType):
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
// TYPE型別可以宣告在類上或列舉上或者是註解上
TYPE,
/** Field declaration (includes enum constants) */
// FIELD宣告在欄位上
FIELD,
/** Method declaration */
// 宣告在方法上
METHOD,
/** Formal parameter declaration */
// 宣告在形參列表中
PARAMETER,
/** Constructor declaration */
// 宣告在構造方法上
CONSTRUCTOR,
/** Local variable declaration */
// 宣告在區域性變數上
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
@Retention註解表示的是本註解(標註這個註解的註解保留時期)
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
// 原始碼時期
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
// 位元組碼時期, 編譯之後
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
// 執行時期, 也就是一直保留, 通常也都用這個
RUNTIME
}
@Documented是否生成文件的標註, 也就是生成介面文件是, 是否生成註解文件
註解說完了, 下面需要到對應的controller的方法中取添加註解, 配置該方法允許的許可權
在方法上配置許可權
@RestController
public class HelloController {
@RequestMapping(value = "/admin", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, method = RequestMethod.GET)
// 配置註解許可權, 允許身份為admin, 或者說允許許可權為admin的人訪問
@Access(authorities = {"admin"})
public String hello() {
return "Hello, admin";
}
}
編寫許可權邏輯
// 自定義一個許可權攔截器, 繼承HandlerInterceptorAdapter類
public class AuthenticationInterceptor extends HandlerInterceptorAdapter {
// 在呼叫方法之前執行攔截
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 將handler強轉為HandlerMethod, 前面已經證實這個handler就是HandlerMethod
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 從方法處理器中獲取出要呼叫的方法
Method method = handlerMethod.getMethod();
// 獲取出方法上的Access註解
Access access = method.getAnnotation(Access.class);
if (access == null) {
// 如果註解為null, 說明不需要攔截, 直接放過
return true;
}
if (access.authorities().length > 0) {
// 如果許可權配置不為空, 則取出配置值
String[] authorities = access.authorities();
Set<String> authSet = new HashSet<>();
for (String authority : authorities) {
// 將許可權加入一個set集合中
authSet.add(authority);
}
// 這裡我為了方便是直接引數傳入許可權, 在實際操作中應該是從引數中獲取使用者Id
// 到資料庫許可權表中查詢使用者擁有的許可權集合, 與set集合中的許可權進行對比完成許可權校驗
String role = request.getParameter("role");
if (StringUtils.isNotBlank(role)) {
if (authSet.contains(role)) {
// 校驗通過返回true, 否則攔截請求
return true;
}
}
}
// 攔截之後應該返回公共結果, 這裡沒做處理
return false;
}
}
常見使用場景
日誌記錄: 記錄請求資訊的日誌, 以便進行資訊監控, 資訊統計, 計算PV(page View)等
效能監控:
許可權檢查:
通用行為:
使用自定義註解實現許可權攔截
首先HandlerInterceptor瞭解
在HandlerInterceptor中有三個方法:
public interface HandlerInterceptor {
// 在執行目標方法之前執行
boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler)throws Exception;
// 執行目標方法之後執行
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception;
// 在請求已經返回之後執行
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception;
}
在以上註釋中已經寫明執行順序, 測試及測試結果如下:
public class TestInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandler");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandler");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion");
}
}
結果:
preHandler
postHandler
afterCompletion
如何配置攔截器後面講:
下面來講講這個攔截器是攔截的什麼?
方法攔截器HandlerInterceptor
我們使用SpringMVC都知道SpringMVC是基於方法的請求處理, 和Struts2有明顯區別(我是這麼理解的), 並且Controller是單例模式, 所有請求都是從DispatcherServlet來呼叫請求url對應的方法的(處理器對映器的功能), 那麼它是一個方法攔截器, 如何知道呢?
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(handler.getClass());
return true;
}
執行結果:
class org.springframework.web.method.HandlerMethod
已經看到攔截器如何工作, 並且知道它是方法級別的攔截器, 那麼接下來看看如何配置攔截器, 讓它在伺服器執行期間起作用
實現HandlerInterceptor
實現HandlerInterceptor介面或者繼承HandlerInterceptorAdapter類
HandlerInterceptorAdapter介面卡是對HandlerInterceptor介面做了預設實現, 這種介面卡模式, 是為了方便開發者只去想要複寫方法, 其他方法採取預設措施.
上面的TestInterceptor就是一個Demo, 具體可以按需求在我們想要攔截的位置進行相應的邏輯處理
配置攔截器
從Spring4.x開始, 就支援註解配置具體是使用@Configuration註解標註一個普通類, 使該類成為配置類, 可以在該類中定義Bean, 以及注入一些配置引數等.
首先, 我們要配置攔截器,就需要想SpringMvc的攔截鏈中取注入我們的攔截器, 這樣才能請求進來時去攔截我們想要攔截的請求, 所以, 需要我們新建一個普通類, 使用@Configuration註解標註在類名上, 並且讓該類繼承WebMvcConfigurerAdapter,為什麼不實現WebMvcConfigurer介面呢? 因為方法太多,我們不需要複寫其中所有方法...
下面讓我們新建一個配置類, 來配置我們的攔截器
@Configuration
public class InterceptorConfig extends WebMvcConfigurerAdapter {
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TestInterceptor()).addPathPatterns("/**");
}
}
複寫addInterceptors之後, 當我們的伺服器啟動時, 會自動呼叫這個方法, 引數怎麼傳我們都不用管(目前是java初級階段, 沒空研究Spring深層原理), 我們只要知道這個方法一定會被呼叫就行了.
我們直接new一個自定義攔截器, 註冊到整個攔截鏈中, 並且制定攔截路徑, 這樣當滿足請求url攔截配置時, 我們的自定義攔截器就會執行相應的方法了.
攔截方法是非常靈的, 除了攔截配置的, 也可以攔截非匹配的, 也可以根據正則表示式攔截請求
至此, 我們的攔截器就完成了, 接下來自定義許可權相關注解
自定義許可權註解
定義一個@interface類
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Access {
String[] value() default {};
String[] authorities() default {};
String[] roles() default {};
}
@Target註解是標註這個類它可以標註的位置:
常用的元素型別(ElementType):
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
// TYPE型別可以宣告在類上或列舉上或者是註解上
TYPE,
/** Field declaration (includes enum constants) */
// FIELD宣告在欄位上
FIELD,
/** Method declaration */
// 宣告在方法上
METHOD,
/** Formal parameter declaration */
// 宣告在形參列表中
PARAMETER,
/** Constructor declaration */
// 宣告在構造方法上
CONSTRUCTOR,
/** Local variable declaration */
// 宣告在區域性變數上
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
@Retention註解表示的是本註解(標註這個註解的註解保留時期)
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
// 原始碼時期
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
// 位元組碼時期, 編譯之後
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
// 執行時期, 也就是一直保留, 通常也都用這個
RUNTIME
}
@Documented是否生成文件的標註, 也就是生成介面文件是, 是否生成註解文件
註解說完了, 下面需要到對應的controller的方法中取添加註解, 配置該方法允許的許可權
在方法上配置許可權
@RestController
public class HelloController {
@RequestMapping(value = "/admin", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, method = RequestMethod.GET)
// 配置註解許可權, 允許身份為admin, 或者說允許許可權為admin的人訪問
@Access(authorities = {"admin"})
public String hello() {
return "Hello, admin";
}
}
編寫許可權邏輯
// 自定義一個許可權攔截器, 繼承HandlerInterceptorAdapter類
public class AuthenticationInterceptor extends HandlerInterceptorAdapter {
// 在呼叫方法之前執行攔截
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 將handler強轉為HandlerMethod, 前面已經證實這個handler就是HandlerMethod
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 從方法處理器中獲取出要呼叫的方法
Method method = handlerMethod.getMethod();
// 獲取出方法上的Access註解
Access access = method.getAnnotation(Access.class);
if (access == null) {
// 如果註解為null, 說明不需要攔截, 直接放過
return true;
}
if (access.authorities().length > 0) {
// 如果許可權配置不為空, 則取出配置值
String[] authorities = access.authorities();
Set<String> authSet = new HashSet<>();
for (String authority : authorities) {
// 將許可權加入一個set集合中
authSet.add(authority);
}
// 這裡我為了方便是直接引數傳入許可權, 在實際操作中應該是從引數中獲取使用者Id
// 到資料庫許可權表中查詢使用者擁有的許可權集合, 與set集合中的許可權進行對比完成許可權校驗
String role = request.getParameter("role");
if (StringUtils.isNotBlank(role)) {
if (authSet.contains(role)) {
// 校驗通過返回true, 否則攔截請求
return true;
}
}
}
// 攔截之後應該返回公共結果, 這裡沒做處理
return false;
}
}