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>
欢迎找我,一起进步(我还是个菜鸟......)

浙公网安备 33010602011771号