1178 lines
32 KiB
Markdown
1178 lines
32 KiB
Markdown
### 1. 技术选型
|
||
|
||
----
|
||
|
||
* 注册中心:nacos,替代方案eureka、consul、zookeeper
|
||
|
||
* 配置中心: nacos ,替代方案sc config、consul config
|
||
|
||
* 服务调用:feign,替代方案:resttempate
|
||
|
||
* 熔断:sentinel、,替代方案:Resilience4j
|
||
|
||
* 熔断监控:sentinel dashboard
|
||
|
||
* 负载均衡:sc loadbalancer
|
||
|
||
* 网关:spring cloud gateway
|
||
|
||
* 链路:spring cloud sleuth+zipkin,替代方案:skywalking等。
|
||
|
||
* 分时任务调度框架 xxl-job
|
||
|
||
* admin 监控
|
||
|
||
-----
|
||
|
||
### 2.工程搭建
|
||
|
||
<br>
|
||
|
||
#### 2.1 搭建父工程
|
||
|
||
<br>
|
||
|
||
* mystyle-cloud-parent
|
||
|
||
```xml
|
||
|
||
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||
<modelVersion>4.0.0</modelVersion>
|
||
|
||
<groupId>com.zhangmeng</groupId>
|
||
<artifactId>mystyle-cloud-parent</artifactId>
|
||
<version>1.0-SNAPSHOT</version>
|
||
|
||
<properties>
|
||
<java.version>1.8</java.version>
|
||
<spring-boot.version>2.4.4</spring-boot.version>
|
||
<spring-cloud.version>2020.0.2</spring-cloud.version>
|
||
<spring-cloud-alibaba.version>2020.0.RC1</spring-cloud-alibaba.version>
|
||
</properties>
|
||
|
||
|
||
<dependencyManagement>
|
||
<dependencies>
|
||
<!-- spring boot 依赖 -->
|
||
<dependency>
|
||
<groupId>org.springframework.boot</groupId>
|
||
<artifactId>spring-boot-dependencies</artifactId>
|
||
<version>${spring-boot.version}</version>
|
||
<type>pom</type>
|
||
<scope>import</scope>
|
||
</dependency>
|
||
<!-- spring cloud 依赖 -->
|
||
<dependency>
|
||
<groupId>org.springframework.cloud</groupId>
|
||
<artifactId>spring-cloud-dependencies</artifactId>
|
||
<version>${spring-cloud.version}</version>
|
||
<type>pom</type>
|
||
<scope>import</scope>
|
||
</dependency>
|
||
<!-- spring cloud alibaba 依赖 -->
|
||
<dependency>
|
||
<groupId>com.alibaba.cloud</groupId>
|
||
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
|
||
<version>${spring-cloud-alibaba.version}</version>
|
||
<type>pom</type>
|
||
<scope>import</scope>
|
||
</dependency>
|
||
</dependencies>
|
||
</dependencyManagement>
|
||
</project>
|
||
|
||
```
|
||
|
||
|
||
#### 2.2 搭建用户中心(废除)
|
||
|
||
* mystyle-cloud-user
|
||
|
||
> 包结构 : com.zhangmeng.user
|
||
|
||
[mystyle-cloud-user](http://localhost:8848/nacos/)
|
||
|
||
```xml
|
||
|
||
<dependencies>
|
||
|
||
<dependency>
|
||
<groupId>org.springframework.boot</groupId>
|
||
<artifactId>spring-boot-starter-web</artifactId>
|
||
</dependency>
|
||
|
||
<dependency>
|
||
<groupId>com.alibaba.cloud</groupId>
|
||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||
</dependency>
|
||
|
||
</dependencies>
|
||
```
|
||
|
||
#### 2.3 搭建文件中心
|
||
|
||
* mystyle-cloud-file
|
||
|
||
> 包结构 :com.zhangmeng.file
|
||
|
||
[file调用user](http://localhost:8763/file/name?name=转身的背影在心底里沉沦)
|
||
|
||

|
||
|
||

|
||
|
||
-------
|
||
|
||
```xml
|
||
|
||
<dependencies>
|
||
<dependency>
|
||
<groupId>org.springframework.boot</groupId>
|
||
<artifactId>spring-boot-starter-web</artifactId>
|
||
</dependency>
|
||
|
||
<dependency>
|
||
<groupId>com.alibaba.cloud</groupId>
|
||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||
</dependency>
|
||
|
||
<dependency>
|
||
<groupId>org.springframework.cloud</groupId>
|
||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||
</dependency>
|
||
|
||
<dependency>
|
||
<groupId>org.springframework.cloud</groupId>
|
||
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||
</dependency>
|
||
</dependencies>
|
||
```
|
||
|
||
#### 2.4 搭建网关
|
||
|
||
|
||
* mystyle-cloud-gateway
|
||
|
||
> 包结构 :com.zhangmeng.gateway
|
||
|
||
[测试](http://localhost:5000/mystyle-file/file/name?name=转身的背影在心底里沉沦)
|
||
|
||

|
||
|
||
-------
|
||
```xml
|
||
<dependencies>
|
||
<dependency>
|
||
<groupId>com.alibaba.cloud</groupId>
|
||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>org.springframework.cloud</groupId>
|
||
<artifactId>spring-cloud-starter-gateway</artifactId>
|
||
</dependency>
|
||
|
||
<dependency>
|
||
<groupId>org.springframework.boot</groupId>
|
||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||
</dependency>
|
||
|
||
<dependency>
|
||
<groupId>org.springframework.cloud</groupId>
|
||
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||
</dependency>
|
||
|
||
<dependency>
|
||
<groupId>com.alibaba.cloud</groupId>
|
||
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
|
||
</dependency>
|
||
|
||
<dependency>
|
||
<groupId>com.alibaba.cloud</groupId>
|
||
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
|
||
</dependency>
|
||
|
||
</dependencies>
|
||
|
||
```
|
||
|
||
* 创建一个网关分组和网关的限流规则
|
||
|
||
```java
|
||
@Configuration
|
||
public class GatewayConfiguration {
|
||
|
||
private final List<ViewResolver> viewResolvers;
|
||
private final ServerCodecConfigurer serverCodecConfigurer;
|
||
|
||
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
|
||
ServerCodecConfigurer serverCodecConfigurer) {
|
||
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
|
||
this.serverCodecConfigurer = serverCodecConfigurer;
|
||
}
|
||
|
||
@Bean
|
||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
|
||
// Register the block exception handler for Spring Cloud Gateway.
|
||
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
|
||
}
|
||
|
||
@Bean
|
||
@Order(-1)
|
||
public GlobalFilter sentinelGatewayFilter() {
|
||
return new SentinelGatewayFilter();
|
||
}
|
||
|
||
@PostConstruct
|
||
public void doInit() {
|
||
initCustomizedApis();
|
||
initGatewayRules();
|
||
}
|
||
|
||
private void initCustomizedApis() {
|
||
Set<ApiDefinition> definitions = new HashSet<>();
|
||
ApiDefinition api1 = new ApiDefinition("consumer")
|
||
.setPredicateItems(new HashSet<ApiPredicateItem>() );
|
||
ApiDefinition api2 = new ApiDefinition("provider")
|
||
.setPredicateItems(new HashSet<ApiPredicateItem>() );
|
||
definitions.add(api1);
|
||
definitions.add(api2);
|
||
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
|
||
}
|
||
|
||
private void initGatewayRules() {
|
||
Set<GatewayFlowRule> rules = new HashSet<>();
|
||
rules.add(new GatewayFlowRule("consumer")
|
||
.setCount(10)
|
||
.setIntervalSec(1)
|
||
);
|
||
rules.add(new GatewayFlowRule("consumer")
|
||
.setCount(2)
|
||
.setIntervalSec(2)
|
||
.setBurst(2)
|
||
.setParamItem(new GatewayParamFlowItem()
|
||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP)
|
||
)
|
||
);
|
||
rules.add(new GatewayFlowRule("provider")
|
||
.setCount(10)
|
||
.setIntervalSec(1)
|
||
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)
|
||
.setMaxQueueingTimeoutMs(600)
|
||
.setParamItem(new GatewayParamFlowItem()
|
||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER)
|
||
.setFieldName("X-Sentinel-Flag")
|
||
)
|
||
);
|
||
rules.add(new GatewayFlowRule("provider")
|
||
.setCount(1)
|
||
.setIntervalSec(1)
|
||
.setParamItem(new GatewayParamFlowItem()
|
||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
|
||
.setFieldName("pa")
|
||
)
|
||
);
|
||
rules.add(new GatewayFlowRule("provider")
|
||
.setCount(2)
|
||
.setIntervalSec(30)
|
||
.setParamItem(new GatewayParamFlowItem()
|
||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
|
||
.setFieldName("type")
|
||
.setPattern("warn")
|
||
.setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_CONTAINS)
|
||
)
|
||
);
|
||
|
||
rules.add(new GatewayFlowRule("provider")
|
||
.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME)
|
||
.setCount(5)
|
||
.setIntervalSec(1)
|
||
.setParamItem(new GatewayParamFlowItem()
|
||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
|
||
.setFieldName("pn")
|
||
)
|
||
);
|
||
GatewayRuleManager.loadRules(rules);
|
||
}
|
||
}
|
||
```
|
||
|
||
|
||
|
||
|
||
#### 2.5 sentinel dashboard
|
||
|
||
[sentinel dashboard](http://localhost:8748)
|
||
|
||
> java -Dserver.port=8748 -Dcsp.sentinel.dashboard.server=localhost:8748 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.1.jar
|
||
|
||
添加流控规则 :
|
||
|
||

|
||
|
||
|
||
<br>
|
||
|
||
#### 2.6 搭建注册中心
|
||
|
||
<br>
|
||
|
||
[nacos](http://localhost:8848/nacos/#/login)
|
||
|
||

|
||
|
||
#### 2.7 搭建 zipkin server
|
||
|
||
[zipkin server](http://localhost:9411/zipkin/)
|
||
|
||

|
||
|
||
<br>
|
||
|
||

|
||
|
||
|
||
批处理 启动
|
||
|
||
```bat
|
||
@echo off
|
||
|
||
start /d "E:\nacos-server-2.0.2\nacos\bin\" startup.cmd
|
||
|
||
start cmd /c "title sentinel-dashboard && java -jar E:\nacos-server-2.0.2\sentinel-dashboard-1.8.2.jar --server.port=8748 &"
|
||
|
||
start cmd /c "title zipkin-server-2.23.4-exec && java -jar E:\nacos-server-2.0.2\zipkin-server-2.23.4-exec.jar --server.port=9411 &"
|
||
|
||
pause
|
||
|
||
```
|
||
|
||
#### 2.8 搭建 model
|
||
|
||
* mystyle-cloud-model
|
||
|
||
> 包结构 :com.zhangmeng.model
|
||
|
||
集成 jpa 及 通用 mapper
|
||
|
||

|
||
|
||
```java
|
||
@SpringBootApplication
|
||
@EnableDiscoveryClient
|
||
@EnableFeignClients
|
||
@ComponentScan(basePackages = {"com.zhangmeng.model","com.zhangmeng.file"})
|
||
public class FileApplication {
|
||
|
||
public static void main(String[] args) {
|
||
SpringApplication.run(FileApplication.class,args);
|
||
}
|
||
}
|
||
```
|
||
|
||
测试
|
||

|
||
|
||
#### 2.8 搭建 管理后台
|
||
|
||
* mystyle-cloud-admin-manager
|
||
|
||
> 包结构 :com.zhangmeng.admin.manager
|
||
|
||

|
||
|
||
#### 2.9 api 搭建 api
|
||
|
||
* mystyle-cloud-api
|
||
|
||
> 包结构 :com.zhangmeng.api
|
||
>
|
||
|
||

|
||
|
||
```java
|
||
|
||
package com.zhangmeng.api.config;
|
||
|
||
import org.springframework.context.annotation.Bean;
|
||
import org.springframework.context.annotation.Configuration;
|
||
import springfox.documentation.builders.ApiInfoBuilder;
|
||
import springfox.documentation.builders.PathSelectors;
|
||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||
import springfox.documentation.service.*;
|
||
import springfox.documentation.spi.DocumentationType;
|
||
import springfox.documentation.spi.service.contexts.SecurityContext;
|
||
import springfox.documentation.spring.web.plugins.Docket;
|
||
import springfox.documentation.swagger2.annotations.EnableSwagger2;
|
||
|
||
import java.util.ArrayList;
|
||
import java.util.List;
|
||
|
||
/**
|
||
* @author zhangmeng
|
||
* @version 1.0
|
||
* @date 2021年7月8日09:12:01
|
||
*/
|
||
@EnableSwagger2
|
||
@Configuration
|
||
public class SwaggerConfig {
|
||
|
||
@Bean
|
||
public Docket docket() {
|
||
return new Docket(DocumentationType.SWAGGER_2).groupName("swagger接口文档")
|
||
.apiInfo(new ApiInfoBuilder().title("swagger接口文档")
|
||
.contact(new Contact("转身的背影在心底里沉沦", "", "1334717033.com")).version("1.0").build())
|
||
.securitySchemes(securitySchemes())
|
||
.securityContexts(securityContexts())
|
||
.select()
|
||
.apis(RequestHandlerSelectors.basePackage("com.zhangmeng"))
|
||
.paths(PathSelectors.any()).build();
|
||
}
|
||
|
||
private List<ApiKey> securitySchemes() {
|
||
List<ApiKey> apiKeyList= new ArrayList<>();
|
||
apiKeyList.add(new ApiKey("token", "token", "header"));
|
||
return apiKeyList;
|
||
}
|
||
|
||
private List<SecurityContext> securityContexts() {
|
||
List<SecurityContext> securityContexts=new ArrayList<>();
|
||
securityContexts.add(
|
||
SecurityContext.builder()
|
||
.securityReferences(defaultAuth())
|
||
.forPaths(PathSelectors.regex("^(?!auth).*$"))
|
||
.build());
|
||
return securityContexts;
|
||
}
|
||
|
||
private List<SecurityReference> defaultAuth() {
|
||
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
|
||
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
|
||
authorizationScopes[0] = authorizationScope;
|
||
List<SecurityReference> securityReferences=new ArrayList<>();
|
||
securityReferences.add(new SecurityReference("token", authorizationScopes));
|
||
return securityReferences;
|
||
}
|
||
}
|
||
|
||
```
|
||
|
||
[admin_manager](http://localhost:31003/swagger-ui.html)
|
||
|
||

|
||
|
||
|
||
#### 2.10 搭建 canal
|
||
|
||
* mystyle-cloud-canal
|
||
|
||
> 包结构 :com.zhangmeng.canal
|
||
|
||

|
||
|
||
|
||
(2) 创建账号 用于测试使用,
|
||
|
||
使用root账号创建用户并授予权限
|
||
|
||
```properties
|
||
create user canal@'%' IDENTIFIED by 'canal';
|
||
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT,SUPER ON *.* TO 'canal'@'%';
|
||
FLUSH PRIVILEGES;
|
||
```
|
||
|
||
|
||
|
||
```java
|
||
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
|
||
@EnableEurekaClient
|
||
@EnableCanalClient
|
||
public class CanalApplication {
|
||
|
||
public static void main(String[] args) {
|
||
SpringApplication.run(CanalApplication.class,args);
|
||
}
|
||
}
|
||
```
|
||
|
||

|
||
|
||
|
||
#### 2.11 搭建 消息中间件 rabbit mq
|
||
|
||
* mystyle-cloud-mq
|
||
|
||
> 包结构 :com.zhangmeng.mq
|
||
|
||
|
||
#### 2.12 搭建 授权中心
|
||
|
||
* mystyle-cloud-oauth
|
||
|
||
> 包结构 :com.zhangmeng.oauth
|
||
|
||
* Encoded password does not look like BCrypt
|
||
|
||

|
||
|
||

|
||
|
||
#### 2.13 搭建博客
|
||
|
||
* mystyle-cloud-blog
|
||
|
||
> 包结构 :com.zhangmeng.blog
|
||
|
||
#### 2.14 搭建定时任务
|
||
|
||
思路 :
|
||
>* quartz + rabbit_mq
|
||
>
|
||
>* xxl-job 分布式调度框架
|
||
|
||
* mystyle-cloud-quartz
|
||
> com.zhangmeng.quartz
|
||

|
||
|
||
后续调整为:
|
||
|
||
* mystyle-cloud-xxl-job
|
||
> com.zhangmeng.job
|
||
|
||
引入依赖
|
||
|
||
```yaml
|
||
<dependency>
|
||
<groupId>com.xuxueli</groupId>
|
||
<artifactId>xxl-job-core</artifactId>
|
||
<version>2.2.0</version>
|
||
</dependency>
|
||
```
|
||
|
||
#### 2.15 搭建邮件
|
||
|
||
* mystyle-cloud-mail
|
||
> 包结构 :com.zhangmeng.mail
|
||
|
||
```java
|
||
<dependency>
|
||
<groupId>org.springframework.boot</groupId>
|
||
<artifactId>spring-boot-starter-mail</artifactId>
|
||
</dependency>
|
||
```
|
||
|
||
#### 2.15 搭建小说
|
||
|
||
* mystyle-cloud-fiction
|
||
> 包结构 :com.zhangmeng.fiction
|
||
|
||
#### 2.16 搭建admin 监控
|
||
|
||
* mystyle-cloud-admin-monitor
|
||
> 包结构 :com.zhangmeng.monitor
|
||
|
||
```yaml
|
||
management:
|
||
endpoints:
|
||
web:
|
||
exposure:
|
||
include: '*'
|
||
endpoint:
|
||
health:
|
||
show-details: ALWAYS
|
||
enabled: true
|
||
```
|
||
```xml
|
||
<dependency>
|
||
<groupId>de.codecentric</groupId>
|
||
<artifactId>spring-boot-admin-starter-client</artifactId>
|
||
</dependency>
|
||
```
|
||
|
||
```java
|
||
- /actuator/**
|
||
- /instances/**
|
||
```
|
||
|
||

|
||
|
||

|
||
|
||
|
||
|
||
### 3.接口说明
|
||
|
||
#### 3.1 登录接口
|
||
|
||
##### 3.1.1 验证码
|
||
|
||
[验证码](http://localhost:9000/mystyle-cloud-admin-manager/verificationCode/generate)
|
||

|
||
|
||
##### 3.1.2 查询当前用户
|
||
|
||
[地址](http://localhost:9000/mystyle-cloud-admin-manager/user/current)
|
||
|
||
##### 3.1.3 登录
|
||
|
||
* 问题 跨域
|
||

|
||
|
||
[地址](http://localhost:9000/mystyle-cloud-oauth/user/login)
|
||
|
||
* 解决办法
|
||
|
||
gate_way yml 配置
|
||
|
||
```yaml
|
||
server:
|
||
port: 9000
|
||
spring:
|
||
application:
|
||
name: mystyle-cloud-gateway
|
||
zipkin:
|
||
sender:
|
||
type: web
|
||
base-url: http://localhost:9411/
|
||
service:
|
||
name: mystyle-cloud-gateway
|
||
sleuth:
|
||
sampler:
|
||
probability: 1
|
||
cloud:
|
||
sentinel:
|
||
transport:
|
||
port: 15000
|
||
dashboard: localhost:8748
|
||
nacos:
|
||
discovery:
|
||
server-addr: 127.0.0.1:8848
|
||
gateway:
|
||
globalcors:
|
||
corsConfigurations:
|
||
'[/**]': # 匹配所有请求
|
||
allowedOrigins: "*" #跨域处理 允许所有的域
|
||
allowedMethods: # 支持的方法
|
||
- GET
|
||
- POST
|
||
- PUT
|
||
- DELETE
|
||
allowedOriginPatterns: "*"
|
||
discovery:
|
||
locator:
|
||
enabled: false
|
||
lowerCaseServiceId: true
|
||
routes:
|
||
- id: mystyle-cloud-oauth
|
||
uri: lb://mystyle-cloud-oauth
|
||
predicates:
|
||
- Path=/mystyle-cloud-oauth/**
|
||
filters:
|
||
- StripPrefix=1
|
||
- id: mystyle-cloud-admin-manager
|
||
uri: lb://mystyle-cloud-admin-manager
|
||
predicates:
|
||
- Path=/mystyle-cloud-admin-manager/**
|
||
filters:
|
||
- StripPrefix=1
|
||
|
||
```
|
||
|
||
或者
|
||
|
||
```java
|
||
|
||
package com.zhangmeng.gateway.config;
|
||
|
||
import org.springframework.context.annotation.Bean;
|
||
import org.springframework.context.annotation.Configuration;
|
||
import org.springframework.web.cors.CorsConfiguration;
|
||
import org.springframework.web.cors.reactive.CorsWebFilter;
|
||
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
|
||
|
||
|
||
/**
|
||
* @author zhangmeng
|
||
* @date 2021年11月8日15:52:27
|
||
* @version 1.0
|
||
*/
|
||
@Configuration
|
||
public class CorsConfig {
|
||
|
||
@Bean
|
||
public CorsWebFilter corsWebFilter(){
|
||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||
|
||
CorsConfiguration corsConfiguration = new CorsConfiguration();
|
||
//1、配置跨域
|
||
//允许哪些头进行跨域
|
||
corsConfiguration.addAllowedHeader("*");
|
||
//允许哪些请求方式进行跨域
|
||
corsConfiguration.addAllowedMethod("*");
|
||
//允许哪些请求来源进行跨域
|
||
corsConfiguration.addAllowedOriginPattern("*");
|
||
//是否允许携带cookie进行跨域,否则跨域请求会丢失cookie信息
|
||
corsConfiguration.setAllowCredentials(true);
|
||
|
||
source.registerCorsConfiguration("/**",corsConfiguration);
|
||
|
||
return new CorsWebFilter(source);
|
||
}
|
||
}
|
||
|
||
```
|
||
|
||
> 提示 : 调用的具体服务去掉跨域配置
|
||
|
||
[首页访问](http://localhost:31007/login.html)
|
||
|
||

|
||
|
||
#### 3.2 首页
|
||
|
||
-------
|
||
>**获取鉴权信息**
|
||
|
||
<br>
|
||
|
||
* 源码
|
||
|
||
<br>
|
||
|
||
oauth2.0 获取鉴权
|
||
|
||
```java
|
||
|
||
/**
|
||
* Copyright 2006-2011 the original author or authors.
|
||
*
|
||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||
* the License. You may obtain a copy of the License at
|
||
*
|
||
* http://www.apache.org/licenses/LICENSE-2.0
|
||
*
|
||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||
* specific language governing permissions and limitations under the License.
|
||
*/
|
||
package org.springframework.security.oauth2.provider.authentication;
|
||
|
||
import java.io.IOException;
|
||
|
||
import javax.servlet.Filter;
|
||
import javax.servlet.FilterChain;
|
||
import javax.servlet.FilterConfig;
|
||
import javax.servlet.ServletException;
|
||
import javax.servlet.ServletRequest;
|
||
import javax.servlet.ServletResponse;
|
||
import javax.servlet.http.HttpServletRequest;
|
||
import javax.servlet.http.HttpServletResponse;
|
||
|
||
import org.apache.commons.logging.Log;
|
||
import org.apache.commons.logging.LogFactory;
|
||
import org.springframework.beans.factory.InitializingBean;
|
||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||
import org.springframework.security.authentication.AuthenticationDetailsSource;
|
||
import org.springframework.security.authentication.AuthenticationEventPublisher;
|
||
import org.springframework.security.authentication.AuthenticationManager;
|
||
import org.springframework.security.authentication.BadCredentialsException;
|
||
import org.springframework.security.authentication.InsufficientAuthenticationException;
|
||
import org.springframework.security.core.Authentication;
|
||
import org.springframework.security.core.AuthenticationException;
|
||
import org.springframework.security.core.context.SecurityContextHolder;
|
||
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
|
||
import org.springframework.security.oauth2.provider.OAuth2Authentication;
|
||
import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint;
|
||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
||
import org.springframework.util.Assert;
|
||
|
||
/**
|
||
* A pre-authentication filter for OAuth2 protected resources. Extracts an OAuth2 token from the incoming request and
|
||
* uses it to populate the Spring Security context with an {@link OAuth2Authentication} (if used in conjunction with an
|
||
* {@link OAuth2AuthenticationManager}).
|
||
*
|
||
* @author Dave Syer
|
||
*
|
||
*/
|
||
public class OAuth2AuthenticationProcessingFilter implements Filter, InitializingBean {
|
||
|
||
private final static Log logger = LogFactory.getLog(OAuth2AuthenticationProcessingFilter.class);
|
||
|
||
private AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
|
||
|
||
private AuthenticationManager authenticationManager;
|
||
|
||
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new OAuth2AuthenticationDetailsSource();
|
||
|
||
private TokenExtractor tokenExtractor = new BearerTokenExtractor();
|
||
|
||
private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();
|
||
|
||
private boolean stateless = true;
|
||
|
||
/**
|
||
* Flag to say that this filter guards stateless resources (default true). Set this to true if the only way the
|
||
* resource can be accessed is with a token. If false then an incoming cookie can populate the security context and
|
||
* allow access to a caller that isn't an OAuth2 client.
|
||
*
|
||
* @param stateless the flag to set (default true)
|
||
*/
|
||
public void setStateless(boolean stateless) {
|
||
this.stateless = stateless;
|
||
}
|
||
|
||
/**
|
||
* @param authenticationEntryPoint the authentication entry point to set
|
||
*/
|
||
public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
|
||
this.authenticationEntryPoint = authenticationEntryPoint;
|
||
}
|
||
|
||
/**
|
||
* @param authenticationManager the authentication manager to set (mandatory with no default)
|
||
*/
|
||
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
|
||
this.authenticationManager = authenticationManager;
|
||
}
|
||
|
||
/**
|
||
* @param tokenExtractor the tokenExtractor to set
|
||
*/
|
||
public void setTokenExtractor(TokenExtractor tokenExtractor) {
|
||
this.tokenExtractor = tokenExtractor;
|
||
}
|
||
|
||
/**
|
||
* @param eventPublisher the event publisher to set
|
||
*/
|
||
public void setAuthenticationEventPublisher(AuthenticationEventPublisher eventPublisher) {
|
||
this.eventPublisher = eventPublisher;
|
||
}
|
||
|
||
/**
|
||
* @param authenticationDetailsSource The AuthenticationDetailsSource to use
|
||
*/
|
||
public void setAuthenticationDetailsSource(
|
||
AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
|
||
Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required");
|
||
this.authenticationDetailsSource = authenticationDetailsSource;
|
||
}
|
||
|
||
public void afterPropertiesSet() {
|
||
Assert.state(authenticationManager != null, "AuthenticationManager is required");
|
||
}
|
||
|
||
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
|
||
ServletException {
|
||
|
||
final boolean debug = logger.isDebugEnabled();
|
||
final HttpServletRequest request = (HttpServletRequest) req;
|
||
final HttpServletResponse response = (HttpServletResponse) res;
|
||
|
||
try {
|
||
|
||
Authentication authentication = tokenExtractor.extract(request);
|
||
|
||
if (authentication == null) {
|
||
if (stateless && isAuthenticated()) {
|
||
if (debug) {
|
||
logger.debug("Clearing security context.");
|
||
}
|
||
SecurityContextHolder.clearContext();
|
||
}
|
||
if (debug) {
|
||
logger.debug("No token in request, will continue chain.");
|
||
}
|
||
}
|
||
else {
|
||
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
|
||
if (authentication instanceof AbstractAuthenticationToken) {
|
||
AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken) authentication;
|
||
needsDetails.setDetails(authenticationDetailsSource.buildDetails(request));
|
||
}
|
||
Authentication authResult = authenticationManager.authenticate(authentication);
|
||
|
||
if (debug) {
|
||
logger.debug("Authentication success: " + authResult);
|
||
}
|
||
|
||
eventPublisher.publishAuthenticationSuccess(authResult);
|
||
SecurityContextHolder.getContext().setAuthentication(authResult);
|
||
|
||
}
|
||
}
|
||
catch (OAuth2Exception failed) {
|
||
SecurityContextHolder.clearContext();
|
||
|
||
if (debug) {
|
||
logger.debug("Authentication request failed: " + failed);
|
||
}
|
||
eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed),
|
||
new PreAuthenticatedAuthenticationToken("access-token", "N/A"));
|
||
|
||
authenticationEntryPoint.commence(request, response,
|
||
new InsufficientAuthenticationException(failed.getMessage(), failed));
|
||
|
||
return;
|
||
}
|
||
|
||
chain.doFilter(request, response);
|
||
}
|
||
|
||
private boolean isAuthenticated() {
|
||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||
if (authentication == null || authentication instanceof AnonymousAuthenticationToken) {
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
public void init(FilterConfig filterConfig) throws ServletException {
|
||
}
|
||
|
||
public void destroy() {
|
||
}
|
||
|
||
private static final class NullEventPublisher implements AuthenticationEventPublisher {
|
||
public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) {
|
||
}
|
||
|
||
public void publishAuthenticationSuccess(Authentication authentication) {
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
```
|
||
|
||

|
||
|
||
##### 3.2.1 先从请求头中获取
|
||
|
||
```java
|
||
protected String extractToken(HttpServletRequest request) {
|
||
// first check the header...
|
||
--------------------------------------------------------------
|
||
String token = extractHeaderToken(request);
|
||
--------------------------------------------------------------
|
||
// bearer type allows a request parameter as well
|
||
if (token == null) {
|
||
logger.debug("Token not found in headers. Trying request parameters.");
|
||
token = request.getParameter(OAuth2AccessToken.ACCESS_TOKEN);
|
||
if (token == null) {
|
||
logger.debug("Token not found in request parameters. Not an OAuth2 request.");
|
||
}
|
||
else {
|
||
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, OAuth2AccessToken.BEARER_TYPE);
|
||
}
|
||
}
|
||
|
||
return token;
|
||
}
|
||
```
|
||
|
||

|
||
|
||
```java
|
||
|
||
/**
|
||
* Extract the OAuth bearer token from a header.
|
||
*
|
||
* @param request The request.
|
||
* @return The token, or null if no OAuth authorization header was supplied.
|
||
*/
|
||
protected String extractHeaderToken(HttpServletRequest request) {
|
||
---------------------------------------------------------------------------
|
||
Enumeration<String> headers = request.getHeaders("Authorization");
|
||
----------------------------------------------------------------------------
|
||
while (headers.hasMoreElements()) { // typically there is only one (most servers enforce that)
|
||
String value = headers.nextElement();
|
||
if ((value.toLowerCase().startsWith(OAuth2AccessToken.BEARER_TYPE.toLowerCase()))) {
|
||
String authHeaderValue = value.substring(OAuth2AccessToken.BEARER_TYPE.length()).trim();
|
||
// Add this here for the auth details later. Would be better to change the signature of this method.
|
||
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE,
|
||
value.substring(0, OAuth2AccessToken.BEARER_TYPE.length()).trim());
|
||
int commaIndex = authHeaderValue.indexOf(',');
|
||
if (commaIndex > 0) {
|
||
authHeaderValue = authHeaderValue.substring(0, commaIndex);
|
||
}
|
||
return authHeaderValue;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
```
|
||
|
||

|
||
|
||
##### 3.2.2 从请求参数中获取
|
||
|
||
```java
|
||
protected String extractToken(HttpServletRequest request) {
|
||
// first check the header...
|
||
String token = extractHeaderToken(request);
|
||
|
||
// bearer type allows a request parameter as well
|
||
if (token == null) {
|
||
logger.debug("Token not found in headers. Trying request parameters.");
|
||
-------------------------------------------------------------------
|
||
token = request.getParameter(OAuth2AccessToken.ACCESS_TOKEN);
|
||
-------------------------------------------------------------------
|
||
if (token == null) {
|
||
logger.debug("Token not found in request parameters. Not an OAuth2 request.");
|
||
}
|
||
else {
|
||
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, OAuth2AccessToken.BEARER_TYPE);
|
||
}
|
||
}
|
||
|
||
return token;
|
||
}
|
||
|
||
```
|
||
|
||

|
||
|
||
-------------------------------------------
|
||
|
||
|
||
##### 3.2.3 问题
|
||
|
||
>首页不展示
|
||
|
||

|
||
|
||
>控制台报错 Refused to display 'http://localhost:9000/' in a frame because it set 'X-Frame-Options' to 'deny'.
|
||
|
||
> 解决办法
|
||
|
||
```java
|
||
http.headers().frameOptions().disable();
|
||
```
|
||

|
||
--------------------------
|
||

|
||
-------------------------------
|
||
> token 过期展示
|
||
|
||

|
||
|
||
|
||
|
||
#### 3.3 文件上传
|
||

|
||
|
||
> 解决办法
|
||
|
||
```yaml
|
||
spring:
|
||
servlet:
|
||
multipart:
|
||
max-file-size: 4GB
|
||
max-request-size: 4GB
|
||
```
|
||
|
||

|
||
|
||
#### 3.4 feign 远程调用
|
||
|
||
> 报错 :nested exception is feign.RetryableException: too many bytes written executing
|
||
|
||
>* 解决办法
|
||
>
|
||
```java
|
||
// 跳过 content-length
|
||
if (element.equals("content-length")){
|
||
continue;
|
||
}
|
||
```
|
||
```java
|
||
@Configuration
|
||
public class FeignOauth2RequestInterceptor implements RequestInterceptor {
|
||
@Override
|
||
public void apply(RequestTemplate requestTemplate) {
|
||
// 获取的全部请求信息
|
||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||
if (attributes != null){
|
||
HttpServletRequest request = attributes.getRequest();
|
||
// 获取所有的请求头信息
|
||
Enumeration<String> headerNames = request.getHeaderNames();
|
||
if (headerNames != null){
|
||
while (headerNames.hasMoreElements()){
|
||
// 获取请求头的key
|
||
String element = headerNames.nextElement();
|
||
// 获取请求头的value
|
||
String value = request.getHeader(element);
|
||
----------------------------------------------------------------------------------------------------------------
|
||
// 跳过 content-length
|
||
if (element.equals("content-length")){
|
||
continue;
|
||
}
|
||
----------------------------------------------------------------------------------------------------------------
|
||
// 将请求头信息放入到请求头
|
||
requestTemplate.header(element,value);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
```
|
||
|
||
### 4. 博客展示
|
||
|
||

|
||
|
||
#### 4.1 详情
|
||
|
||

|
||
|
||
#### 4.2 关于我
|
||
|
||

|
||
|
||
### 5.小说展示
|
||
|
||

|
||
|
||
#### 5.2 小说展示
|
||

|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|