[TOC]
#### @Inner 注解原理和使用

#### ① 服务交互三种情况
* 1. 外部从 Gateway 访问,需要鉴权(eg.CURD 操作)。这种是最常使用的,用户登录后正常访问接口,**不需要添加 @Inner**
* 2. 如上图 ④⑤ ,外部从 Gateway 访问不需要鉴权(eg.获取短信验证码接口)。 **只需要再目标接口增加 @Inner(false)**

* 3. 如上图 ①②③,外部从 Gateway 访问的请求没有携带 token(eg.登录接口), auth 需要调用 upms 的接口获取用户信息

#### ② Inner 的原理
##### 统一的 ignore-url 处理
首先我们来看看这个注解的代码
~~~
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Inner {
/**
* 是否AOP统一处理(可以理解为是否仅允许Feign之间调用)
*
* @return false, true
*/
boolean value() default true;
/**
* 需要特殊判空的字段(预留)
*
* @return {}
*/
String[] field() default {};
}
~~~
首先,在我们项目加载阶段,我们获取有 Inner 注解的类和方法,然后获取我们配置的 uri,经过正则替换后面的可变参数为\*,然后将此 uri 加入到 ignore-url 中。此时我们就能达到所有 Inner 配置的方法/类上的接口地址,都统一在项目加载阶段自动帮我们加到 ignore-url 中,不需要我们手动配置,免去了很多开发工作,同时也能避免我们忘记配置,而浪费开发时间。核心代码如下:
~~~
@Slf4j
@Configuration
@ConditionalOnExpression("!'${security.oauth2.client.ignore-urls}'.isEmpty()")
@ConfigurationProperties(prefix = "security.oauth2.client")
public class PermitAllUrlProperties implements InitializingBean {
private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");
@Autowired
private WebApplicationContext applicationContext;
@Getter
@Setter
private List<String> ignoreUrls = new ArrayList<>();
@Override
public void afterPropertiesSet() {
RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
map.keySet().forEach(info -> {
HandlerMethod handlerMethod = map.get(info);
// 获取方法上边的注解 替代path variable 为 *
Inner method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Inner.class);
Optional.ofNullable(method)
.ifPresent(inner -> info.getPatternsCondition().getPatterns()
.forEach(url -> ignoreUrls.add(ReUtil.replaceAll(url, PATTERN, StringPool.ASTERISK))));
// 获取类上边的注解, 替代path variable 为 *
Inner controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Inner.class);
Optional.ofNullable(controller)
.ifPresent(inner -> info.getPatternsCondition().getPatterns()
.forEach(url -> ignoreUrls.add(ReUtil.replaceAll(url, PATTERN, StringPool.ASTERISK))));
});
}
}
~~~
##### 统一的安全性处理
那上面讲到的,如果我们不希望这个 url 可以直接被外网调用,仅能在 Feign 服务中调用,改如何统一处理呢?
我们使用一个 Spring-AOP,在对所有 Inner 注解的方法做一个环绕增强的切点,进行统一的处理。在上面我们提到的 Inner 的 value 参数,当该参数为 true 时,我们对方法的入参进行判断,仅当符合我们定制的入参规则时( 这里是用的`@RequestHeader(SecurityConstants.FROM)` 与`SecurityConstants.FROM_IN`做比较),我们对它进行放行,不符合时,抛出异常;当 value 为 false 时,咱不做任何处理,此时 Inner 仅起到了一个 ignore-url 的作用。
~~~
@Slf4j
@Aspect
@Component
@AllArgsConstructor
public class ScaSecurityInnerAspect {
private final HttpServletRequest request;
@SneakyThrows
@Around("@annotation(inner)")
public Object around(ProceedingJoinPoint point, Inner inner) {
String header = request.getHeader(SecurityConstants.FROM);
if (inner.value() && !StrUtil.equals(SecurityConstants.FROM_IN, header)) {
log.warn("访问接口 {} 没有权限", point.getSignature().getName());
throw new AccessDeniedException("Access is denied");
}
return point.proceed();
}
}
~~~
通过这两步呢,我们首先是在加载时通过找到 Inner 注解,将相应的 uri 加入到 ignore-url 中,达到自动化配置的目的;之后我们又使用切面对 Inner 的方法进行环绕处理,达到安全控制。对比之前的处理方式,现在我们使用一个`@Inner`注解,就能很快的满足上面说的两种场景,大大节省了我们的开发时间。
#### ③ 合理的使用@Inner 注解
如下接口通过 Inner 接口不鉴权,Restful pathVariable 风格的请求
~~~
@Inner
@GetMapping("/info/{username}")
~~~
根据上文原理说明 ,会给 spring security 添加一条 `/info/*` 的 忽略拦截规则,这样会导致只要是满足此规则的路径都会进行 token 校验,获取不到用户信息。 (这也就是为啥 SecurityUtils.getUser 为空的原因)
- 平台介绍
- 平台总览
- 平台架构
- 架构介绍
- 架构选择说明
- 环境部署
- 微服务部署图
- 环境说明
- 准备工作
- Redis安装
- Minio安装
- 数据库安装
- MySQL数据库
- Oracle数据库
- 达梦数据库
- PostgresSQL数据库
- DB2数据库
- GaussDB
- OceanBase
- JDK安装
- 后端部署
- 快速部署
- 单体部署
- 初始化数据库
- 修改配置
- 启动单体服务
- 微服务部署
- 初始化微服务数据库
- 配置本地host(可选)
- 启动nacos并修改项目配置
- 服务启动顺序
- 进阶部署
- nacos集群部署
- gateway集群部署
- 中间件部署
- 服务War包容器部署
- Websphere环境部署
- 宝兰德部署
- docker部署
- Docker镜像仓库Harbor搭建及配置
- 前端部署
- 快速上手
- 平台代码下载
- 单体架构
- 源码运行步骤
- 获取代码
- 代码结构说明
- 集成服务删减(可选)
- 配置文件修改
- 启动项目
- 开发规范
- 实现新增CRUD
- 前端启动
- 微服务架构
- UPMS源码启动
- 新增微服务完成CRUD
- 初始化项目
- CRUD代码生成
- 配置动态路由
- Nginx配置代理
- 分配角色权限
- 系统账户密码说明
- 微服务使用
- Nacos注册配置中心
- Nacos使用
- UPMS用户权限中心
- upms功能说明
- AUTH认证中心
- session、token、jwt、oauth2
- Gateway网关服务
- 路由规则
- 路由配置
- 网关限流
- API接口管理
- 网关配置跨域
- BPM工作流服务
- monitor监控服务
- SpringBootAdmin查看系统日志
- SpringBootAdmin动态日志级别
- sentinel熔断限流
- 通讯前置服务
- 概述
- 接入项目
- 接出项目
- 接入服务配置
- 接入服务节点
- 通讯配置
- TCP
- JMS
- MQ
- TongLinkQ
- HTTP
- HTTPS
- WebService
- 通讯接出扩展
- 交易码配置
- 接入流程配置
- 接入通讯
- 解包报文头
- 解包报文
- 组包报文
- 发送报文
- Rest服务调用
- RestIP调用
- 接出的异步应答
- 设置返回属性
- YFA调用
- 插件调用
- 报文配置
- 报文配置节点
- 定长报文
- 分隔符报文
- XML报文
- CNAPS1报文
- CNAPS2报文
- HTTP报文
- JSON报文
- ISO8583报文
- 插件开发说明
- 常见问题与解答
- 接出调用示例
- 代码生成服务
- 定时任务服务
- 消息中心服务
- 消息基础数据
- 消息发送
- API接口发送消息
- 大屏设计
- 报表设计
- 报表引擎部署安装
- 报表使用介绍
- Swagger接口文档
- Swagger接口调试
- 单点登录使用
- 数据埋点统计
- 事件管理
- 统计定义
- 动态展示
- 前端数据采集
- 后端部分
- 核心库字段说明
- 多租户使用
- 分布式文件系统
- 平台权限控制机制
- 功能菜单权限
- 后端使用
- 前端使用
- 数据权限
- 平台安全控制策略
- 账户安全策略
- 账号安全策略
- 弱密码检测
- 密码检测-后端使用
- 密码检测-前端使用
- 密码检测-网关使用
- 数据安全
- 数据权限和审计日志
- 字段存储加解密
- 数据脱敏
- 日志脱敏
- 配置文件加密
- 请求数据加密
- 平台加解密组件
- 加解密-后端使用
- 加解密-前端使用
- 平台登录流程
- 登录访问链路
- 登录token生成、传递及校验
- token、终端、令牌说明
- 登录流程及token的生成过程
- 请求过程token校验过程
- feign调用及token传递
- 登录密码加解密
- 获取当前用户
- postman调用登录接口
- 接口对外暴露
- 接口忽略鉴权对外暴露
- Inner注解使用及原理
- 登录验证码
- 验证码配置及开关
- 验证码类型选择
- social登录带验证码
- 事务管理
- 动态数据源
- 灰度发布
- 系统日志
- logback配置
- 日志切面注解SysLog
- 审计日志
- 日志内容脱敏
- 日志添加全局流水号跟踪分布式调用
- 系统参数
- 系统缓存
- redis缓存扩展
- 缓存方案切换
- 配置数据动态刷新
- 整合邮件发送
- 整合手机短信发送
- 微服务本地调试
- postman调试接口
- 集成swagger接口文档
- 使用feign接口调用
- 集成auth授权认证
- 集成单元测试
- 上传下载
- 导入导出
- excel组件模块
- websocket模块
- xss安全过滤
- 分布式发号器
- PDF文档转换及文档预览
- 前端部分
- 开发环境搭建
- vue前端
- 配置NPM镜像
- vue目录结构说明
- 前端文档说明
- 前端图标引入
- 在线图标
- 离线图标
- 前端字典功能
- 前端登录过程详解
- 前端加解密方法使用
- 验证码选择
- 三方登录带验证码
- 自定义返回码提示
- bootstrap前端开发文档
- 前端国际化
- bootstrap国际化
- vue前端国际化
- 前端添加水印
- vue
- Bootstrap
- Bootstrap-ie8
- 扩展部分
- 登录方式扩展
- 社交账户登录
- 免密登录
- 自定义登录方式
- skywalking链路监控
- Prometheus+grafana监控使用
- ELK日志采集使用
- 分布式事务
- 分库分表
- 常见问题
- 如何配置跨域访问
- 如果导出swagger文档
- log4j漏洞处理
- window启动异常
- 业务模块访问404
- 文件上传大小限制
- 如何获取当前登录用户信息
- sca-auth 报错 Cannot deserialize
- 快开平台1.0迁移2.0指导手册
- 小程序开发框架比较
- Spring Cloud应用改造SOFAStack
- 技术问题解决流程
- 平台使用发布制度
- 平台版本更新日志
- 文档修订记录