springboot專案: 將整合springsecurity修改成自定義aop許可權認證
前因: 前期因為時間趕, 網上覆制了一份springsecurity的整合內容,雖說能用,但出現問題修改起來挺麻煩的,後面看碼雲上一篇大佬的開原始碼覺得在小專案中用起來比使用springsecurity好用
技術: 使用 aop + 攔截器 自定義實現許可權認證(很實用)
注: 純因本人對springsecurity瞭解不深所以才進行改變的
出現問題1: 改完後另一個專案使用 Fegin 遠端呼叫時出現了異常資訊:status 401 reading DRecordContentFeignService#findUnShiftAndAid(Integer) 貼完程式碼後解釋
開始前準備工作: 將springsecurity許可權認證,token校驗等程式碼註釋
- 在springboot中引入依賴(忘了!!! 好像不需要引入依賴,跳過,我在專案中都是直接使用的,主要是這個註解(用於角色許可權啥的)--> 當時沒找到這個依賴包):
import org.aspectj.lang.annotation.Aspect;
- 建立一個攔截器實現介面org.springframework.web.servlet.HandlerInterceptor 重寫preHandle 方法
注: 攔截路徑在此處設定,類似springsecurity中的config配置裡需要認證的路徑許可權啥的
package com.ss.ggw.core.interceptor;
-
實現org.springframework.web.servlet.config.annotation.WebMvcConfigurer 將其註冊到攔截器中
package com.ss.ggw.core.interceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @author xiaomu * @version 1.0 * @date 2020-12-18 15:04 * @Description: 自定義攔截器 */ @Configuration public class CustomInterceptor implements WebMvcConfigurer { @Autowired private AppContextInterceptor appContextInterceptor;
// 新增攔截器 @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(appContextInterceptor); } }以上未登入前的操作, 簡單實用(僅為登入前操作,許可權檢驗等測試完此處在進行新增)
以上搞定後,進行資料測試看看是否可用
結果o(╥﹏╥)o 剛巧另一個專案中實用 springcloud的 Fegin元件呼叫了專案中的一個藉口,一連接出現bug
- 在修改結構的這邊中出現異常:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalStateException: getWriter() has already been called for this response at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE] at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:866) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE] at javax.servlet.http.HttpServlet.service(HttpServlet.java:635) ~[tomcat-embed-core-8.5.29.jar:8.5.29] at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE] at javax.servlet.http.HttpServlet.service(HttpServlet.java:742) ~[tomcat-embed-core-8.5.29.jar:8.5.29] ........... Caused by: java.lang.IllegalStateException: getWriter() has already been called for this response at org.apache.catalina.connector.Response.getOutputStream(Response.java:590) ~[tomcat-embed-core-8.5.29.jar:8.5.29] .....
這個問題是因為response兩種流一起調用出現的問題,網上一堆教程
private void returnNoLogin(HttpServletRequest request, HttpServletResponse response) throws IOException { Writer out= new BufferedWriter(new OutputStreamWriter(response.getOutputStream())); response.setCharacterEncoding("utf-8"); // 設定返回編碼 response.setContentType("application/json; charset=utf-8"); out.write("{\"code\":" + 410 + ", \"msg\":\"未登入!\"}"); out.flush(); out.close(); }
-- 流轉化就好了
- 好吧這邊看懂了也操作解決不了問題,就寫出內容異常啥的,看網頁和後臺顯示內容
2020-12-21 16:24:19.721 WARN 3804 --- [nio-8086-exec-8] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [feign.FeignException$Unauthorized: status 401 reading AreaFeignService#findAll()]
出現遠端呼叫異常, 此刻 我一臉懵逼 ???
抽根菸思考下人生, 畢竟這麼簡單的程式碼也能出現這種異常我也是飄了 !!!
在剛寫完的攔截器中打了個斷點瞅了一眼, emmmmmmmm....
我明明呼叫的是介面
到這邊就變成了 /error o((⊙﹏⊙))o
由於是改springsecurity的許可權認證,我在想是不是被springsecurity給攔截掉了, 在程式碼中仔細排查了一遍, 關於springsecurity的程式碼我都註釋的乾乾淨淨的
啊, 這 .............
排除了程式碼,那剩下的也只有依賴了, springsecurity 好像有個預設的攔截, 只要添加了
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
這個依賴預設是有個攔截器的,將其註釋起來...重新啟動後..........................好了,我勒個擦
路徑也對了
開始許可權控制操作了:
- 自定義註解@RequestAuthorized 用來角色需要認證操作的(注: 這玩意自定義的名字見名知義就更好了)
package com.ss.ggw.core.security.authorize; import java.lang.annotation.*; /** * 請求認證註解 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) // 保留在什麼時間 執行時 @Documented // 保留在doc生成的文件中 https://www.cnblogs.com/uoar/p/8036642.html public @interface RequestAuthorize { }
使用aop來進行對註解的控制
package com.ss.ggw.core.security.authorize; import com.ss.ggw.common.info.GlobalUserInfo; import com.ss.ggw.common.utils.HttpUtils; import com.ss.ggw.mvc.pojo.Permission; import com.ss.ggw.mvc.pojo.UserInfo; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.lang.reflect.Method; import java.util.List; /** * @author xiaomu * @version 1.0 * @date 2020-12-14 11:03 * @Description: 請求認證切面 */ @Aspect @Order(1) // 排序列位置 @Component // spring掃描到 public class RequestAuthonrizeAspect { @Autowired private GlobalUserInfo globalUserInfo; /** * 定義攔截規則: 放在 com.ss.ggw.mvc.controller 下面的 */ @Around("execution(* com.ss.ggw.mvc..*(..))" + " and @annotation(com.ss.ggw.core.security.authorize.RequestAuthorize)") public Object method(ProceedingJoinPoint pjp) throws Throwable { MethodSignature signature = (MethodSignature) pjp.getSignature(); Method method = signature.getMethod(); // 獲取被攔截的方法 RequestAuthorize limit = method.getAnnotation(RequestAuthorize.class); if (limit == null) { return pjp.proceed(); // 如果方法上沒有這個註解,跳過 } // ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); // 簡寫 HttpServletRequest request = HttpUtils.getRequest(); HttpServletResponse response = HttpUtils.getResponse(); // 判定是否登入 UserInfo userInfo = globalUserInfo.getUserInfo(); if (userInfo == null) { try { return returnAuthorizeRequest(request, response); } catch (Exception e) { } } // 獲取角色別名 此處只有 ROLE_ADMIN 才行 String alias = userInfo.getRole().getAlias(); if (StringUtils.isBlank(alias)) { if (!"ROLE_ADMIN".equals(alias)) { returnAuthorizeRequest(request, response); } } // // 判定許可權 // List<Permission> permissions = userInfo.getPermissions(); // if (permissions == null || permissions.size() == 0) { // try { // return returnAuthorizeRequest(request, response); // } catch (Exception e) { // e.printStackTrace(); // } // } else { // // // 判斷是否有對應許可權 // } return pjp.proceed(); } private Object returnAuthorizeRequest(HttpServletRequest request, HttpServletResponse response) throws IOException { Writer out = new BufferedWriter(new OutputStreamWriter(response.getOutputStream())); response.setCharacterEncoding("utf-8"); response.setContentType("application/json; charset=utf-8"); out.write("{\"code\":" + "410" + ", \"msg\":\"沒有許可權\"}"); out.flush(); out.close(); return null; } }
以上, 簡單許可權認證或者角色認證就好了, 其他諸如介面流量控制原理可與攔截器同理, 使用redis進行資料統計即可(懶得寫了)
貼一下maven依賴記錄下
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.ss</groupId> <artifactId>ggw</artifactId> <version>1.0-SNAPSHOT</version> <name>ggw</name> <description>guanggaow project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR5</spring-cloud.version> </properties> <dependencies> <!-- jdbc連線 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jdbc</artifactId> </dependency> <!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- security --> <!-- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>--> <!--web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- aop切面 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!-- 熱部署--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <!-- MySQL連線 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency> <!-- 測試 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- 加入swagger --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <!-- 匯入其他輔助依賴 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency> <dependency> <groupId>commons-httpclient</groupId> <artifactId>commons-httpclient</artifactId> <version>3.1</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.35</version> </dependency> <!-- nacos 配置管理 服務發現 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- sentinel 配置流量監控啥的 --> <!-- 閘道器流控實現原理: 當通過 GatewayRuleManager 載入閘道器流控規則(GatewayFlowRule)時,無論是否針對請求屬性進行限流, Sentinel 底層都會將閘道器流控規則轉化為熱點引數規則(ParamFlowRule),儲存在 GatewayRuleManager 中,與正常的熱點引數規則相隔離。轉換時 Sentinel 會根據請求屬性配置,為閘道器流控規則設定引數索引(idx),並同步到生成的熱點引數規則中。 外部請求進入 API Gateway 時會經過 Sentinel 實現的 filter,其中會依次進行 路由/API 分組匹配、請求屬性解析和引數組裝。 Sentinel 會根據配置的閘道器流控規則來解析請求屬性,並依照引數索引順序組裝引數陣列, 最終傳入 SphU.entry(res, args) 中。Sentinel API Gateway Adapter Common 模組向 Slot Chain 中添加了一個 GatewayFlowSlot, 專門用來做閘道器規則的檢查。GatewayFlowSlot 會從 GatewayRuleManager 中提取生成的熱點引數規則, 根據傳入的引數依次進行規則檢查。若某條規則不針對請求屬性,則會在引數最後一個位置置入預設的常量,達到普通流控的效果。 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> </dependencies> <dependencyManagement> <!-- 鎖定springcloud依賴版本 --> <dependencies> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.1.0.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <!-- 依賴預設匯入為jar包, 使用<type>標籤的pom表示匯入的為一個父模組,加上<scope>表示匯入全部的jar包, 實現多繼承依賴--> <!-- 匯入spring-boot依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.1.3.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <!-- 匯入spring-cloud依賴 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.RELEASE </version> <!-- https://blog.csdn.net/j3t9z7h/article/details/86662828 --> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <!-- 打包使用外掛 --> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.1</version> <configuration> <skipTests>true</skipTests> </configuration> </plugin> </plugins> </build> </project>
歡迎找我,一起進步(我還是個菜鳥......)