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.*;
    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 (requestURI.startsWith(url) || "/".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 {
            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();
        }
    }

     

  • 实现  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>

 

欢迎找我,一起进步(我还是个菜鸟......)

posted @ 2020-12-21 17:18  LostHeart  阅读(327)  评论(0)    收藏  举报