# JavaCloud **Repository Path**: naka507/JavaCloud ## Basic Information - **Project Name**: JavaCloud - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-08-30 - **Last Updated**: 2021-09-05 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## Spring Cloud ---- 构建分布式系统不应该是复杂的,SpringCloud对常见的分布式系统模式提供了简单易用的编程模型,帮助开发者构建弹性、可靠、协调的应用程序。 SpringCloud是在SpringBoot的基础上构建的,使开发者可以轻松入门并快速提高工作效率。 SpringCloud为开发人员提供了快速构建分布式系统架构的工具,例如配置管理,服务发现,断路器,智能路由,微代理,控制总线,一次性令牌,全局锁定,领导选举,分布式会话,集群状态等。 ### Spring Cloud Config 集中配置管理工具,分布式系统中统一的外部配置管理,默认使用Git来存储配置,可以支持客户端配置的刷新及加密、解密操作。 ### Spring Cloud Netflix Netflix OSS 开源组件集成,包括Eureka、Hystrix、Ribbon、Feign、Zuul等核心组件。 * Eureka:服务治理组件,包括服务端的注册中心和客户端的服务发现机制; * Ribbon:负载均衡的服务调用组件,具有多种负载均衡调用策略; * Hystrix:服务容错组件,实现了断路器模式,为依赖服务的出错和延迟提供了容错能力; * Feign:基于Ribbon和Hystrix的声明式服务调用组件; * Zuul:API网关组件,对请求提供路由及过滤功能。 ### Spring Cloud Bus 用于传播集群状态变化的消息总线,使用轻量级消息代理链接分布式系统中的节点,可以用来动态刷新集群中的服务配置。 ### Spring Cloud Consul 基于Hashicorp Consul的服务治理组件。 ### Spring Cloud Security 安全工具包,对Zuul代理中的负载均衡OAuth2客户端及登录认证进行支持。 ### Spring Cloud Sleuth SpringCloud应用程序的分布式请求链路跟踪,支持使用Zipkin、HTrace和基于日志(例如ELK)的跟踪。 ### Spring Cloud Stream 轻量级事件驱动微服务框架,可以使用简单的声明式模型来发送及接收消息,主要实现为Apache Kafka及RabbitMQ。 ### Spring Cloud Task 用于快速构建短暂、有限数据处理任务的微服务框架,用于向应用中添加功能性和非功能性的特性。 ### Spring Cloud Zookeeper 基于Apache Zookeeper的服务治理组件。 ### Spring Cloud Gateway API网关组件,对请求提供路由及过滤功能。 ### Spring Cloud OpenFeign 基于Ribbon和Hystrix的声明式服务调用组件,可以动态创建基于Spring MVC注解的接口实现用于服务调用,在SpringCloud 2.0中已经取代Feign成为了一等公民。 ## Eureka简介 --- 在微服务架构中往往会有一个注册中心,每个微服务都会向注册中心去注册自己的地址及端口信息,注册中心维护着服务名称与服务实例的对应关系。 每个微服务都会定时从注册中心获取服务列表,同时汇报自己的运行情况,这样当有的服务需要调用其他服务时,就可以从自己获取到的服务列表中获取实例地址进行调用,Eureka实现了这套服务注册与发现机制。 常用配置: ```yaml eureka: client: #eureka客户端配置 register-with-eureka: true #是否将自己注册到eureka服务端上去 fetch-registry: true #是否获取eureka服务端上注册的服务列表 service-url: defaultZone: http://localhost:8001/eureka/ # 指定注册中心地址 enabled: true # 启用eureka客户端 registry-fetch-interval-seconds: 30 #定义去eureka服务端获取服务列表的时间间隔 instance: #eureka客户端实例配置 lease-renewal-interval-in-seconds: 30 #定义服务多久去注册中心续约 lease-expiration-duration-in-seconds: 90 #定义服务多久不去续约认为服务失效 metadata-map: zone: jiangsu #所在区域 hostname: localhost #服务主机名称 prefer-ip-address: false #是否优先使用ip来作为主机名 server: #eureka服务端配置 enable-self-preservation: false #关闭eureka服务端的保护机制 ``` 测试模块: * eureka-server -- eureka注册中心 * eureka-security -- 带登录认证的eureka注册中心 * eureka-client -- eureka客户端 ## Ribbon简介 --- 在微服务架构中,很多服务都会部署多个,其他服务去调用该服务的时候,如何保证负载均衡是个不得不去考虑的问题。 负载均衡可以增加系统的可用性和扩展性,当我们使用RestTemplate来调用其他服务时,Ribbon可以很方便的实现负载均衡功能。 RestTemplate是一个HTTP客户端,使用它我们可以方便的调用HTTP接口,支持GET、POST、PUT、DELETE等方法。 常用配置: ```yaml ribbon: ConnectTimeout: 1000 #服务请求连接超时时间(毫秒) ReadTimeout: 3000 #服务请求处理超时时间(毫秒) OkToRetryOnAllOperations: true #对超时请求启用重试机制 MaxAutoRetriesNextServer: 1 #切换重试实例的最大个数 MaxAutoRetries: 1 # 切换实例后重试最大次数 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #修改负载均衡算法 ``` 指定服务进行配置: ```yaml user-service: ribbon: ConnectTimeout: 1000 #服务请求连接超时时间(毫秒) ReadTimeout: 3000 #服务请求处理超时时间(毫秒) OkToRetryOnAllOperations: true #对超时请求启用重试机制 MaxAutoRetriesNextServer: 1 #切换重试实例的最大个数 MaxAutoRetries: 1 # 切换实例后重试最大次数 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #修改负载均衡算法 ``` 负载均衡策略: 所谓的负载均衡策略,就是当A服务调用B服务时,此时B服务有多个实例,这时A服务以何种方式来选择调用的B实例,ribbon可以选择以下几种负载均衡策略。 * com.netflix.loadbalancer.RandomRule:从提供服务的实例中以随机的方式; * com.netflix.loadbalancer.RoundRobinRule:以线性轮询的方式,就是维护一个计数器,从提供服务的实例中按顺序选取,第一次选第一个,第二次选第二个,以此类推,到最后一个以后再从头来过; * com.netflix.loadbalancer.RetryRule:在RoundRobinRule的基础上添加重试机制,即在指定的重试时间内,反复使用线性轮询策略来选择可用实例; * com.netflix.loadbalancer.WeightedResponseTimeRule:对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择; * com.netflix.loadbalancer.BestAvailableRule:选择并发较小的实例; * com.netflix.loadbalancer.AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例; * com.netflix.loadbalancer.ZoneAwareLoadBalancer:采用双重过滤,同时过滤不是同一区域的实例和故障实例,选择并发较小的实例。 测试模块: * eureka-server -- eureka注册中心 * user-service -- 提供User对象CRUD接口的服务 * ribbon-service -- ribbon服务调用测试服务 ## Hystrix 简介 --- 在微服务架构中,服务与服务之间通过远程调用的方式进行通信,一旦某个被调用的服务发生了故障,其依赖服务也会发生故障,此时就会发生故障的蔓延,最终导致系统瘫痪。 Hystrix实现了断路器模式,当某个服务发生故障时,通过断路器的监控,给调用方返回一个错误响应,而不是长时间的等待,这样就不会使得调用方由于长时间得不到响应而占用线程,从而防止故障的蔓延。 Hystrix具备服务降级、服务熔断、线程隔离、请求缓存、请求合并及服务监控等强大功能。 @HystrixCommand中的常用参数: * fallbackMethod:指定服务降级处理方法; * ignoreExceptions:忽略某些异常,不发生服务降级; * commandKey:命令名称,用于区分不同的命令; * groupKey:分组名称,Hystrix会根据不同的分组来统计命令的告警及仪表盘信息; * threadPoolKey:线程池名称,用于划分线程池。 Hystrix的常用配置: ```yaml hystrix: command: #用于控制HystrixCommand的行为 default: execution: isolation: strategy: THREAD #控制HystrixCommand的隔离策略,THREAD->线程池隔离策略(默认),SEMAPHORE->信号量隔离策略 thread: timeoutInMilliseconds: 1000 #配置HystrixCommand执行的超时时间,执行超过该时间会进行服务降级处理 interruptOnTimeout: true #配置HystrixCommand执行超时的时候是否要中断 interruptOnCancel: true #配置HystrixCommand执行被取消的时候是否要中断 timeout: enabled: true #配置HystrixCommand的执行是否启用超时时间 semaphore: maxConcurrentRequests: 10 #当使用信号量隔离策略时,用来控制并发量的大小,超过该并发量的请求会被拒绝 fallback: enabled: true #用于控制是否启用服务降级 circuitBreaker: #用于控制HystrixCircuitBreaker的行为 enabled: true #用于控制断路器是否跟踪健康状况以及熔断请求 requestVolumeThreshold: 20 #超过该请求数的请求会被拒绝 forceOpen: false #强制打开断路器,拒绝所有请求 forceClosed: false #强制关闭断路器,接收所有请求 requestCache: enabled: true #用于控制是否开启请求缓存 collapser: #用于控制HystrixCollapser的执行行为 default: maxRequestsInBatch: 100 #控制一次合并请求合并的最大请求数 timerDelayinMilliseconds: 10 #控制多少毫秒内的请求会被合并成一个 requestCache: enabled: true #控制合并请求是否开启缓存 threadpool: #用于控制HystrixCommand执行所在线程池的行为 default: coreSize: 10 #线程池的核心线程数 maximumSize: 10 #线程池的最大线程数,超过该线程数的请求会被拒绝 maxQueueSize: -1 #用于设置线程池的最大队列大小,-1采用SynchronousQueue,其他正数采用LinkedBlockingQueue queueSizeRejectionThreshold: 5 #用于设置线程池队列的拒绝阀值,由于LinkedBlockingQueue不能动态改版大小,使用时需要用该参数来控制线程数 ``` 实例配置只需要将全局配置中的default换成与之对应的key即可。 ```yaml hystrix: command: HystrixComandKey: #将default换成HystrixComrnandKey execution: isolation: strategy: THREAD collapser: HystrixCollapserKey: #将default换成HystrixCollapserKey maxRequestsInBatch: 100 threadpool: HystrixThreadPoolKey: #将default换成HystrixThreadPoolKey coreSize: 10 ``` 测试模块: * eureka-server -- eureka注册中心 * user-service -- 提供User对象CRUD接口的服务 * hystrix-service -- hystrix服务调用测试服务 ## Hystrix Dashboard:断路器执行监控 Hystrix提供了Hystrix Dashboard来实时监控HystrixCommand方法的执行情况。 Hystrix Dashboard可以有效地反映出每个Hystrix实例的运行情况,帮助我们快速发现系统中的问题,从而采取对应措施。 * 监控地址: `http://localhost:8401/actuator/hystrix.stream` * hystrix-service 配置 ```yaml management: endpoints: web: exposure: include: 'hystrix.stream' #暴露hystrix监控端点 ``` * Hystrix 集群实例监控 Turbine相关配置: ```yaml server: port: 8601 spring: application: name: turbine-service eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:8001/eureka/ turbine: app-config: hystrix-service #指定需要收集信息的服务名称 cluster-name-expression: new String('default') #指定服务所属集群 combine-host-port: true #以主机名和端口号来区分服务 ``` * 监控地址: `http://localhost:8601/turbine.stream` 测试模块: * eureka-server -- eureka注册中心 * user-service -- 提供User对象CRUD接口的服务 * hystrix-service -- hystrix服务调用测试服务 * turbine-service -- 聚合收集hystrix实例监控信息的服务 * hystrix-dashboard -- 展示hystrix实例监控信息的仪表盘 ## Feign简介 ---- Feign是声明式的服务调用工具,我们只需创建一个接口并用注解的方式来配置它,就可以实现对某个服务接口的调用,简化了直接使用RestTemplate来调用服务接口的开发量。 Feign具备可插拔的注解支持,同时支持Feign注解、JAX-RS注解及SpringMvc注解。 当使用Feign时,Spring Cloud集成了Ribbon和Eureka以提供负载均衡的服务调用及基于Hystrix的服务容错保护功能。 日志级别: * NONE:默认的,不显示任何日志; * BASIC:仅记录请求方法、URL、响应状态码及执行时间; * HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息; * FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。 Feign的常用配置: ```yaml feign: hystrix: enabled: true #在Feign中开启Hystrix compression: request: enabled: false #是否对请求进行GZIP压缩 mime-types: text/xml,application/xml,application/json #指定压缩的请求数据类型 min-request-size: 2048 #超过该大小的请求会被压缩 response: enabled: false #是否对响应进行GZIP压缩 logging: level: #修改日志级别 com.macro.cloud.service.UserService: debug ``` 测试模块: * eureka-server -- eureka注册中心 * user-service -- 提供User对象CRUD接口的服务 * feign-service -- feign服务调用测试服务 ## Zuul:API网关服务 API网关为微服务架构中的服务提供了统一的访问入口,客户端通过API网关访问相关服务。 API网关的定义类似于设计模式中的门面模式,它相当于整个微服务架构中的门面,所有客户端的访问都通过它来进行路由及过滤。 它实现了请求路由、负载均衡、校验过滤、服务容错、服务聚合等功能。 过滤器: 路由与过滤是Zuul的两大核心功能,路由功能负责将外部请求转发到具体的服务实例上去,是实现统一访问入口的基础,过滤功能负责对请求过程进行额外的处理,是请求校验过滤及服务聚合的基础。 * pre:在请求被路由到目标服务前执行,比如权限校验、打印日志等功能; * routing:在请求被路由到目标服务时执行,这是使用Apache HttpClient或Netflix Ribbon构建和发送原始HTTP请求的地方; * post:在请求被路由到目标服务后执行,比如给目标服务的响应添加头信息,收集统计数据等功能; * error:请求在其他阶段发生错误时执行。 常用配置: ```yaml zuul: routes: #给服务配置路由 user-service: path: /userService/** feign-service: path: /feignService/** ignored-services: user-service,feign-service #关闭默认路由配置 prefix: /proxy #给网关路由添加前缀 sensitive-headers: Cookie,Set-Cookie,Authorization #配置过滤敏感的请求头信息,设置为空就不会过滤 add-host-header: true #设置为true重定向是会添加host请求头 retryable: true # 关闭重试机制 PreLogFilter: pre: disable: false #控制是否启用过滤器 ``` 测试模块: * eureka-server -- eureka注册中心 * user-service -- 提供User对象CRUD接口的服务 * feign-service -- feign服务调用测试服务 * zuul-proxy -- zuul作为网关的测试服务 ## Config: 外部集中化配置管理 Spring Cloud Config 分为服务端和客户端两个部分。 服务端被称为分布式配置中心,它是个独立的应用,可以从配置仓库获取配置信息并提供给客户端使用。 客户端可以通过配置中心来获取配置信息,在启动时加载配置。 Spring Cloud Config 的配置中心默认采用Git来存储配置信息,所以天然就支持配置信息的版本管理,并且可以使用Git客户端来方便地管理和访问配置信息。 访问格式: ```yaml # 获取配置信息 /{label}/{application}-{profile} # 获取配置文件信息 /{label}/{application}-{profile}.yml ``` * application:代表应用名称,默认为配置文件中的spring.application.name,如果配置了spring.cloud.config.name,则为该名称; * label:代表分支名称,对应配置文件中的spring.cloud.config.label; * profile:代表环境名称,对应配置文件中的spring.cloud.config.profile。 搜索目录: ```yaml spring: cloud: config: server: git: search-paths: '{application}' ``` 刷新配置: ```yaml management: endpoints: web: exposure: include: 'refresh' ``` POST http://localhost:9001/actuator/refresh * 通过整合SpringSecurity来为配置中心添加安全认证 ```yaml server: port: 8905 spring: application: name: config-security cloud: config: server: git: uri: https://gitee.com/naka1205/springcloud-config.git username: naka1205 password: 123qwe clone-on-start: true #开启启动时直接从git获取配置 security: #配置用户名和密码 user: name: oyoula password: 123456 ``` 集群搭建: ```yaml spring: cloud: config: profile: dev #启用环境名称 label: dev #分支名称 name: config #配置文件名称 discovery: enabled: true service-id: config-server eureka: client: service-url: defaultZone: http://localhost:8001/eureka/ ``` 测试模块: * eureka-server -- eureka注册中心 * config-server -- 配置中心服务 * config-security -- 带安全认证的配置中心服务 * config-client -- 获取配置的客户端服务 ## Bus:消息总线 我们通常会使用消息代理来构建一个主题,然后把微服务架构中的所有服务都连接到这个主题上去。 当我们向该主题发送消息时,所有订阅该主题的服务都会收到消息并进行消费。 使用 Spring Cloud Bus 可以方便地构建起这套机制,所以 Spring Cloud Bus 又被称为消息总线。 Spring Cloud Bus 配合 Spring Cloud Config 使用可以实现配置的动态刷新。 相关配置; ```yaml server: port: 8904 spring: application: name: config-server cloud: config: server: git: uri: https://gitee.com/naka1205/springcloud-config.git username: naka1205 password: 123qwe clone-on-start: true # 开启启动时直接从git获取配置 rabbitmq: #rabbitmq相关配置 host: localhost port: 5672 username: guest password: guest eureka: client: service-url: defaultZone: http://localhost:8001/eureka/ management: endpoints: #暴露bus刷新配置的端点 web: exposure: include: 'bus-refresh' ``` 测试模块: * eureka-server -- eureka注册中心 * config-server -- 配置中心服务 * config-client -- 获取配置的客户端服务 ## Sleuth:分布式请求链路跟踪 随着我们的系统越来越庞大,各个服务间的调用关系也变得越来越复杂。 当客户端发起一个请求时,这个请求经过多个服务后,最终返回了结果,经过的每一个服务都有可能发生延迟或错误,从而导致请求失败。 这时候我们就需要请求链路跟踪工具来帮助我们,理清请求调用的服务链路,解决问题。 运行Zipkin: ```bash java -jar zipkin-server-2.12.9-exec.jar ``` 访问地址:`http://localhost:9411` * Elasticsearch存储跟踪信息 ```bash # STORAGE_TYPE:表示存储类型 ES_HOSTS:表示ES的访问地址 java -jar zipkin-server-2.12.9-exec.jar --STORAGE_TYPE=elasticsearch --ES_HOSTS=localhost:9200 ``` 测试模块: * eureka-server -- eureka注册中心 * user-service -- 提供User对象CRUD接口的服务 * ribbon-service -- ribbon服务调用测试服务 ## Gateway: API网关服务 Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能, 例如:熔断、限流、重试等。 特性: * 基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建; * 动态路由:能够匹配任何请求属性; * 可以对路由指定 Predicate(断言)和 Filter(过滤器); * 集成Hystrix的断路器功能; * 集成 Spring Cloud 服务发现功能; * 易于编写的 Predicate(断言)和 Filter(过滤器); * 请求限流功能; * 支持路径重写。 概念: * Route(路由):路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由; * Predicate(断言):指的是Java 8 的 Function Predicate。 输入类型是Spring框架中的ServerWebExchange。 这使开发人员可以匹配HTTP请求中的所有内容,例如请求头或请求参数。如果请求与断言相匹配,则进行路由; * Filter(过滤器):指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前后对请求进行修改。 配置文件: ```yaml server: port: 9201 service-url: user-service: http://localhost:8201 spring: cloud: gateway: routes: - id: path_route #路由的ID uri: ${service-url.user-service}/user/{id} #匹配后路由地址 predicates: # 断言,路径相匹配的进行路由 - Path=/user/{id} ``` * Hystrix 过滤器允许你将断路器功能添加到网关路由中,使你的服务免受级联故障的影响,并提供服务降级处理。 ```yaml spring: cloud: gateway: routes: - id: hystrix_route uri: http://localhost:8201 predicates: - Method=GET filters: - name: Hystrix args: name: fallbackcmd fallbackUri: forward:/fallback ``` * RequestRateLimiter 过滤器可以用于限流,使用RateLimiter实现来确定是否允许当前请求继续进行,如果请求太大默认会返回HTTP 429-太多请求状态。 1) 限流策略配置类,这里有两种策略一种是根据请求参数中的username进行限流,另一种是根据访问IP进行限流; 2) 使用Redis限流,所以需要添加Redis和RequestRateLimiter的配置,这里对所有的GET请求都进行了按IP来限流的操作; ```yaml server: port: 9201 spring: redis: host: localhost port: 6379 password: cloud: gateway: routes: - id: requestratelimiter_route uri: http://localhost:8201 filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 1 #每秒允许处理的请求数量 redis-rate-limiter.burstCapacity: 2 #每秒最大处理的请求数量 key-resolver: "#{@ipKeyResolver}" #限流策略,对应策略的Bean predicates: - Method=GET logging: level: org.springframework.cloud.gateway: debug ``` * 对路由请求进行重试的过滤器,可以根据路由请求返回的HTTP状态码来确定是否进行重试。 ```yaml pring: cloud: gateway: routes: - id: retry_route uri: http://localhost:8201 predicates: - Method=GET filters: - name: Retry args: retries: 1 #需要进行重试的次数 statuses: BAD_GATEWAY #返回哪个状态码需要进行重试,返回状态码为5XX进行重试 backoff: firstBackoff: 10ms maxBackoff: 50ms factor: 2 basedOnPreviousValue: false ``` * 结合注册中心 ```yaml server: port: 9201 spring: application: name: api-gateway cloud: gateway: discovery: locator: enabled: true #开启从注册中心动态创建路由的功能 lower-case-service-id: true #使用小写服务名,默认是大写 eureka: client: service-url: defaultZone: http://localhost:8001/eureka/ logging: level: org.springframework.cloud.gateway: debug ``` * 启用负载均衡 使用的uri协议为`lb`,并使用PrefixPath过滤器,会为所有GET请求路径添加/user路径 ```yaml server: port: 9201 spring: application: name: api-gateway cloud: gateway: routes: - id: prefixpath_route uri: lb://user-service #此处需要使用lb协议 predicates: - Method=GET filters: - PrefixPath=/user discovery: locator: enabled: true eureka: client: service-url: defaultZone: http://localhost:8001/eureka/ logging: level: org.springframework.cloud.gateway: debug ``` 测试模块: * eureka-server -- eureka注册中心 * user-service -- 提供User对象CRUD接口的服务 * api-gateway -- gateway作为网关的测试服务 ## Admin:微服务应用监控 SpringBoot应用可以通过Actuator来暴露应用运行过程中的各项指标,Spring Boot Admin通过这些指标来监控SpringBoot应用,然后通过图形化界面呈现出来。Spring Boot Admin不仅可以监控单体应用,还可以和Spring Cloud的注册中心相结合来监控微服务应用。 监控信息: 1) 监控应用运行过程中的概览信息; 2) 度量指标信息,比如JVM、Tomcat及进程信息; 3) 环境变量信息,比如系统属性、系统环境变量以及应用配置信息; 4) 查看所有创建的Bean信息; 5) 查看应用中的所有配置信息; 6) 查看应用运行日志信息; 7) 查看JVM信息; 8) 查看可以访问的Web端点; 9) 查看HTTP跟踪信息。 添加注册中心配置 admin-server ```yaml spring: application: name: admin-server server: port: 9301 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:8001/eureka/ ``` admin-client ```yaml spring: application: name: admin-client server: port: 9305 management: endpoints: web: exposure: include: '*' endpoint: health: show-details: always logging: file: admin-client.log #添加开启admin的日志监控 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:8001/eureka/ ``` 添加登录认证 ```yaml spring: application: name: admin-security security: # 配置登录用户名和密码 user: name: oyoula password: 123456 boot: # 不显示admin-security的监控信息 admin: discovery: ignored-services: ${spring.application.name} server: port: 9301 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:8001/eureka/ ``` 测试模块: * eureka-server -- eureka注册中心 * admin-server -- admin监控中心服务 * admin-client -- admin监控中心监控的应用服务 * admin-security -- 带登录认证的admin监控中心服务 ## Security:Oauth2使用 为构建安全的SpringBoot应用提供了一系列解决方案,结合Oauth2可以实现单点登录、令牌中继、令牌交换等功能,本文将对其结合Oauth2入门使用进行详细介绍。 名词解释: 1) Resource owner(资源拥有者):拥有该资源的最终用户,他有访问资源的账号密码; 2) Resource server(资源服务器):拥有受保护资源的服务器,如果请求包含正确的访问令牌,可以访问资源; 3) Client(客户端):访问资源的客户端,会使用访问令牌去获取资源服务器的资源,可以是浏览器、移动设备或者服务器; 4) Authorization server(认证服务器):用于认证用户的服务器,如果客户端认证通过,发放访问资源服务器的令牌。 授权模式: 1) Authorization Code(授权码模式):正宗的OAuth2的授权模式,客户端先将用户导向认证服务器,登录后获取授权码,然后进行授权,最后根据授权码获取访问令牌; 2) Implicit(简化模式):和授权码模式相比,取消了获取授权码的过程,直接获取访问令牌; 3) Resource Owner Password Credentials(密码模式):客户端直接向用户获取用户名和密码,之后向认证服务器获取访问令牌; 4) Client Credentials(客户端模式):客户端直接通过客户端认证(比如client_id和client_secret)从认证服务器获取访问令牌。 授权码模式: 1) 客户端将用户导向认证服务器; 2) 用户在认证服务器进行登录并授权; 3) 认证服务器返回授权码给客户端; 4) 客户端通过授权码和跳转地址向认证服务器获取访问令牌; 5) 认证服务器发放访问令牌(有需要带上刷新令牌)。 密码模式: 1) 客户端从用户获取用户名和密码; 2) 客户端通过用户的用户名和密码访问认证服务器; 3) 认证服务器返回访问令牌(有需要带上刷新令牌)。 ```yaml server: port: 9401 spring: application: name: oauth2-service ``` 授权码模式使用: * 在浏览器访问该地址进行[登录授权](http://localhost:9401/oauth/authorize?response_type=code&client_id=admin&redirect_uri=http://www.oyoula.com&scope=all&state=normal) * 登录后进行授权操作 `Approve` * 之后会浏览器会带着授权码跳转到我们指定的路径: `http://www.oyoula.com/?code=SdhPF2&state=normal` * 使用授权码请求该地址获取访问令牌:`http://localhost:9401/oauth/token` * 使用Basic认证通过client_id和client_secret构造一个Authorization头信息 ![Basic认证](images/oauth2-server-basic.png) * 在body中添加以下参数信息,通过POST请求获取访问令牌 ![访问令牌](images/oauth2-server-authorization_code.png) ```json { "grant_type": "authorization_code", "client_id": "admin", "redirect_uri": "http://www.oyoula.com", "scope": "all", "code": "SdhPF2" } ``` * 在请求头中添加访问令牌,访问需要登录认证的接口进行测试,发现已经可以成功访问:`http://localhost:9401/user/getCurrentUser` ![授权访问](images/oauth2-server-authorization.png) 密码模式使用: * 使用密码请求该地址获取访问令牌:`http://localhost:9401/oauth/token` ![Basic认证](images/oauth2-server-basic.png) * 在body中添加以下参数信息,通过POST请求获取访问令牌; ![密码登录](images/oauth2-server-password.png) ```json { "grant_type": "password", "username": "oyoula", "password": "123456", "scope": "all" } ``` 结合JWT使用: JWT是JSON WEB TOKEN的缩写,它是基于 RFC 7519 标准定义的一种可以安全传输的的JSON对象,由于使用了数字签名,所以是可信任和安全的。 * JWT token的格式:header.payload.signature; * header中用于存放签名的生成算法; ```json { "alg": "HS256", "typ": "JWT" } ``` * payload中用于存放数据,比如过期时间、用户名、用户所拥有的权限等; ```json { "exp": 1572682831, "user_name": "macro", "authorities": [ "admin" ], "jti": "c1a0645a-28b5-4468-b4c7-9623131853af", "client_id": "admin", "scope": [ "all" ] } ``` * signature为以header和payload生成的签名,一旦header和payload被篡改,验证将失败。 * Redis存储令牌的配置: ```java @Configuration public class RedisTokenStoreConfig { @Autowired private RedisConnectionFactory redisConnectionFactory; @Bean public TokenStore redisTokenStore (){ return new RedisTokenStore(redisConnectionFactory); } } ``` * 使用密码模式来获取令牌,访问如下地址:`http://localhost:9401/oauth/token` ![密码登录](images/oauth2-server-password.png) * 令牌已经被存储到Redis中 ![密码登录](images/oauth2-jwt-redis.png) 使用JWT存储令牌: * 添加使用JWT存储令牌的配置: ```java @Configuration public class JwtTokenStoreConfig { @Bean public TokenStore jwtTokenStore() { return new JwtTokenStore(jwtAccessTokenConverter()); } @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter(); accessTokenConverter.setSigningKey("oyoula_key");//配置JWT使用的秘钥 return accessTokenConverter; } } ``` * 在认证服务器配置中指定令牌的存储策略为JWT: ```java @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { //...省略代码... @Autowired private JwtAccessTokenConverter jwtAccessTokenConverter; @Autowired private JwtTokenEnhancer jwtTokenEnhancer; /** * 使用密码模式需要配置 */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { endpoints.authenticationManager(authenticationManager) .userDetailsService(userService) .tokenStore(tokenStore) //配置令牌存储策略 .accessTokenConverter(jwtAccessTokenConverter); } //...省略代码... } ``` * 使用密码模式来获取令牌,访问如下地址:`http://localhost:9401/oauth/token` * 获取到的令牌已经变成了JWT令牌,将access_token解析: ```json { "exp": 1630734832, "user_name": "oyoula", "authorities": [ "admin" ], "jti": "3700046b-f900-4143-98bc-cf3da78e36f0", "client_id": "admin", "scope": [ "all" ] } ``` 扩展JWT: * 在JWT中扩展一个key为enhance,value为enhance info的数据。 * 继承TokenEnhancer实现一个JWT内容增强器: ```java public class JwtTokenEnhancer implements TokenEnhancer { @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { Map info = new HashMap<>(); info.put("enhance", "enhance info"); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info); return accessToken; } } ``` * 创建一个JwtTokenEnhancer实例: ```java @Configuration public class JwtTokenStoreConfig { //...省略代码... @Bean public JwtTokenEnhancer jwtTokenEnhancer() { return new JwtTokenEnhancer(); } } ``` * 使用密码模式来获取令牌,之后对令牌进行解析: ```json { "user_name": "oyoula", "scope": [ "all" ], "exp": 1630735861, "authorities": [ "admin" ], "jti": "f4289f58-53e8-4643-a1d9-a0241fa8c33c", "client_id": "admin", "enhance": "enhance info" } ``` Java解析JWT: * 在UserController类中使用jjwt工具类来解析Authorization头中存储的JWT内容 * 将令牌放入Authorization头中,访问:`http://localhost:9401/user/getCurrentUser` ![授权访问](images/oauth2-jwt-bearer.png) 刷新令牌: * 令牌失效,可以使用刷新令牌通过`refresh_token`的授权模式再次获取`access_token` * 认证服务器配置添加`refresh_token`的授权模式 ```java @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("admin") .secret(passwordEncoder.encode("123qwe")) .accessTokenValiditySeconds(3600) .refreshTokenValiditySeconds(864000) .redirectUris("http://www.oyoula.com") .autoApprove(true) //自动授权配置 .scopes("all") .authorizedGrantTypes("authorization_code","password","refresh_token"); //添加授权模式 } } ``` * 使用密码模式获取新的令牌,访问: `http://localhost:9401/oauth/token` ![刷新令牌](images/oauth2-jwt-refresh_token.png) ```json { "grant_type": "refresh_token", "refresh_token": "..." } ``` 单点登录: 单点登录(Single Sign On)指的是当有多个系统需要登录时,用户只需登录一个系统,就可以访问其他需要登录的系统而无需登录。 ```yaml server: port: 9501 servlet: session: cookie: name: OAUTH2-CLIENT-SESSIONID #防止Cookie冲突,冲突会导致登录验证不通过 oauth2-server-url: http://localhost:9401 spring: application: name: oauth2-client security: oauth2: #与oauth2-server对应的配置 client: client-id: admin client-secret: 123qwe user-authorization-uri: ${oauth2-server-url}/oauth/authorize access-token-uri: ${oauth2-server-url}/oauth/token resource: jwt: key-uri: ${oauth2-server-url}/oauth/token_key ``` * 修改认证服务器配置,绑定的跳转 `http://localhost:9501/login`,并添加获取秘钥时的身份认证。 ```java @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { //...省略代码... @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("admin") .secret(passwordEncoder.encode("123qwe")) .accessTokenValiditySeconds(3600) .refreshTokenValiditySeconds(864000) .redirectUris("http://localhost:9501/login") //单点登录时配置 .autoApprove(true) //自动授权配置 .scopes("all") .authorizedGrantTypes("authorization_code","password","refresh_token"); } @Override public void configure(AuthorizationServerSecurityConfigurer security) { security.tokenKeyAccess("isAuthenticated()"); // 获取密钥需要身份认证,使用单点登录时必须配置 } } ``` * 访问客户端需要授权的接口 `http://localhost:9501/user/getCurrentUser` * 跳转到授权服务的登录界面,输入账号密码登录 * 进行登录操作后跳转到授权页面 * 授权后会跳转到原来需要权限的接口地址,展示登录用户信息 * 如果需要跳过授权操作进行自动授权可以添加autoApprove(true)配置 添加权限校验: * 添加配置开启基于方法的权限校验: ```java @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) @Order(101) public class SecurityConfig extends WebSecurityConfigurerAdapter { } ``` * UserController中添加需要admin权限的接口 ```java @RestController @RequestMapping("/user") public class UserController { @PreAuthorize("hasAuthority('admin')") @GetMapping("/auth/admin") public Object adminAuth() { return "Has admin auth!"; } } ``` * 访问需要admin权限的接口: `http://localhost:9501/user/auth/admin` 测试模块: * oauth2-server -- Oauth2认证测试服务 * oauth2-jwt -- Oauth2结合JWT认证测试服务