1.概念:Spring cloud 是一系列框架的有序集合。它利用 spring boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 spring boot 的开发风格做到一键启动和部署。
2.优缺点(主要针对微服务)
(1)优点
①每个服务足够内聚,足够小,代码容易理解、开发效率提高;
②服务之间可以独立部署,微服务架构让持续部署成为可能;
③精细度业务控制,每个服务可以各自进行负载均衡扩展和数据库扩展,而且,每个服务可以根据自己的需要部署到合适的硬件服务器上;
④容易扩大开发团队,可以针对每个服务组件开发团队;
⑤提高容错性,一个服务的内存泄露并不会让整个系统瘫痪;
⑥每个服务可以用不同的技术开发,系统不会被长期限制在某个技术栈上。
(2)缺点
①开发人员要处理分布式系统的复杂性:
设计服务之间的通信机制,需要考虑分布式事务等问题;
涉及多个服务直接的自动化测试;
服务管理的复杂性(PS:现在docker的出现适合解决这个问题);
②对于业务数据和处理能力不是很明确的创业公司,不适合微服务架构模式,这时候最重要的是快速开发、快速部署、快速试错。
3.应用场景
适合业务复杂度较大、并发量较高的场景
4.SpringCloud与Dubbo对比
(1)架构完整度上,Dubbo只有服务注册/服务治理(zookeeper)两个模块,SpringCloud已经有二十多个模块,而且还在增加;
(2)SpringCloud的社区活跃度高;但Dubbo有高质量的中文文档
(3)Dubbo服务间的通讯采用的RPC,SpringCloud则是Http的Rset,RPC对业务接口有强依赖性,需要通讯双方有一样的接口,REST更为轻量化,只通过一个约定进行规范,不存在代码间的耦合。
SpringCloud的子项目一直在持续更新,这里我把SpringCloud中文网首页的直接放上来了。
详情参照:https://www.springcloud.cc/
1.Spring Cloud Config(Spring)
配置管理工具包,让你可以把配置放到远程服务器,集中化管理集群配置,目前支持本地存储、Git以及Subversion。
2.Spring Cloud Bus(Spring)
事件、消息总线,用于在集群(例如,配置变化事件)中传播状态变化,可与Spring Cloud Config联合实现热部署。
3.Eureka(Netflix)
云端服务发现,一个基于 REST 的服务,用于定位服务,以实现云端中间层服务发现和故障转移。
4.Hystrix(Netflix)
熔断器,容错管理工具,旨在通过熔断机制控制服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。
5.Zuul(Netflix)
Zuul 是在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门。
6.Archaius(Netflix)
配置管理API,包含一系列配置管理API,提供动态类型化属性、线程安全配置操作、轮询框架、回调机制等功能。
7.Consul(HashiCorp)
封装了Consul操作,consul是一个服务发现与配置工具,与Docker容器可以无缝集成。
8.Spring Cloud for Cloud Foundry(Pivotal)
通过Oauth2协议绑定服务到CloudFoundry,CloudFoundry是VMware推出的开源PaaS云平台。
9.Spring Cloud Sleuth(Spring)
日志收集工具包,封装了Dapper和log-based追踪以及Zipkin和HTrace操作,为SpringCloud应用实现了一种分布式追踪解决方案。
10.Spring Cloud Data Flow(Pivotal)
大数据操作工具,作为Spring XD的替代产品,它是一个混合计算模型,结合了流数据与批量数据的处理方式。
11.Spring Cloud Security(Spring)
基于spring security的安全工具包,为你的应用程序添加安全控制。
12.Spring Cloud Zookeeper(Spring)
操作Zookeeper的工具包,用于使用zookeeper方式的服务发现和配置管理。
13.Spring Cloud Stream(Spring)
数据流操作开发包,封装了与Redis,Rabbit、Kafka等发送接收消息。
14.Spring Cloud CLI(Spring)
基于 Spring Boot CLI,可以让你以命令行方式快速建立云组件。
15.Ribbon(Netflix)
提供云端负载均衡,有多种负载均衡策略可供选择,可配合服务发现和断路器使用。
16.Turbine(Netflix)
Turbine是聚合服务器发送事件流数据的一个工具,用来监控集群下hystrix的metrics情况。
17.Feign(OpenFeign)
Feign是一种声明式、模板化的HTTP客户端。
18.Spring Cloud Task(Spring)
提供云端计划任务管理、任务调度。
19.Spring Cloud Connectors(Spring)
便于云端应用程序在各种PaaS平台连接到后端,如:数据库和消息代理服务。
20.Spring Cloud Cluster(Spring)
提供Leadership选举,如:Zookeeper, Redis, Hazelcast, Consul等常见状态模式的抽象和实现。
21.Spring Cloud Starters(Pivotal)
Spring Boot式的启动项目,为Spring Cloud提供开箱即用的依赖管理。
这里我把部分代码传到github上了:https://github.com/tttalkR9/cloud/tree/cloud
(1)首先创建一个File-New-Project选择Spring Initializr,JDK你们可以选择1.8的
(2)修改项目名称,我这里叫springcloud
(3)选择一下基础依赖,这里选择web-spring web,右上角可以修改springboot版本。
(4)然后一个springboot基础的父项目就创建成功了
(5)右击项目,New-Module,同样选择Spring Initializr,项目名称为user,跟刚刚不同的是,我们这里不选择web了,可以直接从父项目获得web的依赖
(6)修改父项目的pom文件,添加packaging的方式和modules
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.6.3version>
<relativePath/>
parent>
<groupId>com.examplegroupId>
<artifactId>springcloudartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>springcloudname>
<description>Demo project for Spring Bootdescription>
<packaging>pompackaging>
<modules>
<module>usermodule>
modules>
<properties>
<java.version>11java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
(7)修改子项目的pom文件
将以下父项目的配置放到子项目的parent标签中,并将子项目中多余的依赖删掉
<groupId>com.examplegroupId>
<artifactId>springcloudartifactId>
<version>0.0.1-SNAPSHOTversion>
最后子项目的pom.xml如下:
<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.0modelVersion>
<parent>
<groupId>com.examplegroupId>
<artifactId>springcloudartifactId>
<version>0.0.1-SNAPSHOTversion>
parent>
<groupId>com.examplegroupId>
<artifactId>userartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>username>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>11java.version>
properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
(8)添加controller文件,将properties文件替换成yml文件,项目结构如下:
(9)UserController.java和application.yml代码如下:
UserController.java
package com.example.user.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalTime;
import java.util.List;
@Controller
public class UserController {
@RequestMapping(value="/test")
@ResponseBody
public String test(){
return "test111";
}
}
application.yml
server:
port: 8030
spring:
application:
name: user-test
(10)右击启动UserApplication,进入http://localhost:8030/test,看到test111则验证成功
①Feign:声明式 REST 客户端–Feign 通过使用 JAX-RS(就是一种使用注解来实现 RESTful 的技术)或 SpringMVC 注解的装饰方式,生成接口的动态实现。旨在自动封装服务调用客户端,减少代码开发量
微服务中调用接口常见方式:
Ⅰ.通过http方式进行直接调用:即通过http+port的方式调用,可以使用okhttp、httpclient等进行接口的调用与解析,需要关注接口数据流、接口的数据转换等。
这种方式的缺点:接口调用有单点故障,对接口调用量大的情况没有熔断机制保护系统,防止雪崩效应。
Ⅱ.后端架设nginx服务做负载均衡。接口部署多路,此方式可避免单点故障但是要实现熔断机制等比较复杂,如:可以使用nginx+lua实现,或在代码层面实现限流机制算法。
Ⅲ.dubbo等其他服务治理架构方式请自行查阅资料。
Ⅳ.使用springcloud 家族的feign来实现接口调用。此方式可以解决单点故障和限流熔断,执行服务降级。
②Ribbon:实现负载均衡,从一个服务的多台机器中选择一台。
Nginx与Ribbon:
1)Nginx 是客户端所有请求统一交给 nginx,由 nginx 进行实现负载均衡请求转发,属于服务器端负载均衡。既请求由 nginx 服务器端进行转发。
2)Ribbon 是从 eureka 注册中心服务器端上获取服务注册信息列表,缓存到本地,然后在本地实现轮询负载均衡策略。
3)Nginx 适合于服务器端实现负载均衡 比如 Tomcat ,Ribbon 适合与在微服务中 RPC 远程调用实现本地服务负载均衡,比如 Dubbo、SpringCloud 中都是采用本地负载均衡。
Ribbon算法:实现Irule接口(choose方法),自带七种算法。
(1)RoundRobinRule:轮询;(默认)
(2)RandomRule:随机;
(3)AvailabilityFilteringRule:会先过滤掉由于多次访问故障而处于断路器状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问;
(4)WeightedResponseTimeRule:根据平均响应时间计算所有服务的权重,响应时间越快的服务权重越大被选中的概率越大。刚启动时如果统计信息不足,则使用RoundRobinRule(轮询)策略,等统计信息足够,会切换到WeightedResponseTimeRule;
(5)RetryRule:先按照RoundRobinRule(轮询)策略获取服务,如果获取服务失败则在指定时间内进行重试,获取可用的服务;
(6)BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务;
(7)ZoneAvoidanceRule:复合判断Server所在区域的性能和Server的可用性选择服务器。
③OpenFeign:Feign的强化版,内置了ribbon,简化了Eureka和Ribbon的使用。
后面nacos的使用中会整合Feign+hystrix,有完整的代码实例,这里就只做介绍不贴代码
①首先,在pom.xml中引入openfeign和nacos的依赖
②在nacos注册中心端提供一个可以调用的接口,并在application中添加注释@EnableDiscoveryClient
③在nacosclient客户端入口建立一个接口,@FeignClient括号里填写的是注册中心的application-name(application.yml中配置的),然后在controller中调用这个客户端。application中添加注解@EnableDiscoveryClient和@EnableFeignClients
介绍Hystrix前,我们先了解一下它产生的背景–服务雪崩。
服务雪崩:一个服务失败(调用时间过长或失败),导致整条链路的服务都失败的情形,我们称之为服务雪崩。
解决服务雪崩的方式有下面几种:
1)应用扩容:加机器、升级硬件
2)流量控制: 限流(超出限定流量,返回让用户稍后再试)
3)缓存:将用户访问量大的数据放入缓存中,减少数据库访问。
4)服务降级:
①超时:当下游的服务响应过慢,下游服务主动停掉一些不太重要的业务,释放出服务器资源,增加响应速度
②程序运行异常:当下游的服务因为某种原因不可用,上游主动调用本地的一些降级逻辑,避免卡顿,迅速返回给用户
5) 服务熔断:不调用该失败的服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用
而Hystrix的出现为微服务提供了服务降级与熔断的方案。
Hystrix 工作流程:
https://github.com/Netflix/Hystrix/wiki/How-it-Works
hystrix熔断触发:
当失败的调用到一定阀值,时间窗 5 秒内 20 次调用失败(默认),就会启动熔断机制。
此时断路器将会开启,当开启的时候,所有请求都不会进行转发
hystrix熔断恢复:
一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启。
有两种方式实现,一个是通过Feign内置的hystrix实现:
1、导入依赖spring-cloud-starter-hystrix
2、启动类开启hystrix支持@EnableHystrix
3、配置yml文件feign.hystrix.enabled=true
4、实现FeignClient接口或FallbackFactory接口
另一种是通过注解@HystrixCommand完成,属于方法级别的降级,后面nacos的使用有代码实例:
1、导入依赖spring-cloud-starter-hystrix
2、启动类开启hystrix支持@EnableHystrix
3、在方法上添加注解@HystrixCommand并配置降级后的方法
①Eureka常用于SpringCloud项目,Zookeeper常用于dubbo
②ZooKeeper保证的是CP(一致、分区容错但不保证可用),Eureka保证的是AP(可用、分区容错但不保证一致)
ZooKeeper在选举期间注册服务瘫痪,虽然服务最终会恢复,但是选举期间不可用的
Eureka各个节点是平等关系,只要有一台Eureka就可以保证服务可用,而查询到的数据并不是最新的
③ZooKeeper有Leader和Follower角色,Eureka各个节点平等
④ZooKeeper采用过半数存活原则,Eureka采用自我保护机制解决分区问题
Eureka 自我保护机制:通过在Eureka Server配置如下参数,可启动保护机制。
eureka.server.enable-self-preservation=true
它的原理是,当Eureka Server节点在短时间内丢失过多的客户端时(可能发送了网络故障),那么这个节点将进入自我保护模式,不再注销任何微服务,当网络故障回复后,该节点会自动退出自我保护模式。
参考:https://www.cnblogs.com/huangwentian/p/14504473.html
总的来说,目前Nacos的功能更强大一些,Nacos = Spring Cloud Eureka + Spring Cloud Config +Spring Cloud Bus,不需要为配置中心新增集群,不需要MQ实现就能实现配置的中心动态刷新,如果考虑引入阿里巴巴生态,是一个不错的选择;
但是Eureka起步比较早,属于Spring Cloud原生全家桶,短期内更加稳定一些。
Spring Cloud Config:是一种用来动态获取Git、SVN、本地的配置文件的一种工具,为分布式系统中的外部配置提供服务器和客户端支持。官方使用说明文档:https://www.springcloud.cc/spring-cloud-config.html
Springcloud Bus:是springcloud中消息总线。用来将分布式系统的节点与轻量级消息系统链接起来,在微服务系统中实现远端配置更新时通过广播形式通知所有客户端刷新配置信息,避免手动重启服务的工作。常结合RabbitMq和Kafka使用。官方文档参考:https://www.springcloud.cc/spring-cloud-bus.html
Ⅰ.新建子模块eureka,这里新建的是注册中心,后面还需要个客户端去调用,建立方法在上面介绍过了
Ⅱ.在父项目的pom文件添加相关依赖,主要是springcloud的依赖和eureka的依赖等,注意版本。修改后文件如下
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.6.3version>
<relativePath/>
parent>
<groupId>com.examplegroupId>
<artifactId>springcloudartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>springcloudname>
<description>Demo project for Spring Bootdescription>
<packaging>pompackaging>
<modules>
<module>usermodule>
<module>eurekamodule>
modules>
<properties>
<java.version>11java.version>
<spring-cloud.version>2021.0.1spring-cloud.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
<version>2.2.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
Ⅲ.在子项目中新添加application.yml和修改EurekaApplication、pom.xml,项目结构如下
pom.xml:主要是修改parent标签
<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.0modelVersion>
<parent>
<groupId>com.examplegroupId>
<artifactId>springcloudartifactId>
<version>0.0.1-SNAPSHOTversion>
parent>
<groupId>com.examplegroupId>
<artifactId>eurekaartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>eurekaname>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>11java.version>
properties>
<dependencies>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
application.yml:
server:
#服务端口
port: 8899
eureka:
client:
service-url:
# 需要进行消息注册的请求地址,不需要用户名密码验证
#defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
#其他服务想注册到Eureka上需要添加用户名和密码进行认证
defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@${eureka.instance.hostname}:${server.port}/eureka
#表示当前服务不会注册到服务中心
register-with-eureka: false
#表示当前服务不会拉取其他服务
fetch-registry: false
instance:
hostname: localhost
spring:
#登录Eureka所需的用户名和密码
security:
user:
name: admin
password: admin
EurekaApplication:添加@EnableEurekaServer注释,并重新配置spring security
package com.example.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@SpringBootApplication
//eureka的客户端配置
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
/*
* Eureka会自动配置CSRF防御机制,spring security认为post,put and delete http methods 都是有风险的,
* 如果这些method发送过程中没有带上CSRF token,会被直接拦截并返回403 forbidden
* */
@EnableWebSecurity
static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().ignoringAntMatchers("/eureka/**");
super.configure(http);
}
}
}
Ⅳ.访问Eureka注册中心:http://localhost:8899/,输入admin/admin,可以看到下面的界面则搭建成功了,application为空代表还没有应用注册了这个服务
Ⅴ.搭建Eureka客户端eurekaClient,新建module同上,新增EurekaclientApplication .java和application.yml、pom.xml
pom.xml:
<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.0modelVersion>
<parent>
<groupId>com.examplegroupId>
<artifactId>springcloudartifactId>
<version>0.0.1-SNAPSHOTversion>
parent>
<groupId>com.examplegroupId>
<artifactId>eurekaclientartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>eurekaclientname>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>11java.version>
properties>
<dependencies>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
application.yml:
server:
# 该服务的端口号
port: 8900
eureka:
client:
service-url:
#此服务注册的地址,需要跟eureka模块中地址一致,注意这里用server.port是8900不是8899不要用变量
#defaultZone: http://${eureka.instance.hostname}:8899/eureka
defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@localhost:8899/eureka
instance:
hostname: localhost
spring:
application:
#注册到注册中心的名称,注意不能使用“_”
name: eureka-client-8900
# 指定用户名和密码,其他服务想注册到Eureka上需要添加用户名和密码进行认证
security:
user:
name: admin
password: admin
cloud:
inetutils:
timeout-seconds: 10
EurekaclientApplication .java
package com.example.eurekaclient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@SpringBootApplication
//eureka的客户端配置
@EnableEurekaServer
// 开启EnableWebSecurity组件
@EnableWebSecurity
public class EurekaclientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaclientApplication.class, args);
}
}
Ⅵ.启动,http://localhost:8900/,登录admin/admin,发现服务注册成功了,如果有报错,查看后面的报错解决模块。
Ⅰ.下载源码压缩包,nacos-server-XXX.zip 或者 nacos-server-XXX.tar.gz,我这里是windows版本,下了个nacos-server-1.4.3.zip。
下载地址:https://github.com/alibaba/nacos/releases
Ⅱ.启动,解压后找到startup.cmd。修改startup.cmd(由于我们没配置集群,需要修改为单机启动),将set MODE = "cluster"替换为
rem set MODE="cluster"
set MODE="standalone"
启动成功后访问:http://localhost:8848/nacos,用户名密码:nacos/nacosⅢ.同样的我们也需要创建nacos的注册中心和客户端。我这里直接在刚刚创建好的父子项目中添加模块nacos作为注册中心,项目结构如下
Ⅳ.相关文件代码如下,注意要把之前Eureka的依赖都注释掉,会和nacos的依赖有冲突
nacos注册中心的pom.xml:引入父模块
<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.0modelVersion>
<parent>
<groupId>com.examplegroupId>
<artifactId>springcloudartifactId>
<version>0.0.1-SNAPSHOTversion>
parent>
<groupId>com.examplegroupId>
<artifactId>nacosartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>nacosname>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>11java.version>
properties>
<dependencies>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
父项目的pom.xml:添加nacos的依赖,同时注释掉eureka依赖,服务发现时两者jar包会冲突;同时添加openfeign依赖及配套的loadbalancer,同时openfeign内置了负载均衡的ribbon,所以还需要把nacos中的ribbon注释掉;添加hystrix依赖
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.6.3version>
<relativePath/>
parent>
<groupId>com.examplegroupId>
<artifactId>springcloudartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>springcloudname>
<description>Demo project for Spring Bootdescription>
<packaging>pompackaging>
<modules>
<module>usermodule>
<module>eurekamodule>
modules>
<properties>
<java.version>11java.version>
<spring-cloud.version>2021.0.1spring-cloud.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
<version>2.2.6.RELEASEversion>
<exclusions>
<exclusion>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-ribbonartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
<version>2.2.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-loadbalancerartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
<version>2.2.6.RELEASEversion>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
application.yml
server:
port: 8070
spring:
application:
name: nacos-provider
cloud:
# nacos服务地址
nacos:
server-addr: 127.0.0.1:8848
NacosApplication.java::主要是添加@EnableDiscoveryClient、@EnableHystrix注释
package com.example.nacos;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
@SpringBootApplication
//实现服务注册
@EnableDiscoveryClient
//开启hystrix支持
@EnableHystrix
public class NacosApplication {
public static void main(String[] args) {
SpringApplication.run(NacosApplication.class, args);
}
}
NacosController.java
package com.example.nacos.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalTime;
@RestController
public class NacosController {
@GetMapping("invoke")
//配置降级方法
@HystrixCommand(fallbackMethod = "timeOutInvoke",commandProperties = {
//设置服务调用超时10秒时触发服务降级
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "10000")
})
public String invoke() throws InterruptedException {
//模拟服务超时
//Thread.sleep(10000);
return LocalTime.now() + ":invoke";
}
public String timeOutInvoke() {
return "系统繁忙,请稍后再试";
}
}
Ⅴ.启动后,再去网址http://localhost:8848/nacos上查看服务管理-服务列表模块,发现注册中心成功了
Ⅵ.同样我们需要一个客户端去调用,新建一个nacosclient模块,项目结构如下:
Ⅶ.相关文件代码
pom.xml:引入父模块依赖
<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.0modelVersion>
<parent>
<groupId>com.examplegroupId>
<artifactId>springcloudartifactId>
<version>0.0.1-SNAPSHOTversion>
parent>
<groupId>com.examplegroupId>
<artifactId>nacosclientartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>nacosclientname>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>11java.version>
properties>
<dependencies>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
application.yml
server:
port: 8071
spring:
application:
name: nacos-consumer
cloud:
# nacos服务地址
nacos:
server-addr: 127.0.0.1:8848
NacosClientApplication.java:主要是添加@EnableDiscoveryClient、@EnableFeignClients注释
package com.example.nacosclient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
//实现服务注册
@EnableDiscoveryClient
//使用feign客户端
@EnableFeignClients
public class NacosclientApplication {
public static void main(String[] args) {
SpringApplication.run(NacosclientApplication.class, args);
}
}
NacosClientController.java:添加了FeignClient(后面会介绍这个组件),提供程序入口
package com.example.nacosclient.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalTime;
@RestController
public class NacosClientController {
@Autowired
private ProviderFeign providerFeign;
@GetMapping("/test")
public String test() {
return providerFeign.invoke();
}
}
@FeignClient("nacos-provider")
interface ProviderFeign {
@GetMapping("invoke")
String invoke();
}
Ⅷ.启动后再次查看,服务创建成功了,进入http://localhost:8071/test,发现有输出,则调用成功
Ⅸ.修改NacosController,将"Thread.sleep(10000);"的注释放开
package com.example.nacos.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalTime;
@RestController
public class NacosController {
@GetMapping("invoke")
//配置降级方法
@HystrixCommand(fallbackMethod = "timeOutInvoke",commandProperties = {
//设置服务调用超时10秒时触发服务降级
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "10000")
})
public String invoke() throws InterruptedException {
//模拟服务超时
Thread.sleep(10000);
return LocalTime.now() + ":invoke";
}
public String timeOutInvoke() {
return "系统繁忙,请稍后再试";
}
}
重启nacos和nacosclient,再次访问,发现服务降级生效了
①nacos集群数据库配置(用于数据持久化)–目前版本的依赖mysql
1)为保证数据一致性,集群需使用公共数据库来保存服务管理中的数据,目前支持mysql数据库。
mysql搭建可以参考这篇文章:https://blog.csdn.net/tttalk/article/details/121929642?spm=1001.2014.3001.5501
2)在解压后的文件的\nacos\conf\目录下找到mysql数据库建表文件nacos-mysql.sql,在mysql中执行以创建一系列配置表。进入mysql后选择数据库后执行source /root/mysql/nacos-mysql.sql
可以看到这些表创建成功了
3)在\nacos\conf目录下的application.properties文件最下方添加数据库连接配置,注意更改ip密码啥的
### mysql配置
spring.datasource.platform = mysql
db.num = 1
db.url.0 = jdbc:mysql://192.168.124.128:3306/mysql?characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true
db.user=root
db.password=Ppp11111111!
②集群节点配置
1)将\nacos\conf\目录下的cluster.conf.example文件拷贝一份,重命名为cluster.conf,打开添加如下配置,注意!这里一定要写真实的IP(cmd ipconfig查看),不能写localhost,127.0.0.1,不然集群启动会找不到别的实例,导致无法选举出leader
192.168.124.1:8841
192.168.124.1:8842
192.168.124.1:8843
2)修改启动文件startup.cmd,还记得我们之前修改成了单机么,改回来即可,rem是注释的意思。将set MODE="standalone"注释掉,并把set MODE="cluster"的注释取消。
set MODE="cluster"
rem set MODE="standalone"
3)将调整后的nacos文件拷贝三份
将拷贝后的文件分别命名为 nacos8841、nacos8842、nacos8843
4)分别在\nacos\conf目录下的application.properties文件中修改对应的端口号server.port=8848为:8841、8842、8843(nacos8841的application.properties端口改为8841,以次类推)
③Nginx配置
1)Nginx下载:http://nginx.org/en/download.html,选择stable版本
2)解压后修改\nginx-1.20.2\conf目录的nginx.conf文件。
找到server和location模块,在server前添加upstream,修改location,添加下面的代码:
#集群的3个节点
upstream nacos {
server 127.0.0.1:8841 weight=2;
server 127.0.0.1:8842 weight=2;
server 127.0.0.1:8843 weight=2;
}
location / {
# root html;
# index index.html index.htm;
#代理
proxy_pass http://nacos/;
}
添加后效果如图:
④启动测试
分别启动nacos8841、nacos8842、nacos8843下的startup.cmd文件,并启动nginx.exe。
登录location中配置的路径:http://localhost/nacos,nacos/nacos,登录成功。
刷新后,端口变成了8842,说明利用nginx已经实现了负载均衡
①概念功能:Zuul 是netflix开源的一个API Gateway 服务器, 本质上是一个web servlet应用。Zuul 在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。
②实现:Zuul的核心是一系列的filters,使用Zuul后,在集群中将一对多的关系转变成了一对一的关系。
③优点:可以在网关层进行数据合法校验、权限认证、负载均衡等统一处理;客户端只须跟网关交互,减少了客户端与各个微服务之间的交互次数
1.Zuul是Netflix开源的微服务网关,它可以和Eureka、Ribbon、Hystrix等组件配合使用。
使用步骤如下:
①添加依赖
②启动类添加注释@EnableZuulProxy
③配置application.yml路由分发
2.Zuul + Nacos
代码实例
(1)在父项目的 pom.xml引入如下依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-zuulartifactId>
<version>2.2.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-ribbonartifactId>
<version>2.2.6.RELEASEversion>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
<version>2.2.6.RELEASEversion>
<exclusions>
<exclusion>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-ribbonartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
<version>2.2.6.RELEASEversion>
dependency>
(2)naco子项目代码,我之前做过,直接复用之前的代码。注意下application的name:nacos-provider后面zuul配置要用到
然后我这边在controller中加了个方法以供测试
@GetMapping("/gateway")
@ResponseBody
public String gateway() {
return "gateway test";
}
(3)创建网关模块,结构如下:
pom文件就引用父模块的依赖就行
ZuulConfiguration.java这个配置用来解决版本冲突问题
package com.example.gateway.config;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.CallbackFilter;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.cglib.proxy.NoOp;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.cloud.netflix.zuul.web.ZuulController;
import org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Fix for Zuul configuration with Spring Boot 2.5.x + Zuul - "NoSuchMethodError: ErrorController.getErrorPath()":
*/
@Configuration
public class ZuulConfiguration {
/**
* The path returned by ErrorController.getErrorPath() with Spring Boot < 2.5
* (and no longer available on Spring Boot >= 2.5).
*/
private static final String ERROR_PATH = "/error";
private static final String METHOD = "lookupHandler";
/**
* Constructs a new bean post-processor for Zuul.
*
* @param routeLocator the route locator.
* @param zuulController the Zuul controller.
* @param errorController the error controller.
* @return the new bean post-processor.
*/
@Bean
public ZuulPostProcessor zuulPostProcessor(@Autowired RouteLocator routeLocator,
@Autowired ZuulController zuulController,
@Autowired(required = false) ErrorController errorController) {
return new ZuulPostProcessor(routeLocator, zuulController, errorController);
}
private enum LookupHandlerCallbackFilter implements CallbackFilter {
INSTANCE;
@Override
public int accept(Method method) {
if (METHOD.equals(method.getName())) {
return 0;
}
return 1;
}
}
private enum LookupHandlerMethodInterceptor implements MethodInterceptor {
INSTANCE;
@Override
public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if (ERROR_PATH.equals(args[0])) {
// by entering this branch we avoid the ZuulHandlerMapping.lookupHandler method to trigger the
// NoSuchMethodError
return null;
}
return methodProxy.invokeSuper(target, args);
}
}
private static final class ZuulPostProcessor implements BeanPostProcessor {
private final RouteLocator routeLocator;
private final ZuulController zuulController;
private final boolean hasErrorController;
ZuulPostProcessor(RouteLocator routeLocator, ZuulController zuulController, ErrorController errorController) {
this.routeLocator = routeLocator;
this.zuulController = zuulController;
this.hasErrorController = (errorController != null);
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (hasErrorController && (bean instanceof ZuulHandlerMapping)) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ZuulHandlerMapping.class);
enhancer.setCallbackFilter(LookupHandlerCallbackFilter.INSTANCE); // only for lookupHandler
enhancer.setCallbacks(new Callback[] {LookupHandlerMethodInterceptor.INSTANCE, NoOp.INSTANCE});
Constructor<?> ctor = ZuulHandlerMapping.class.getConstructors()[0];
return enhancer.create(ctor.getParameterTypes(), new Object[] {routeLocator, zuulController});
}
return bean;
}
}
}
MyZuulFilter 过滤器用来统一处理逻辑
package com.example.gateway.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import javax.servlet.http.HttpServletRequest;
public class MyZuulFilter extends ZuulFilter {
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
System.out.println("filter test");
return null;
}
}
application.yml配置端口等,注意这里的serviceId是刚刚的nacos-provider
server:
port: 8008
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
zuul:
routes:
auth:
path: /auth/**
serviceId: nacos-provider
GatewayApplication添加filter,并增加注释
package com.example.gateway;
import com.example.gateway.filter.MyZuulFilter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
@EnableZuulProxy
public class GatewayApplication {
@Bean
public MyZuulFilter preRequestLogFilter() {
return new MyZuulFilter ();
}
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
3.测试,可以看到下面两个网址效果一致,并且filter生效了
http://localhost:8070/gateway
http://localhost:8008/auth/gateway
1)Spring Security
SpringSecurity是基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。 核心功能主要包括:认证 、授权 、攻击防护等。 其核心就是一组过滤器链。
2)Spring Cloud Security
Spring Cloud Security是基于spring security的安全工具包,在分布式项目中为你的应用程序添加安全控制。
主要功能包括单点登录,令牌中继和令牌交换等。
我这里主要介绍的springsecurity的使用,cloud security的用法基本一致
1.去官网查看文档,了解下你所使用的版本特性。网址:https://spring.io/projects/spring-security,从Projects-View all projects点进去,找到Spring Security,然后查看相关文档。
2.SpringSecurity实战
(1)简单实现用户认证功能
①首先创建一个springboot的web项目,并引入springsecurity的依赖
pom.xml如下:
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.6.RELEASEversion>
<relativePath/>
parent>
<groupId>com.examplegroupId>
<artifactId>springsecurityartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>springsecurityname>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>11java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
②创建controller和config等类,controller提供入口,config中配置用户名和密码
TestController:
package com.example.springsecurity.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class TestController {
@RequestMapping(value="/hello")
@ResponseBody
public String hello(){
return "hello";
}
}
WebSecurityConfig.java:
package com.example.springsecurity.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//用户名密码设置 这里使用的在内存中定义,可以把这里改成通过数据库查询
auth.inMemoryAuthentication()
.passwordEncoder(new BCryptPasswordEncoder())
.withUser("admin").password(new BCryptPasswordEncoder().encode("admin123")).roles("admin");
}
}
③验证:进入http://localhost:8080/hello,会弹出登录页面,输入我们设置的用户名及密码即可
最终结果如下:
(2)在此基础上,通过角色来控制用户权限。这里使用了自定义的登陆页面,并将用户名密码的配置通过userDetailsService实现。
代码如下:
①java类
WebSecurityConfig.java:
package com.example.springsecurity.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import java.util.List;
@Configuration
//@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//用户名密码设置 可以把这里改成通过数据库查询
// 这里使用的在内存中定义
/*auth.inMemoryAuthentication()
.passwordEncoder(new BCryptPasswordEncoder())
.withUser("admin").password(new BCryptPasswordEncoder().encode("admin123")).roles("admin");*/
//这里使用自定义类的方式
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//自定义登录页面
http.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
//配置接收的用户名密码参数
.usernameParameter("username")
.passwordParameter("password")
//登陆成功后调转的请求
.defaultSuccessUrl("/index").permitAll();
//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求
http.csrf().disable();
http.authorizeRequests()
//permitAll()的意思就是放行,这里除了登录页放行,其他的都需要认证
.antMatchers("/","/login").permitAll()
//赋权,指定目录下的页面需要特定角色 这里我把admin权限设置的比较高,也能访问role1的页面
.antMatchers("/admin/2").hasAnyAuthority("admin")
.antMatchers("/role1/1").hasAnyAuthority("admin,role1");
}
}
TestController.java
package com.example.springsecurity.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class TestController {
@RequestMapping(value="/hello")
@ResponseBody
public String hello(){
return "hello";
}
@RequestMapping("role1/1")
public String role1Page(){
return "role1/1";
}
@RequestMapping("admin/2")
public String adminPage(){
return "admin/2";
}
@RequestMapping(value="/")
public String test1(){
return "login";
}
@RequestMapping("login")
public String login(){
return "login";
}
@RequestMapping("index")
public String index(){
System.out.println("logging...");
return "index";
}
}
UserDetailService.java
package com.example.springsecurity.service;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("userDetailsService")
public class UserDetailService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//这里设定角色
/*List authList = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
return new User("admin",new BCryptPasswordEncoder().encode("admin123"),authList);*/
List<GrantedAuthority> authList = AuthorityUtils.commaSeparatedStringToAuthorityList("role1");
return new User("role1",new BCryptPasswordEncoder().encode("123"),authList);
}
}
②html类
index.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
登录成功!
<br/>
<a href="/role1/1">role1 a>
<br/>
<a href="/admin/2">admin a>
body>
html>
login.thml
DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example title>
head>
<body>
<div th:if="${param.error}">
用户名或密码不正确。
div>
<form th:action="@{/login}" method="post">
<div><label> 用户名 : <input type="text" name="username"/> label>div>
<div><label> 密码::<input type="password" name="password"/> label>div>
<div><input type="submit" value="提交"/>div>
form>
body>
html>
1.html
DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example title>
head>
<body>
role1 Page
body>
html>
2.html
DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example title>
head>
<body>
admin Page
body>
html>
③其他(pom文件没有修改)
application.yml
spring:
thymeleaf:
#模板的模式,支持 HTML, XML TEXT JAVASCRIPT
mode: HTML5
#开发配置为false,避免修改模板还要重启服务器
cache: false
④测试
第一步:UserDetailService 中使用role1的用户名及密码
然后登录
点击role1,可以进入;但是点击admin,显示403,没权限访问
第二步:UserDetailService 中使用admin的用户名及密码,修改如下图
然后重启,按照上面的步骤再操作一次(用户名密码要用admin的),admin的页面可以进去。
1.Oauth2介绍
(1)概念:是用于REST/APIs的代理授权框架,它指定了将长期的令牌替换为短期的令牌的策略;
(2)特性:基于令牌Token的授权,在无需暴露用户密码的情况下,使应用有获取用户资源的权限;
(3)使用场景:常用于APP、第三方服务的授权,如使用微信授权第三方应用系统;
(4)有四种模式:密码模式(不推荐)、授权码模式(常用)、简化模式、客户端模式。
2.SpringCloudSecurity + Oauth2实践
(1)不使用redis的用法
pom.xml:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.security.oauthgroupId>
<artifactId>spring-security-oauth2artifactId>
<version>2.3.3.RELEASEversion>
dependency>
<dependency>
<groupId>javax.xml.bindgroupId>
<artifactId>jaxb-apiartifactId>
<version>2.3.0version>
dependency>
<dependency>
<groupId>com.sun.xml.bindgroupId>
<artifactId>jaxb-implartifactId>
<version>2.3.0version>
dependency>
<dependency>
<groupId>com.sun.xml.bindgroupId>
<artifactId>jaxb-coreartifactId>
<version>2.3.0version>
dependency>
<dependency>
<groupId>javax.activationgroupId>
<artifactId>activationartifactId>
<version>1.1.1version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
<exclusions>
<exclusion>
<groupId>io.lettucegroupId>
<artifactId>lettuce-coreartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
dependency>
授权服务器AuthorizationServerConfig :
package com.example.testsecurity.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
// 该对象用来支持 password 模式
@Autowired
AuthenticationManager authenticationManager;
// 该对象用来将令牌信息存储到内存中
@Autowired(required = false)
TokenStore inMemoryTokenStore;
// 该对象将为刷新token提供支持
@Autowired
UserDetailsService userDetailsService;
// 指定密码的加密方式
@Bean
PasswordEncoder passwordEncoder() {
// 使用BCrypt强哈希函数加密方案(密钥迭代次数默认为10)
return new BCryptPasswordEncoder();
}
// 该对象用来将令牌信息存储到Redis中
@Autowired
RedisConnectionFactory redisConnectionFactory;
// 配置 password 授权模式
@Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.inMemory()
.withClient("client1")
// 该client允许的授权类型 authorization_code,password,refresh_token
.authorizedGrantTypes("authorization_code","password", "refresh_token")
.accessTokenValiditySeconds(1800) // 配置access_token的过期时间
.resourceIds("hello") //配置资源id
.scopes("all")
.secret(passwordEncoder().encode("123"));
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.tokenStore(inMemoryTokenStore) //配置令牌的存储(这里存放在内存中)
//endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory)) //配置令牌的存储(这里存放在redis中)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
// 表示支持 client_id 和 client_secret 做登录认证
security.allowFormAuthenticationForClients();
}
}
资源服务器ResourceServerConfig :
package com.example.testsecurity.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId("hello") // 配置资源id,这里的资源id和授权服务器中的资源id一致
.stateless(true); // 设置这些资源仅基于令牌认证
}
// 配置 URL 访问权限
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")
.antMatchers("/role1/**").hasRole("role1")
.anyRequest().authenticated();
}
}
security配置:
package com.example.testsecurity.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
@Override
protected UserDetailsService userDetailsService() {
return super.userDetailsService();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password(new BCryptPasswordEncoder().encode("123"))
.roles("admin")
.and()
.withUser("role1")
.password(new BCryptPasswordEncoder().encode("123"))
.roles("role1");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/oauth/**").authorizeRequests()
//放行授权的请求
.antMatchers("/oauth/**").permitAll()
//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求
.and().csrf().disable();
}
}
测试入口HelloController:
package com.example.testsecurity.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/admin/hello")
public String admin() {
return "hello admin";
}
@GetMapping("/role1/hello")
public String user() {
return "hello role1";
}
@GetMapping("/hello")
public String hello() {
return "hello";
}
}
测试:注意这里必须用postman,首先要获取access_token
然后通过accesstoken可以访问对应的资源,其中除role1的资源外其他资源均可访问
http://localhost:8080/hello?access_token=3645490f-9822-4adb-8dc7-1b59c5b8cf8c
http://localhost:8080/admin/hello?access_token=3645490f-9822-4adb-8dc7-1b59c5b8cf8c
http://localhost:8080/role1/hello?access_token=3645490f-9822-4adb-8dc7-1b59c5b8cf8c
(2)使用redis:
1.首先我们了解下使用redis储存token的好处:
token具有时效性,redis有过期功能处理有优势;
可以在多个服务之间实现令牌共享
redis安装使用参考:https://blog.csdn.net/tttalk/article/details/122042206?spm=1001.2014.3001.5501
2.使用代码
application.yml:
spring:
redis:
timeout: 6000ms
# redis数据库索引(默认为0)
database: 0
host: 192.168.124.128
port: 6379
lettuce:
pool:
max-active: 1000 #连接池最大连接数(使用负值表示没有限制)
max-idle: 10 #连接池中的最大空闲连接
min-idle: 3 #连接池中的最小空闲连接
max-wait: -1 #连接池最大阻塞等待时间(使用负值表示没有限制)
password: 123
在AuthorizationServerConfig中将令牌放到redis中:
重新进行access_token的请求,可以看到已经存在redis中了
(1)Failed to introspect Class [org.springframework.cloud.context.properties.ConfigurationPropertiesBean
解决方式:查看官网地址https://start.spring.io/actuator/info
把拿到的字符串去https://www.bejson.com/上面解析一下,结果如下
可以看到我用的springboot2.6.3版本对应的springcloud版本为2021.0.1
(1) Request execution failure with status code 403; retrying on another server if available
Cannot execute request on any known server
报错原因:Eureka会自动配置CSRF防御机制,spring security认为post,put and delete http methods 都是有风险的,
如果这些method发送过程中没有带上CSRF token,会被直接拦截并返回403 forbidden
解决:见SpringCloudEurekaServerApplication,继承WebSecurityConfigurerAdapter放行eureka相关csrf 请求
(1)启动服务时报错:java.lang.IllegalArgumentException: db.num is null
报错原因:默认集群启动,而集群没有配置
解决:修改为单机启动。修改startup.cmd,将set MODE = "cluster"替换为
rem set MODE="cluster"
set MODE="standalone"
(1)ServerPropertiesAutoConfiguration.class] cannot be opened because it does not exist
报错原因:依赖有问题
解决:spring-cloud-starter-hystrix已经被废弃了,现在应该使用spring-cloud-starter-netflix-hystrix。
如何解决类似问题呢?我这里给个基本步骤
①进入maven网址:https://mvnrepository.com/,搜索框输入hystrix。可以看到有两个依赖。
②点进第二个,可以看到deprecated标识,代表已经废弃了,现在使用spring-cloud-starter-netflix-hystrix
(2)注入Feign接口时提示:Could not autowire. There is more than one beans of ‘UserFeign’ type
错误原因:这是IDEA工具 中spring 插件的问题
解决方法:settings -editor -Inspections- Spring-Spring Core-Code-Autowiring for Bean Class >>>>>>>> 右侧:severity 修改成warning 保存即可。
(1)There is no passwordEncoder mapped for the id “null”
原因:Spring Security5.0开始改变了默认的密码格式,需要要将前端传过来的密码进行某种方式加密,官方推荐的是使用bcrypt加密方式
如:
auth.inMemoryAuthentication()
.withUser("admin").password("admin123").roles("admin")
改为
auth.inMemoryAuthentication()
.passwordEncoder(new BCryptPasswordEncoder())
.withUser("admin").password(new BCryptPasswordEncoder().encode("admin123")).roles("admin");
(1)NoSuchMethodError: org.springframework.boot.web.servlet.error.ErrorController.getErrorPath
原因: spring-boot 较新的版本,从 ErrorController 中删除了 getErrorPath() API,但spring-cloud-starter-netflix-zuul 2.2.6.RELEASE 仍然调用此 API 并导致此错误。
解决:换较低版本的spring-boot版本或者创建BeanPostProcessor拦截 lookupHandler 方法的调用,参考我在zuul使用教程中写的ZuulConfiguration,然后在启动类引用一下就可以。
各位看官如果你已经从前看到最后了,点赞收藏一下不过分吧。