项目二技巧一
目录
nginx实现根据域名来访问不同的ip端口
配置Maven私服
快照版和发布版的区别
快照版本(Snapshot)
发布版本(Release)
导入发布版的父工程
理清楚授权规则
一.首先浏览器发送/manager/**路径请求
第二步:构造了TokenGatewayFilter方法,并把myConfig对象和当前过滤器对象作为参数传入
第三步:执行了filter方法并校验是否为放行path
第四步:执行AuthFilter的check方法,检验票据的合法性
第五步:执行AuthFilter的auth方法,进行鉴权
第六步:放行
完整的TokenGateWayFilter类
权限管家的api使用
nginx实现根据域名来访问不同的ip端口
nginx.conf配置文件的内容
在server_name中书写域名的名字,只要访问域名的根路径就会把请求反向代理到
proxy_pass http://127.0.0.1:10880 ip端口上
proxy_set_header Host $host;
:这行配置设置了代理请求的Host
头部,将其设置为原始请求中的Host
头部。
proxy_set_header X-Real-IP $remote_addr;
:这行配置设置了X-Real-IP
头部,将其设置为客户端的IP地址。
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
:这行配置设置了X-Forwarded-For
头部,用于添加客户端的IP地址到现有的X-Forwarded-For
头部中,这对于跟踪客户端的真实IP地址在代理环境中非常有用。
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name git.sl-express.com;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
location / {
client_max_body_size 1024m;
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_pass http://127.0.0.1:10880;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
server {
listen 80;
server_name maven.sl-express.com;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
location / {
client_max_body_size 300m;
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_pass http://127.0.0.1:8081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
这里由于nginx在linux虚拟机上运行,如果我们需要在主机上根据域名来访问虚拟机上的不同ip端口时,需要编写hosts文件的内容
hosts文件所在目录
C:\Windows\System32\drivers\etc
编写以下内容
192.168.150.101 git.sl-express.com
192.168.150.101 maven.sl-express.com
这样一来在主机访问git.sl-express.com域名时,就会访问192.168.150.101这个虚拟机的ip,nginx就会根据nginx.conf配置文件的内容反向代理到虚拟机本地的http://127.0.0.1:10880ip端口
配置Maven私服
Maven 私服(也称为 Maven 仓库管理器)是一个本地的 Maven 仓库,用于存储和管理 Maven 项目依赖的远程仓库中的项目。配置 Maven 私服可以提高构建速度,减少网络延迟,并且可以对依赖进行更好的控制。
配置完成后,当你运行 Maven 构建时,Maven 会首先检查私服中是否有所需的依赖,如果没有,才会从远程仓库下载。
我们可以在私服的 Web 界面中管理依赖,包括上传、下载、删除等操作。
配置私服
settings.xml文件
在<servers>标签中子标签<server>中配置私服的用户名和密码
<id>sl-releases</id>:这个标签定义了服务器的唯一标识符(ID),在 Maven 的其他配置中,可以通过这个 ID 引用这个服务器配置。例如,在 pom.xml 文件中配置部署或发布时,可以使用这个 ID 来指定要部署到的服务器。 <username>deployment</username>:这个标签定义了访问服务器时使用的用户名。 <password>deployment123</password>:这个标签定义了访问服务器时使用的密码。
<servers>
<server>
<id>sl-releases</id>
<username>deployment</username>
<password>deployment123</password>
</server>
<server>
<id>sl-snapshots</id>
<username>deployment</username>
<password>deployment123</password>
</server>
</servers>
mirror镜像,如果私服里没有相关的依赖,就去远程仓库里拉取对应的依赖
<!-- 使用阿里云maven镜像,排除私服资源库 -->
<mirrors>
<mirror>
<id>mirror</id>
<mirrorOf>central,jcenter,!sl-releases,!sl-snapshots</mirrorOf>
<name>mirror</name>
<url>https://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
</mirrors>
<profiles>
标签:
- 这个标签是一个容器,用于包含多个
<profile>
配置。
<profile>
配置:
<id>sl</id>
:这个标签定义了配置文件的唯一标识符(ID),在 Maven 命令中可以通过-P
参数来激活这个 profile。
<properties>
标签:
- 这个标签用于定义一些属性,这些属性可以在 Maven 构建过程中被引用。
<altReleaseDeploymentRepository>
和<altSnapshotDeploymentRepository>
属性:
- 这两个属性分别定义了发布版本和快照版本的部署仓库地址。格式为
<id>::default::<url>
。<altReleaseDeploymentRepository>
:指定了发布版本的部署仓库地址,这里使用的是sl-releases
这个 ID,对应于<servers>
配置中的一个服务器配置。<altSnapshotDeploymentRepository>
:指定了快照版本的部署仓库地址,这里使用的是sl-snapshots
这个 ID,同样对应于<servers>
配置中的一个服务器配置。
<repositories>
标签:
- 这个标签用于定义项目依赖的远程仓库列表。
第一个
<repository>
配置:
<id>sl-releases</id>
:定义了仓库的唯一标识符(ID),与<servers>
配置中的 ID 相对应。<url>
:指定了发布版本的远程仓库地址。<releases>
:指定了对于发布版本的配置,<enabled>true</enabled>
表示启用发布版本的下载。<snapshots>
:指定了对于快照版本的配置,<enabled>false</enabled>
表示禁用快照版本的下载。第二个
<repository>
配置:
<id>sl-snapshots</id>
:定义了仓库的唯一标识符(ID),与<servers>
配置中的 ID 相对应。<url>
:指定了快照版本的远程仓库地址。<releases>
:指定了对于发布版本的配置,<enabled>false</enabled>
表示禁用发布版本的下载。<snapshots>
:指定了对于快照版本的配置,<enabled>true</enabled>
表示启用快照版本的下载。
<?xml version="1.0" encoding="UTF-8"?>
<settings
xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<!-- 本地仓库 -->
<localRepository>F:\maven\repository</localRepository>
<!-- 配置私服中deploy的账号 -->
<servers>
<server>
<id>sl-releases</id>
<username>deployment</username>
<password>deployment123</password>
</server>
<server>
<id>sl-snapshots</id>
<username>deployment</username>
<password>deployment123</password>
</server>
</servers>
<!-- 使用阿里云maven镜像,排除私服资源库 -->
<mirrors>
<mirror>
<id>mirror</id>
<mirrorOf>central,jcenter,!sl-releases,!sl-snapshots</mirrorOf>
<name>mirror</name>
<url>https://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
</mirrors>
<profiles>
<profile>
<id>sl</id>
<!-- 配置项目deploy的地址 -->
<properties>
<altReleaseDeploymentRepository>
sl-releases::default::http://maven.sl-express.com/nexus/content/repositories/releases/
</altReleaseDeploymentRepository>
<altSnapshotDeploymentRepository>
sl-snapshots::default::http://maven.sl-express.com/nexus/content/repositories/snapshots/
</altSnapshotDeploymentRepository>
</properties>
<!-- 配置项目下载依赖的私服地址 -->
<repositories>
<repository>
<id>sl-releases</id>
<url>http://maven.sl-express.com/nexus/content/repositories/releases/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>sl-snapshots</id>
<url>http://maven.sl-express.com/nexus/content/repositories/snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
</profile>
</profiles>
<activeProfiles>
<!-- 激活配置 -->
<activeProfile>sl</activeProfile>
</activeProfiles>
</settings>
配置的内容
http://maven.sl-express.com/nexus/content/repositories/releases
快照版和发布版的区别
在 Maven 中,快照版本(Snapshot)和发布版本(Release)是两种不同类型的版本,它们在版本控制和依赖管理中有着不同的用途和行为:
快照版本(Snapshot)
- 开发阶段:快照版本通常用于开发阶段,它们表示代码还在积极开发中,可能会频繁变化。
- 版本号:快照版本的版本号通常会包含时间戳或构建编号,例如
1.0-SNAPSHOT
或1.0.1-SNAPSHOT
。 - 更新频率:快照版本可能会非常频繁地更新,因为它们通常与持续集成(CI)流程相关联。
- 不稳定性:由于快照版本是开发中的版本,它们可能不稳定,不保证向后兼容性。
- 依赖更新:在 Maven 项目中,如果依赖了一个快照版本,Maven 会定期检查更新,并在发现新版本时自动更新依赖。
发布版本(Release)
- 稳定阶段:发布版本用于表示已经稳定的代码,它们是经过测试和验证的,可以用于生产环境。
- 版本号:发布版本的版本号不包含时间戳或构建编号,例如
1.0
或1.0.1
。 - 更新频率:发布版本的更新频率通常较低,只有在有新功能或修复时才会发布新版本。
- 稳定性和兼容性:发布版本需要保证稳定性和向后兼容性,以确保依赖它们的项目能够正常运行。
- 依赖锁定:在 Maven 项目中,如果依赖了一个发布版本,Maven 会锁定该版本的依赖,不会自动更新到新版本,除非显式地更改了版本号。
导入发布版的父工程
<parent>
<groupId>com.sl-express</groupId>
<artifactId>sl-express-parent</artifactId>
<version>1.3</version>
</parent>
快照版的父工程
理清楚授权规则
以上是授权路线图,我们需要跟着这幅图来理清楚这个项目的授权规则
一.首先浏览器发送/manager/**路径请求
一般根据请求路径的不同来转发到不同的微服务是由gateway网关来实现的,所以我们去gateway网关的application.yml文件中查看路径断言规则
spring:
cloud:
nacos:
username: nacos
password: nacos
server-addr: 192.168.150.101:8848
discovery:
namespace: ecae68ba-7b43-4473-a980-4ddeb6157bdc
ip: 192.168.150.1 #设置本地服务在nacos的地址与虚拟机ip的前三段相同,不然一部分微服务在本地跑,一部分在虚拟机跑
#导致在nacos中的ip地址不同,以至于不同的微服务无法互相访问通
config:
namespace: ecae68ba-7b43-4473-a980-4ddeb6157bdc
gateway:
globalcors:
cors-configurations:
'[/**]':
allowed-origin-patterns: "*"
allowed-headers: "*"
allow-credentials: true
allowed-methods:
- GET
- POST
- DELETE
- PUT
- OPTION
discovery:
locator:
enabled: true #表明gateway开启服务注册和发现的功能,并且spring cloud gateway自动根据服务发现为每一个服务创建了一个router,这个router将以服务名开头的请求路径转发到对应的服务
routes:
- id: sl-express-ms-web-manager
uri: lb://sl-express-ms-web-manager
predicates:
- Path=/manager/**
filters:
- StripPrefix=1
- ManagerToken
- AddRequestHeader=X-Request-From, sl-express-gateway
可以发现以/manager开头的路径会被负载均衡到lb://sl-express-ms-web-manager这个微服务中
不过在转发请求之前,还有三个过滤器
StripPrefix=1表示会跳过请求中的第一段在进行转发,即原来的路径会去掉/manager
AddRequestHeader=X-Request-From, sl-express-gateway表示添加请求头,表示请求的来源是gateway转发的
ManagerToken是自定义的过滤器,我们需要去查看这个过滤器的实现
filters:
- StripPrefix=1
- ManagerToken
- AddRequestHeader=X-Request-From, sl-express-gateway
gateway过滤器默认会省略后面的GatewayFilterFactory后缀,即应该为 ManagerTokenGatewayFilterFactory类
找到这个类
内容:
该类继承了Gateway的AbstractGatewayFilterFactory<Object>类,表示这是一个过滤器
AuthFilter是自定义的接口,规定了检验token,和鉴权的方法
public interface AuthFilter {
/**
* 校验token
*
* @param token 请求中的token
* @return token中携带的数据
*/
AuthUserInfoDTO check(String token);
/**
* 鉴权
*
* @param token 请求中的token
* @param authUserInfo token中携带的数据
* @param path 当前请求的路径
* @return 是否通过
*/
Boolean auth(String token, AuthUserInfoDTO authUserInfo, String path);
}
/**
* 后台管理员token拦截处理
*/
@Component
public class ManagerTokenGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> implements AuthFilter {
@Resource
private MyConfig myConfig;
@Resource
private TokenCheckService tokenCheckService;
//从配置文件中加载能访问后台管理的角色ids
@Value("${role.manager}")
private List<Long>managerRoleIds;
@Override
public GatewayFilter apply(Object config) {
return new TokenGatewayFilter(this.myConfig, this);
}
@Override
public AuthUserInfoDTO check(String token) {
try {
//校验token
return tokenCheckService.parserToken(token);
} catch (AuthSdkException e) {
// 校验失败
}
return null;
}
@Override
public Boolean auth(String token, AuthUserInfoDTO authUserInfoDTO, String path) {
//获取AuthTemplate对象
AuthTemplate authTemplate = AuthTemplateFactory.get(token);
//查询该用户拥有的的角色id
List<Long> roleIds = authTemplate.opsForRole().findRoleByUserId(authUserInfoDTO.getUserId()).getData();
// 和配置的访问角色 取交集
Collection<Long> intersection = CollUtil.intersection(roleIds, managerRoleIds);
// 判断是否有交集即可判断出是否有权限
return CollUtil.isNotEmpty(intersection);
}
}
第二步:构造了TokenGatewayFilter方法,并把myConfig对象和当前过滤器对象作为参数传入
我们来看一下MyConfig类是什么
可以看到与配置文件中的sl属性继续映射
@Data
@Component
@Configuration
@ConfigurationProperties(prefix = "sl")
public class MyConfig {
private String[] noAuthPaths;
}
这个属性规定了不需要权限就可以直接访问的所有接口
sl:
noAuthPaths:
- /courier/login/account
- /courier/swagger-ui.html
- /courier/webjars/
- /courier/swagger-resources
- /courier/v2/api-docs
- /courier/doc.html
- /customer/user/login
- /customer/swagger-ui.html
- /customer/webjars/
- /customer/swagger-resources
- /customer/v2/api-docs
- /customer/doc.html
- /driver/login/account
- /driver/swagger-ui.html
- /driver/webjars/
- /driver/swagger-resources
- /driver/v2/api-docs
- /driver/doc.html
- /manager/login
- /manager/webjars/
- /manager/swagger-resources
- /manager/v2/api-docs
- /manager/doc.html
- /manager/captcha
@Slf4j
public class TokenGatewayFilter implements GatewayFilter, Ordered {
private MyConfig myConfig;
private AuthFilter authFilter;
public TokenGatewayFilter(MyConfig myConfig, AuthFilter authFilter) {
this.myConfig = myConfig;
this.authFilter = authFilter;
}
}
第三步:执行了filter方法并校验是否为放行path
如果是不需要授权的路径就直接放行,并从 Authorization请求头中获取token票据信息,如果票据信息为空,就禁止访问
票据信息是登录认证时,后端给前端发布的一个票据,包含了用户用户的基本信息和用户拥有的角色id,因为我们设置了获取验证码和登录是不需要验证鉴权的接口,所以可以直接访问然后获取该用户对应的票据信息
- /manager/login
- /manager/captcha
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String path = exchange.getRequest().getPath().toString();
if (StrUtil.startWithAny(path, myConfig.getNoAuthPaths())) {
//无需校验,直接放行
return chain.filter(exchange);
}
//获取header的参数
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
if (StrUtil.isEmpty(token)) {
//没有权限
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
}
第四步:执行AuthFilter的check方法,检验票据的合法性
//校验token
AuthUserInfoDTO authUserInfoDTO = null;
try{ //捕获token校验异常
authUserInfoDTO = this.authFilter.check(token);
}catch (Exception e){
log.error("权限校验失败,e:",e);
}
if (ObjectUtil.isEmpty(authUserInfoDTO)) {
//token失效 或 伪造
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
会去调用AuthFilter的实现类ManagerTokenGatewayFilterFactory的check方法,因为已经把这个类的实例通过TokenGateWayFilter的构造方法传入,因此可以进行回调。
我们来看一下ManagerTokenGatewayFilterFactory的check方法
不合法就返回null
@Override
public AuthUserInfoDTO check(String token) {
try {
//校验token
return tokenCheckService.parserToken(token);
} catch (AuthSdkException e) {
// 校验失败
}
return null;
}
第五步:执行AuthFilter的auth方法,进行鉴权
authUserInfoDTO是解析token时解析出来的用户的基本信息
//鉴权
Boolean result = false;
try { //捕获鉴权异常
result = this.authFilter.auth(token, authUserInfoDTO, path);
}catch (Exception e){
log.error("鉴权失败,e:",e);
}
if (!result) {
//没有权限
exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
return exchange.getResponse().setComplete();
}
回调的ManagerTokenGatewayFilterFactory的auth方法
配置的可以访问的角色id是可以访问/manger开头接口的角色id,如果没有这些角色id就拒绝访问
这个配置文件在nacos配置中心中
//从配置文件中加载能访问后台管理的角色ids
@Value("${role.manager}")
private List<Long>managerRoleIds;
#角色id
role.manager = 986227712144197857,989278284569131905,996045142395786081,996045927523359809
@Override
public Boolean auth(String token, AuthUserInfoDTO authUserInfoDTO, String path) {
//获取AuthTemplate对象
AuthTemplate authTemplate = AuthTemplateFactory.get(token);
//查询该用户拥有的的角色id
List<Long> roleIds = authTemplate.opsForRole().findRoleByUserId(authUserInfoDTO.getUserId()).getData();
// 和配置的访问角色 取交集
Collection<Long> intersection = CollUtil.intersection(roleIds, managerRoleIds);
// 判断是否有交集即可判断出是否有权限
return CollUtil.isNotEmpty(intersection);
}
所以,如果该用户可以访问就返回true,反之
第六步:放行
并添加了两个请求头,把用户的基本信息序列化成json格式,并把票据加上token请求头中
//增加参数
exchange.getRequest().mutate().header("userInfo", JSONUtil.toJsonStr(authUserInfoDTO));
exchange.getRequest().mutate().header("token", token);
//校验通过放行
return chain.filter(exchange);
完整的TokenGateWayFilter类
@Slf4j
public class TokenGatewayFilter implements GatewayFilter, Ordered {
private MyConfig myConfig;
private AuthFilter authFilter;
public TokenGatewayFilter(MyConfig myConfig, AuthFilter authFilter) {
this.myConfig = myConfig;
this.authFilter = authFilter;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String path = exchange.getRequest().getPath().toString();
if (StrUtil.startWithAny(path, myConfig.getNoAuthPaths())) {
//无需校验,直接放行
return chain.filter(exchange);
}
//获取header的参数
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
if (StrUtil.isEmpty(token)) {
//没有权限
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
//校验token
AuthUserInfoDTO authUserInfoDTO = null;
try{ //捕获token校验异常
authUserInfoDTO = this.authFilter.check(token);
}catch (Exception e){
log.error("权限校验失败,e:",e);
}
if (ObjectUtil.isEmpty(authUserInfoDTO)) {
//token失效 或 伪造
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
//鉴权
Boolean result = false;
try { //捕获鉴权异常
result = this.authFilter.auth(token, authUserInfoDTO, path);
}catch (Exception e){
log.error("鉴权失败,e:",e);
}
if (!result) {
//没有权限
exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
return exchange.getResponse().setComplete();
}
//增加参数
exchange.getRequest().mutate().header("userInfo", JSONUtil.toJsonStr(authUserInfoDTO));
exchange.getRequest().mutate().header("token", token);
//校验通过放行
return chain.filter(exchange);
}
@Override
public int getOrder() {
return Integer.MIN_VALUE;
}
}
权限管家的api使用
依赖
<dependency>
<groupId>com.itheima.em.auth</groupId>
<artifactId>itcast-auth-spring-boot-starter</artifactId>
</dependency>
@SpringBootTest(properties = "spring.main.web-application-type = reactive")
public class AuthTemplateTest {
@Resource
private AuthTemplate authTemplate;
@Resource
private TokenCheckService tokenCheckService;
@Autowired
private AuthorityProperties authorityProperties;
@Test
public void testLogin() {
//登录,生成token
Result<LoginDTO> result = this.authTemplate.opsForLogin()
.token("hhh", "123456");
//得到token
String token = result.getData().getToken().getToken();
System.out.println("token为:" + token);
//得到用户的消息
UserDTO user = result.getData().getUser();
System.out.println("user信息:" + user);
//查询这个用户拥有的角色idd
Result<List<Long>> resultRole = AuthTemplateFactory.get(token).opsForRole()
.findRoleByUserId(user.getId());
System.out.println(resultRole);
}
@Test
public void checkToken() {
//上面方法中生成的token
String token = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMzEyNDQ2OTcwNjUyMDAyODQ5IiwiYWNjb3VudCI6ImhoaCIsIm5hbWUiOiLkvZUiLCJvcmdpZCI6MTAyNTEwMzE3Njk0NjM5NTU4NSwic3RhdGlvbmlkIjoxMDI0NzA1NDg5NDM2NDk0NzIxLCJhZG1pbmlzdHJhdG9yIjpmYWxzZSwiZXhwIjoxNzMyOTk2NzI5fQ.lkOPgWXwOJ6OUuBiw4ctOyU7gl9sypxsYn2Sca4waHOFfxkQcVtSFzcKt-aieBS3Dn-vWfKx2YVQXkmggXOYIg";
AuthUserInfoDTO authUserInfo = this.tokenCheckService.parserToken(token);
System.out.println(authUserInfo);
System.out.println(JSONUtil.toJsonStr(authUserInfo));
}
}