1. 程式人生 > 實用技巧 >springboot專案: 將整合springsecurity修改成自定義aop許可權認證

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;
    
    
    import com.ss.ggw.common.info.GlobalUserInfo; import com.ss.ggw.mvc.pojo.UserInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; /** * @author * @version 1.0 * @date 2020-12-18 15:06 * @Description: 攔截器攔截 */ @Component public class AppContextInterceptor implements HandlerInterceptor { @Autowired private GlobalUserInfo globalUserInfo; static List<String> permitUrls = new ArrayList<>(); // 存放不需要校驗的路徑 static { permitUrls.add("/static/**"); // swagger文件 permitUrls.add("/swagger**/**"); permitUrls.add("/webjars/**"); permitUrls.add("/v2/**"); // 登入介面 permitUrls.add("/login"); // 遠端呼叫介面 permitUrls.add("/area/findAll"); permitUrls.add("/dutyRecordData/findUnShiftAndAid"); } // 允許通過的路徑 private boolean permitAll(String requestURI) { for (String url : permitUrls) { if (url.startsWith(requestURI) || "/".equals(requestURI)) { return true; } } return false; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String requestURI = request.getRequestURI(); if (permitAll(requestURI)) { return true; } // 判斷使用者資訊, (基礎判斷在這裡寫) UserInfo userInfo = globalUserInfo.getUserInfo(); if (userInfo == null) { try { returnNoLogin(request, response); } catch (Exception e) { } return false; } return true; } private void returnNoLogin(HttpServletRequest request, HttpServletResponse response) throws IOException { PrintWriter out = response.getWriter(); response.setCharacterEncoding("utf-8"); // 設定返回編碼 response.setContentType("application/json; charset=utf-8"); out.println("{\"code\":" + 410 + ", \"msg\":\"未登入!\"}"); out.flush(); out.close(); } }

  • 實現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>

歡迎找我,一起進步(我還是個菜鳥......)