一、介绍1. 概念OAuth开放授权是一个开放标准，允许用户授权第三方应用访问存储在另外的服务提供者上的信息，而不需要将用户名和密码提供给第三方应用或分享数据的内容，OAuth2.0是OAuth协议的延续版本，不向下兼容OAuth1.0.2. 流程示例允许将认证和授权的过程交由一个独立的第三方来进行担保，OAuth协议用来定义如何让第三方的担保有效且双方可信，以登录百度账号为例：官方示意图：OAuth2.0包含以下几个角色：客户端（示例中的浏览器、微信客户端）本身不存储资源，需要通过资源拥有者的授权去请求资源服务器的资源资源拥有者（示例中的用户）通常是用户，也可以是应用程序，即该资源的拥有者授权服务器（也称认证服务器）（示例中的微信）用于服务提供者对资源资源拥有者的身份进行认证，对访问资源进行授权，认证成功后会给客户端发放令牌（access_token），作为客户端访问资源服务器的凭证资源服务器（示例中的百度、微信）存储资源的服务器，示例中，微信通过OAuth协议让百度可以访问自己存储的用户信息，而百度则通过该协议让用户可以访问自己受保护的资源3. 补充clientDetails(client_id)：客户信息，代表百度在微信中的唯一索引secret：密钥，百度获取微信信息时需要提供的一个加密字段scope：授权作用域百度可以获取到的微信的信息范围，如：登录范围的凭证无法获取用户信息范围的内容access_token：访问令牌，百度获取微信用户信息的凭证grant_type：授权类型，authorization_code(授权码模式), password(密码模式), client_credentials(客户端模式), implicit(简易模式、隐式授权), refresh_token(刷新令牌)userDetails(user_id)：授权用户标识，示例中代表用户的微信号二、Demo实现OAuth2的服务包含授权服务（Authorization Server）和资源服务（Resource Server）。授权服务包含对接入端以及登入用户的合法性进行验证并颁发token等功能，对令牌的请求断点由SpringMVC控制器进行实现AuthorizationEndPoint服务用于认证请求，默认url：/oauth/authorizeTokenEndPoint用于访问令牌的请求，默认url：/oauth/tokenOAuth2AuthenticaionProcessingFilter用于对请求给出的身份令牌进行解析健全大致业务流程：客户请求授权服务器申请access_token客户携带申请到的access_token访问资源服务器中的资源信息资源服务器将检验access_token的合法性，验证合法后返回对应的资源信息1. 父工程搭建pom.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> <modules> <module>OAuth-Server</module> <module>OAuth-User</module> </modules> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.5.RELEASE</version> <relativePath/> </parent> <groupId>com.sw</groupId> <artifactId>Spring-Cloud-OAuth2</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>pom</packaging> <properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <springboot.version>2.2.5.RELEASE</springboot.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>${springboot.version}</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-security</artifactId> <version>${springboot.version}</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> <version>${springboot.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-jwt</artifactId> <version>1.0.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>${springboot.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> <version>${springboot.version}</version> </dependency> <dependency> <groupId>javax.interceptor</groupId> <artifactId>javax.interceptor-api</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>8.0.18</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> </dependency> </dependencies> </dependencyManagement> </project>2. 授权服务pom.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"> <parent> <artifactId>Spring-Cloud-OAuth2</artifactId> <groupId>com.sw</groupId> <version>1.0.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>OAuth-Server</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-jwt</artifactId> </dependency> </dependencies> </project>主启动类开启 @EnableAuthorizationServer 注解package com.sw.oauth.server; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; /** * @author suaxi * @date 2022/2/14 22:01 */ @SpringBootApplication @EnableAuthorizationServer public class OAuthServerApplication { public static void main(String[] args) { SpringApplication.run(OAuthServerApplication.class, args); } } application.yamserver: port: 8088 spring: application: name: OAuth-Server配置AuthorizationConfigpackage com.sw.oauth.server.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 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.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices; import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices; import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; import org.springframework.security.oauth2.provider.token.DefaultTokenServices; import org.springframework.security.oauth2.provider.token.TokenStore; /** * @author suaxi * @date 2022/2/14 22:11 */ @Configuration public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthorizationCodeServices authorizationCodeServices; @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @Autowired private TokenStore tokenStore; @Autowired private ClientDetailsService clientDetailsService; /** * 3.令牌端点安全约束 * @param security * @throws Exception */ @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security //oauth/token_key 公开 .tokenKeyAccess("permitAll()") //oauth/check_token 公开 .checkTokenAccess("permitAll()") //表单认证，申请令牌 .allowFormAuthenticationForClients(); } /** * 1.客户端详情 * @param clients * @throws Exception */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() //clientId .withClient("c1") //客户端密钥 .secret(new BCryptPasswordEncoder().encode("secret")) //资源列表 .resourceIds("admin") //授权方式 .authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token") //授权范围 .scopes("all") //跳转到授权页面 .autoApprove(false) //回调地址 .redirectUris("https://wangchouchou.com"); } /** * 2.令牌服务 * @param endpoints * @throws Exception */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints //认证管理器 .authenticationManager(authenticationManager) //密码模式的用户信息管理 .userDetailsService(userDetailsService) //授权码服务 .authorizationCodeServices(authorizationCodeServices) //令牌管理服务 .tokenServices(tokenServices()) .allowedTokenEndpointRequestMethods(HttpMethod.POST); } public AuthorizationServerTokenServices tokenServices() { DefaultTokenServices tokenServices = new DefaultTokenServices(); //客户端详情 tokenServices.setClientDetailsService(clientDetailsService); //允许令牌自动刷新 tokenServices.setSupportRefreshToken(true); //令牌存储策略 tokenServices.setTokenStore(tokenStore); //默认令牌有效期 tokenServices.setAccessTokenValiditySeconds(3600); //刷新令牌有效期 tokenServices.setRefreshTokenValiditySeconds(86400); return tokenServices; } /** * 授权码模式的授权码如何存取 * @return */ @Bean public AuthorizationCodeServices authorizationCodeServices() { return new InMemoryAuthorizationCodeServices(); } } ClientDetailsServiceConfigurer：配置客户端详情（ClientDetails）服务，客户端详情信息在这里进行初始化，此处以内存配置方式为例clientId：用于标识客户的idsecret：客户端安全码scope：客户端访问范围，如果为空，则代表拥有全部的访问范围authorizedGrantTypes：授权类型authorities：客户端拥有的权限redirectUris：回调地址，授权服务会往该地址推送客户端相关的信息AuthorizationServerEndpointsConfigurer：配置令牌（token）的访问端点和令牌服务（tokenService），它可以完成令牌服务和令牌服务各个端点配置authenticationManager：认证管理器，选择password认证模式时就需要指定authenticationManager对象来进行鉴权userDetailsService：用户主体管理服务，如果设置这个属性，需要实现UserDetailsService接口，也可以设置全局域（GlobalAuthenticationManagerConfigurer），如果配置这种方式，refresh_token刷新令牌方式的授权流程中会多一个检查步骤，来确保当前令牌是否仍然有效authorizationCodeServices：用于授权码模式implicitGrantService：用于设置隐式授权模式的状态tokenGranter：如果设置该属性，授权全部交由自己掌控，并会忽略以上已设置的属性AuthorizationServerSecurityConfigurer：配置令牌端点的安全约束，可以通过pathMapping()方法配置端点url的链接地址，替换oauth默认的授权地址，也可以跟换spring security默认的授权页面/oauth/authorize：授权端点/oauth/token：令牌端点/oauth/confirm_access：用户确认授权提交端点/oauth/error：授权服务错误信息端点/oauth/check_token：检查令牌/oauth/token_key：使用jwt令牌需要用到的提供公有密钥的端点配置TokenConfigpackage com.sw.oauth.server.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; /** * @author suaxi * @date 2022/2/14 22:24 */ @Configuration public class TokenConfig { @Bean public TokenStore tokenStore() { return new InMemoryTokenStore(); } } 实现AuthorizationServerTokenService需要继承DefaultTokenService，该类可以修改令牌的格式和存储，默认情况下，在创建令牌时使用随机字符串来填充，这个类完成了令牌管理的大部分事情，唯一需要依赖的是spring容器中的TokenStore接口，以此来定制令牌持久化；TokenStore有一个默认实现（InMemoryTokenStore），这个实现类将令牌保存到内存中，除此之外还有其他几个默认实现类：InMemoryTokenStore：默认采用方式，可在单节点运行（即并发压力不大的情况下，并且在失败时不会进行备份），也可以在并发的时候进行管理，因为数据保存在内存中，不进行持久化存储，易于调试JdbcTokenStore：基于JDBC的实现类，令牌会被保存到关系型数据库中，可在不同的服务器之间共享令牌信息RedisTokenStore：与jdbc方式类似JwtTokenStore（JSON Web Token）：可以将令牌信息全部编码整合进令牌本身，优点是后端可以不用进行存储操作，缺点是撤销一个已经授权的令牌很困难，所以通常用来处理生命周期较短的令牌以及撤销刷新令牌，另一个缺点是令牌较长，包含的用户凭证信息，它不保存任何数据在转换令牌值和授权信息方面与DefaultTokenServices扮演一样的角色配置WebSecurityConfigpackage com.sw.oauth.server.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.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.User; 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.provisioning.InMemoryUserDetailsManager; /** * @author suaxi * @date 2022/2/14 22:25 */ @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception{ return super.authenticationManager(); } @Bean @Override public UserDetailsService userDetailsService() { InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager( User.withUsername("admin").password(passwordEncoder().encode("123456")).authorities("manager, worker").build(), User.withUsername("manager").password(passwordEncoder().encode("123456")).authorities("manager").build(), User.withUsername("worker").password(passwordEncoder().encode("123456")).authorities("worker").build() ); return inMemoryUserDetailsManager; } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .anyRequest().authenticated() .and() .formLogin(); } } 3. 授权服务流程测试1. 客户端模式client_credentails客户端向授权服务器发送自己的身份信息，请求access_tokenlocalhost:8088/oauth/token参数列表：grant_type：授权类型，需填写client_credentialsclient_id：客户端标识client_secret：客户端密钥这种方式最方便但也是最不安全的，代表了授权服务器对客户端完全信任，一般用于授权服务器对客户端完全信任的场景。2. 密码模式password（1）资源拥有者将用户名、密码发送给客户端（2）客户端用资源拥有者的用户名、密码向授权服务器申请令牌localhost:8088/oauth/token参数列表：grant_type：授权类型，需填写passwordclient_id：客户端标识client_secret：客户端密钥username：用户名password：密码这种方式用户会把用户名、密码直接泄露给客户端，代表了资源拥有者和授权服务器对客户端的绝对互信，一般用于内部开发客户端的场景3.简化模式（隐式模式）implicit（1）用户访问客户端，客户端向授权服务器申请授权（2）授权服务器引导用户进入授权页面，待用户同意授权（3）用户同意授权（4）用户同意授权后，授权服务器向客户端返回令牌测试流程：（1）客户端引导用户，直接访问授权服务器的授权地址http://localhost:8088/oauth/authorize?client_id=c1&response_type=token&scope=all&redirect_uri=https://wangchouchou.com（2）用户登录之后跳转至授权页面（3）用户点击approve同意授权，提交之后，页面跳转至redirect_uri地址并携带令牌信息（该地址需授权服务器提前配置）一般情况下，redirect_uri会配置成客户端自己的一个响应地址，这个地址收到授权服务器推送过来的令牌之后，可将它保存至本地，在需要调用资源服务时，再拿出来携带上访问资源服务器。该模式下，access_token是以#gragement的方式返回的，oauth三方的数据已经进行了隔离，一般用于没有服务端的第三方单页面应用，可在js中直接相应access_token。4. 授权码模式 authorization_code相较于简化模式的流程，授权码模式在第四步时，授权服务器先给客户端返回一个授权码（authorization_code），客户端拿到之后，再向授权服务器申请令牌测试流程：（1）用户申请access_token时访问：http://localhost:8088/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_uri=https://wangchouchou.com首先会跳转到授权服务器登录页，用户进行登录（2）登录完成之后，转到授权页面（3）点击同意授权之后，携带授权码重定向至redirect_uri（4）申请令牌参数列表：grant_type：授权类型，需填写authorization_codeclient_id：客户端标识client_secret：客户端密钥code：授权码（只能用一次）redirect_uri：重定向地址5. 刷新令牌当令牌超时后，可以通过refresh_token申请新的令牌（refresh_token随access_token一起申请到）参数列表：grant_type：授权类型，需填写refresh_tokenclient_id：客户端标识client_secret：客户端密钥refresh_token：刷新令牌6. 验证令牌参数列表：token：令牌4. 资源服务pom.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"> <parent> <artifactId>Spring-Cloud-OAuth2</artifactId> <groupId>com.sw</groupId> <version>1.0.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>OAuth-User</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-jwt</artifactId> </dependency> </dependencies> </project>主启动类打开@EnableResourceServer注解，会自动增加一个OAuth2AuthenticationProcessingFilter的过滤器链package com.sw.oauth.user; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; /** * @author suaxi * @date 2022/2/14 22:08 */ @SpringBootApplication @EnableResourceServer public class OAuthUserApplication { public static void main(String[] args) { SpringApplication.run(OAuthUserApplication.class, args); } } application.ymlserver: port: 8089 spring: application: name: OAuth-User资源服务器核心配置Spring Security也提供了ResourceServerSecurityConfigurer适配器来协助完成资源服务器的配置ResourceServerSecurityConfigurer中主要包含：tokenServices：ResourceServerTokenServices类的实例，用来实现令牌服务，即如何验证令牌tokenStore：TokenStore类的实例，指定令牌如何访问，与tokenServices配置可选resourceId：资源服务器id（可选），一般情况下推荐设置并在授权服务中进行验证tokenExtractor：用于提取请求中的令牌HttpSecurity配置与Spring Security类似：authorizeRequests()方法验证请求antMatchers()方法匹配访问路径access()方法配置需要的权限ResourceServerConfig配置package com.sw.oauth.user.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; import org.springframework.security.oauth2.provider.token.RemoteTokenServices; import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; import org.springframework.security.oauth2.provider.token.TokenStore; /** * @author suaxi * @date 2022/2/15 22:36 */ @Configuration public class ResourceServerConfig extends ResourceServerConfigurerAdapter { private static final String RESOURCE_ADMIN = "admin"; @Autowired private TokenStore tokenStore; @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources //资源ID .resourceId(RESOURCE_ADMIN) //使用远程服务验证令牌（使用JWT令牌时无需远程验证服务） .tokenServices(tokenServices()) .tokenStore(tokenStore) //无状态模式（无需管理session，此处只验证access_token） .stateless(true); } @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/**") .access("#oauth2.hasAnyScope('all')") .and() .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); } /** * access_token远程验证策略 * @return */ public ResourceServerTokenServices tokenServices() { RemoteTokenServices tokenServices = new RemoteTokenServices(); tokenServices.setCheckTokenEndpointUrl("http://localhost:8088/oauth/check_token"); tokenServices.setClientId("c1"); tokenServices.setClientSecret("secret"); return tokenServices; } } 需注意ResourceServerSecurityConfigurer的tokenServices()方法，设置了一个token的管理服务，其中，如果资源服务和授权服务在同一应用程序上，那可以使用DefaultTokenServices，就不用考虑实现所有必要接口一致性的问题，反之，则必须要保证有能够匹配授权服务提供的ResourceServerTokenServices，这个类知道如何对令牌进行解码。令牌解析方法：使用DefaultTokenServices在资源服务器本地配置令牌存储、解码、解析方式；使用RemoteTokenServices，资源服务器通过http请求来解码令牌，每次请求都需要请求授权服务器端点/oauth/check_token，同时还需要授权服务器将该端点暴露出来，以便资源服务器进行访问，在资源服务器配置中需注意：@Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security //oauth/token_key 公开 .tokenKeyAccess("permitAll()") //oauth/check_token 公开 .checkTokenAccess("permitAll()") //表单认证，申请令牌 .allowFormAuthenticationForClients(); }资源服务器WebSecurityConfig配置package com.sw.oauth.user.config; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; /** * @author suaxi * @date 2022/2/15 22:52 */ @Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/admin/**") .hasAnyAuthority("admin") .anyRequest().authenticated(); } } controllerpackage com.sw.oauth.user.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author suaxi * @date 2022/2/15 22:43 */ @RestController @RequestMapping("/admin") public class AdminController { @GetMapping("/test") public String test() { return "test"; } } 接口测试：直接访问：header携带令牌访问：在该测试过程中，资源服务器未配置TokenStore对象，它并不知道access_token的意义；需要使用RemoteTokenServices将令牌拿到授权服务器上进行验证才能得到客户信息，当请求量逐步增大之后，会加重系统的网络负担以及运行效率，而JWT令牌需解决以上提到的问题。三、 JWT令牌1. 概念JWT（JSON Web Token），是一个开放的行业标准（RFC 7519），它定义了一种简单的、自包含的协议格式，用于在通信双方传递json对象，传递的信息经过数字签名，可以被验证和信任，可以使用HMAC、RSA等算法。在OAuth中使用JWT，令牌本身就包含了客户的详细信息，资源服务器就不用再依赖授权服务器就可以完成令牌解析。官网：https://jwt.io/RFC 7519协议：https://datatracker.ietf.org/doc/rfc7519/优点基于json，方便解析自定义令牌内容，可扩展通过非对称加密算法及数字签名防止被篡改，安全性高资源服务器克不依赖于授权服务器完成令牌解析缺点：令牌较长，占用的空间过多令牌结构由Header.Payload.Signature三部分组成，中间由(.)分割Header：头部包括令牌的类型以及使用的hash算法（HMAC、SHA256、RSA）{ "alg": "HS256", "typ": "JWT" }使用Base64编码之后得到JWT令牌的第一部分Payload：负载（Base64编码）：存放有效信息，如：iss（签发者），exp（过期时间戳），sub（面向的用户）等，也可以自定义字段该部分不建议存放敏感信息，可以通过解码还原出原始内容。Signature:该部分防止JWT内容被篡改，使用Base64将前两部分编码，使用(.)连接组成字符串，最后使用header中声明的算法进行签名2. 配置JWT令牌服务1. 授权服务配置TokenConfig配置package com.sw.oauth.server.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; /** * @author suaxi * @date 2022/2/14 22:24 */ @Configuration public class TokenConfig { private static final String SIGN_KEY = "server"; // @Bean // public TokenStore tokenStore() { // return new InMemoryTokenStore(); // } @Bean public TokenStore tokenStore() { return new JwtTokenStore(accessTokenConverter()); } @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter(); accessTokenConverter.setSigningKey(SIGN_KEY); return accessTokenConverter; } } AuthorizationConfig配置@Autowired private JwtAccessTokenConverter accessTokenConverter; public AuthorizationServerTokenServices tokenServices() { DefaultTokenServices tokenServices = new DefaultTokenServices(); //客户端详情 tokenServices.setClientDetailsService(clientDetailsService); //允许令牌自动刷新 tokenServices.setSupportRefreshToken(true); //令牌存储策略 tokenServices.setTokenStore(tokenStore); //使用JWT令牌 tokenServices.setTokenEnhancer(accessTokenConverter); //默认令牌有效期 tokenServices.setAccessTokenValiditySeconds(3600); //刷新令牌有效期 tokenServices.setRefreshTokenValiditySeconds(86400); return tokenServices; }2. 测试申请令牌：验证令牌：3. 资源服务器配置将授权服务器中的TokenConfig拷贝至资源服务器config目录下在ResourceServerConfig中屏蔽ResourceServerTokenServices package com.sw.oauth.user.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; import org.springframework.security.oauth2.provider.token.RemoteTokenServices; import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; import org.springframework.security.oauth2.provider.token.TokenStore; /** * @author suaxi * @date 2022/2/15 22:36 */ @Configuration public class ResourceServerConfig extends ResourceServerConfigurerAdapter { private static final String RESOURCE_ADMIN = "admin"; @Autowired private TokenStore tokenStore; @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources //资源ID .resourceId(RESOURCE_ADMIN) //使用远程服务验证令牌（使用JWT令牌时无需远程验证服务） // .tokenServices(tokenServices()) .tokenStore(tokenStore) //无状态模式（无需管理session，此处只验证access_token） .stateless(true); } @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/**") .access("#oauth2.hasAnyScope('all')") .and() .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); } /** * access_token远程验证策略 * @return */ // public ResourceServerTokenServices tokenServices() { // RemoteTokenServices tokenServices = new RemoteTokenServices(); // tokenServices.setCheckTokenEndpointUrl("http://localhost:8088/oauth/check_token"); // tokenServices.setClientId("c1"); // tokenServices.setClientSecret("secret"); // return tokenServices; // } } 4. 测试Github demo地址：https://github.com/suaxi/Spring-Cloud-OAuth2
一、概述​ Spring Cloud Stream 是一个构建消息驱动微服务的框架，应用程序通过inputs（生产者）或outputs（消费者）来与Spring Cloud Stream中binder对象交互，binder主要负责与消息中间件交互（开发人员只需操作Stream，而不用关心底层使用的是什么MQ）。注：目前仅支持RabbitMQ和kafka二、如何屏蔽底层差异在没有绑定器这个概念的情况下，SpringBoot应用在直接与消息中间件交互的时候，由于各MQ的设计初衷有不同，细节上存在较大的差异。通过定义绑定器作为中间层，完美的实现了应用程序与消息中间件细节之间的隔离；通过向应用程序暴露统一的channel频道，使得应用程序不需要再考虑各种MQ的实现。Stream对消息中间件的进一步封装，可以做到代码层面对中间件的无感知，甚至于动态的切换中间件（RabbitMQ<==>kafka），使得开发高度解耦，更加专注于自己的业务流程。Spring Cloud Stream处理架构三、通信模式Stream消息通信遵循发布-订阅模式Spring Cloud Stream的业务流程source：发布消息sink：接收消息图片来源：尚硅谷 - 周阳 - Spring Cloud Alibaba四、常用注解和API名称说明Middleware中间件Binder应用与消息中间件之间的封装，目前实现了RabbitMQ和Kafka的Binder，利用Binder可以很方便的连接MQ，可以动态的改变消息类型（RabbitMQ--exchange，Kafaka--topic），可以通过yml文件的配置来实现@Input输入@Output输出@StreamListener监听队列@EnableBinding绑定频道五、测试实例1、提供者pom <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>ymlserver: port: 8801 spring: application: name: cloud-stream-provider rabbitmq: host: x.x.x.x port: 5672 username: admin password: 123456 cloud: stream: binders: # 配置要绑定的rabbitmq的服务信息 defaultRabbit: # 定义的名称，用于binding整合 type: rabbit # 消息组件类型 bindings: # 服务整合处理 output: # 通道名称 destination: StreamExChange # 交换机的名字 content-type: application/json # 消息类型 binder: defaultRabbit # 设置要绑定的消息服务的具体设置 eureka: client: service-url: defaultZone: http://localhost:7001/eureka instance: lease-renewal-interval-in-seconds: 2 # 心跳时间间隔 lease-expiration-duration-in-seconds: 5 # 默认90s instance-id: send-8801.com # 在信息列表显示主机名称 prefer-ip-address: true # 访问路径显示ip 主启动类@SpringBootApplication public class StreamMQMain8801 { public static void main(String[] args) { SpringApplication.run(StreamMQMain8801.class, args); } }Service@EnableBinding(Source.class) //定义消息的推送通道 public class MessageProviderImpl implements MessageProvider { @Autowired private MessageChannel output; @Override public String send() { String serial = UUID.randomUUID().toString(); output.send(MessageBuilder.withPayload(serial).build()); System.out.println("======>" + serial); return null; } }controller@RestController public class SendMessageController { @Autowired private MessageProvider service; @GetMapping("/send") public String sendMessage(){ return service.send(); } }2、消费者==创建8802、8803两个消费者==pom同理消费者ymlserver: port: 8802 spring: application: name: cloud-stream-consumer rabbitmq: host: x.x.x.x port: 5672 username: admin password: 123456 cloud: stream: binders: # 配置要绑定的rabbitmq的服务信息 defaultRabbit: # 定义的名称，用于binding整合 type: rabbit # 消息组件类型 bindings: # 服务整合处理 input: # 通道名称 destination: StreamExChange # 交换机的名字 content-type: application/json # 消息类型 binder: defaultRabbit # 设置要绑定的消息服务的具体设置 group: GroupA8802 eureka: client: service-url: defaultZone: http://localhost:7001/eureka instance: lease-renewal-interval-in-seconds: 2 # 心跳时间间隔 lease-expiration-duration-in-seconds: 5 # 默认90s instance-id: recive-8802.com # 在信息列表显示主机名称 prefer-ip-address: true # 访问路径显示ip 启动类@SpringBootApplication public class StreamMQMain8802 { public static void main(String[] args) { SpringApplication.run(StreamMQMain8802.class, args); } }controller@Component @EnableBinding(Sink.class) public class ReciveController { @Value("${server.port}") private String serverPort; @StreamListener(Sink.INPUT) public void input(Message<String> msg){ System.out.println("消费者8802，接收到消息：" + msg.getPayload() + "\t serverPort：" + serverPort); } }六、重复消费的问题启动以上的测试实例，会发现8801生产者发送一条消息，8802、8803都收到了消息如何解决？通过将消费者分组来解决：即将原先GroupA8802、GroupA8803两个分组改为同一个分组GroupA注：在同一个分组中，多个消费者是竞争关系，这样就能保证消息只会被分组中的一个消费者消费server: port: 8802 spring: application: name: cloud-stream-consumer rabbitmq: host: x.x.x.x port: 5672 username: admin password: 123456 cloud: stream: binders: # 配置要绑定的rabbitmq的服务信息 defaultRabbit: # 定义的名称，用于binding整合 type: rabbit # 消息组件类型 bindings: # 服务整合处理 input: # 通道名称 destination: StreamExChange # 交换机的名字 content-type: application/json # 消息类型 binder: defaultRabbit # 设置要绑定的消息服务的具体设置 group: GroupA eureka: client: service-url: defaultZone: http://localhost:7001/eureka instance: lease-renewal-interval-in-seconds: 2 # 心跳时间间隔 lease-expiration-duration-in-seconds: 5 # 默认90s instance-id: recive-8802.com # 在信息列表显示主机名称 prefer-ip-address: true # 访问路径显示ip消息持久化问题在分组解决重复消费的问题是，分组同时也实现了消息的持久化
Spring Cloud Sleuth提供了一套完整的服务跟踪解决方案，并且兼容支持zipkinTrace Id链路关系图片来源：尚硅谷-周阳 Spring CLoud AlibabaTrace：类似于树结构的span集合，表示一条调用链路，存在唯一标识span：表示调用链路来源（也就是一次请求信息）测试
一、概述在微服务架构的系统中，通常会使用轻量级的消息代理来构建一个共用的消息主题，并让系统中所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费，所以称它为消息总线.二、基本原理ConfigClient实例都监听MQ中同一个topic（默认Spring Cloud Bus），当一个服务刷新数据的时候，它会把这个消息放到topic中，这样其他监听同一个topic的服务就能收到通知，然后去更新自身的配置。三、流程四、手动定点刷新curl -X POSt "http://localhost:3344/actuator/bus-refresh/config-client:3355"五、通过分组解决消息重复消费的问题图片来源：尚硅谷-周阳 Spring CLoud Alibaba注：通过分组解决消息重复消费的同时，也实现了消息持久化
一、概述Spring Cloud GateWay作为Spring Cloud生态系统中的网关，目的是代替zuul。其基于WebFlux框架，底层使用高性能的Reactor模式的通信框架Netty。提供统一的路由方式且基于Filter链的方式提供网关的基本功能，如：安全、监控/指标、限流等。二、特性1、基于Spring Framework 5，Project Reactor，SpringBoot 2.0进行构建2、动态路由：能够匹配任何请求属性3、可以对路由指定Predicates（断言）和Filter（过滤器）4、集成Hystrix的断路器功能5、集成SpringCloud服务发现功能6、请求限流7、路径重写……三、三个概念1、Route（路由）路由是构建网关的基本模块，由ID、目标URI和一系列的断言、过滤器组成，如果断言为true则匹配该路由2、Predicates（断言）匹配Http请求中的所有内容（请求头或请求参数等）3、Filter（过滤）指Spring框架中GatewayFilter的实例，使用过滤时，可以在请求被路由之前或之后对请求做出修改四、工作流程客户端向Spring Cloud GateWay发出请求，然后在Gateway Handler Mapping中找到与请求相匹配的路由，将其发送到Gateway Web Handler；Handler再通过指定的过滤器链将请求发送到实际的执行业务逻辑，然后返回（过滤器可能会在发送代理请求之前（pre）或之后（post）执行业务逻辑）；Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等；在“post”类型的过滤器可以做内容响应、响应头的修改，日志的输出、流量监控等功能Predicates cookies路由规则HeaderHostMethodQuery图片来源：尚硅谷-周阳 Spring CLoud Alibaba
Spring Cloud ConfigSpring Cloud Config为微服务架构中的微服务提供集中化的外部配置支持，配置服务器为各个不同微服务应用的所有环节提供了一个中心化的外部配置。Spring Cloud Config分为客户端和服务端：服务端：分布式配置中心，它是一个独立的微服务应用，用来连接配置服务器，并为客户端提供获取配置信息，加密，解密信息等访问接口客户端：通过指定的配置中心来管理应用资源，以及与业务相关的配置内容，并在启动的时候从配置中心获取和加载配置信息。配置服务器默认采用git来存储配置信息，这样有助于对环境配置进行版本管理。作用：集中管理配置文件不同环境，不同配置，动态化的配置更新，分环境部署，如：开发、测试、生产等环境运行期间可以动态调整配置，不再需要在每个服务部署的机器上编写配置文件，服务会向配置中心统一拉取配置自己的信息当配置发生变动时，服务节点不需要重启，动态应用新的配置配置信息以REST接口的形式暴露图片来源：狂神说Java具体实例1、服务端pom依赖：<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> <version>2.1.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies>application.yml配置：server: port: 3344 spring: application: name: springcloud-config-server #连接远程仓库 cloud: config: server: git: uri: https://github.com/suaxi/SpringCloud-config.gitSpringBoot启动类开启ConfigServer注解：package com.sw.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.config.server.EnableConfigServer; /** * @Author suaxi * @Date 2020/12/30 14:49 */ @SpringBootApplication @EnableConfigServer public class Config_Server_3344 { public static void main(String[] args) { SpringApplication.run(Config_Server_3344.class,args); } } 2、客户端pom依赖：<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> <version>2.1.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies>bootstrap.yml配置：#系统级别的配置 spring: cloud: config: name: config-client #需要从远程仓库读取的资源名称 profile: dev #版本 label: master #分支 uri: http://localhost:3344 #获取配置信息的地址（Config配置服务端）application.yml配置：#用户级别的配置 spring: application: name: springcloud-config-client-3355Controller（配置REST接口）package com.sw.springcloud.controller; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @Author suaxi * @Date 2020/12/30 15:29 */ @RestController public class ConfigClientController { @Value("${spring.application.name}") private String applicationName; @Value("${eureka.client.service-url.defaultZone}") private String eurekaServer; @Value("${server.port}") private String port; @RequestMapping("/config") public String getConfig(){ return "applicationName:"+applicationName+ "eurekaServer:"+eurekaServer+ "port:"+port; } } 3、Eureka服务注册中心pom依赖：<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> <version>1.4.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> <version>2.1.1.RELEASE</version> </dependency> </dependencies>bootstrap.yml配置：与config配置中心的客户端一致，都是从Config服务端获取配置信息spring: cloud: config: name: config-eureka label: master profile: dev uri: http://localhost:3344application.yml配置：spring: application: name: springcloud-config-eureka-7001SpringBoot启动类：package com.sw.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; /** * @Author suaxi * @Date 2020/12/29 11:02 */ @SpringBootApplication @EnableEurekaServer //服务端 public class EurekaConfigServer_7001 { public static void main(String[] args) { SpringApplication.run(EurekaConfigServer_7001.class,args); } } 4、Eureka服务提供者pom依赖：<dependencies> <dependency> <groupId>com.sw</groupId> <artifactId>springcloud-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.4.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> <version>1.4.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> <version>2.1.1.RELEASE</version> </dependency> </dependencies>bootstrap.yml配置：与以上两个的配置一致，都是从Config服务端获取配置信息spring: cloud: config: name: config-dept label: master profile: dev uri: http://localhost:3344application.yml配置：spring: application: name: springcloud-config-dept-8088服务提供者对比之前的配置（application.yml）：通过Config分布式配置中心，将配置文件放到git统一管理，现只需从Config-server获取即可server: port: 8088 #mybatis配置 mybatis: type-aliases-package: com.sw.springcloud.pojo config-location: classpath:mybatis/mybatis-config.xml mapper-locations: classpath:mybatis/mapper/*.xml #Spring配置 spring: application: name: springcloud-provider-dept datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db01?useUnicode=true&character=UFT-8 username: root password: 123456 #配置Eureka，配置服务注册到哪里 eureka: client: service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ instance: instance-id: springcloud-provider_dept8088 #修改Eureka默认描述信息 prefer-ip-address: true #显示服务的真实ip地址，替换原先的localhost #info配置 info: app.name: springcloud-demo company.name: suaxi 配置文件交由git统一管理
Zuul路由网关Zuul包含对请求的路由和过滤两个功能：路由功能：负责将外部请求转发到具体的微服务实例上，实现外部访问入口统一过滤：对请求的处理过程进行干预，是实现请求校验，服务聚合等功能的基础注：Zuul服务也会注册到Eureka服务注册中心1、导入依赖<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> <version>1.4.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> <version>1.4.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId> <version>1.4.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> <version>1.4.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.4.7.RELEASE</version> </dependency> <dependency> <groupId>com.sw</groupId> <artifactId>springcloud-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>2、application.yml配置server: port: 8888 spring: application: name: springcloud-zuul eureka: client: service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ instance: instance-id: zuul8888.com prefer-ip-address: true info: app.name: springcloud-demo company.name: suaxi zuul: routes: mydept.serviceID: springcloud-provider-dept mydept.path: /mydept/** ignored-services: "*" #ignored-services: springcloud-provider-dept禁止使用原来的微服务名访问 #设置为 * 表示禁止使用全部的微服务ID进行访问3、SpringBoot启动类开启Zuul注解一般使用@EnableZuulProxypackage com.sw.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; /** * @Author suaxi * @Date 2020/12/30 10:50 */ @SpringBootApplication @EnableZuulProxy //开启Zuul路由网关 public class ZuulApplication_8888 { public static void main(String[] args) { SpringApplication.run(ZuulApplication_8888.class,args); //http://www.xxx.com:8888/mydept/dept/get/1 } } 开启Zuul路由网关后，可通过：域名+端口号 访问微服务项目
Hystrix服务降级A、B、C三台服务器，A在某一时间段的负载很重，急需扩容，而此时间段B、C负载很小或没有访问量，需关闭其中的服务器，拿去给A扩容，以降低负载，同时设置fallback回调，让这个时间段需要访问B、C服务器的用户得到“服务暂时不可用或关闭”的信息，这个过程可以简单的比喻为服务降级。1、API接口设置fallback回调pojopackage com.sw.springcloud.pojo; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import java.io.Serializable; /** * @Author suaxi * @Date 2020/12/28 21:07 */ @Data @NoArgsConstructor @Accessors(chain = true) //链式编程 public class Dept implements Serializable { private Long deptno; private String dname; private String db_source; //数据存在哪个数据库 public Dept(String dname) { this.dname = dname; } } service接口package com.sw.springcloud.service; import com.sw.springcloud.pojo.Dept; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import java.util.List; /** * @Author suaxi * @Date 2020/12/29 20:02 */ @Component @FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT",fallbackFactory = DeptClientServiceFallbackFactory.class) public interface DeptClientService { @GetMapping("/dept/get/{id}") public Dept findById(@PathVariable("id")Long id); @GetMapping("/dept/list") public List<Dept> findAll(); @PostMapping("/dept/add") public boolean addDept(Dept dept); } FallbackFactoryService服务降级设置package com.sw.springcloud.service; import com.sw.springcloud.pojo.Dept; import feign.hystrix.FallbackFactory; import org.springframework.stereotype.Component; import java.util.List; /** * @Author suaxi * @Date 2020/12/30 9:33 * 服务降级 */ @Component public class DeptClientServiceFallbackFactory implements FallbackFactory { @Override public Object create(Throwable throwable) { return new DeptClientService() { @Override public Dept findById(Long id) { return new Dept() .setDeptno(id) .setDname("未查询到数据，该服务现已被关闭") .setDb_source("没有数据库信息"); } @Override public List<Dept> findAll() { return null; } @Override public boolean addDept(Dept dept) { return false; } }; } /* 服务熔断：服务端，某个服务超时或异常，引起熔断（类似于保险丝） 服务降级：客户端，从系统整体负载考虑，当某个服务熔断或关闭之后，服务将不再被调用 此时在客户端可以设置一个回调FallbackFactory，返回一个默认的缺省值，可以 提升用户体验，但服务质量随之下降 */ } 2、消费者端设置appliction.yml配置服务降级server: port: 80 eureka: client: register-with-eureka: false #不向注册中心注册自己（消费者端） service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ #开启降级feign.hystrix feign: hystrix: enabled: true将RestTemplate注册到Spring容器中package com.sw.springcloud.config; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; /** * @Author suaxi * @Date 2020/12/28 22:15 */ @Configuration public class ConfigBean { @Bean @LoadBalanced //注册Ribbon public RestTemplate getRestTemplate(){ return new RestTemplate(); } } SpringBoot启动类package com.sw.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.ComponentScan; /** * @Author suaxi * @Date 2020/12/28 22:29 */ @SpringBootApplication @EnableEurekaClient @EnableFeignClients(basePackages = {"com.sw.springcloud"}) public class FeignDeptConsumer_80 { public static void main(String[] args) { SpringApplication.run(FeignDeptConsumer_80.class,args); } } 用户正常访问服务：微服务节点出现问题，服务降级：
Hystrix服务熔断服务熔断：熔断机制是对应雪崩效应的一种微服务链路保护机制。当某个微服务不可用或响应时间太长，会进行服务的降级，进而熔断该节点微服务的调用，快速返回“错误的响应信息”，当该节点的调用恢复正常之后注册中心将其恢复至调用链路。Spring Cloud的熔断机制通过Hystrix实现，它会监控微服务间的调用状况，当失败的调用到一定阈值（缺省5秒内20次调用失败），就会启动熔断机制。具体实例：1、导入依赖<dependencies> <dependency> <groupId>com.sw</groupId> <artifactId>springcloud-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.4.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> <version>1.4.7.RELEASE</version> </dependency> </dependencies>2、daopackage com.sw.springcloud.dao; import com.sw.springcloud.pojo.Dept; import org.apache.ibatis.annotations.Mapper; import org.springframework.stereotype.Repository; import java.util.List; /** * @Author suaxi * @Date 2020/12/28 21:34 */ @Mapper @Repository public interface DeptDao { public boolean addDept(Dept dept); public Dept findDept(Long id); public List<Dept> findAll(); } 3、Service接口package com.sw.springcloud.service; import com.sw.springcloud.pojo.Dept; import java.util.List; /** * @Author suaxi * @Date 2020/12/28 21:34 */ public interface DeptService { public boolean addDept(Dept dept); public Dept findDept(Long id); public List<Dept> findAll(); } Service实现：package com.sw.springcloud.service; import com.sw.springcloud.dao.DeptDao; import com.sw.springcloud.pojo.Dept; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; /** * @Author suaxi * @Date 2020/12/28 21:42 */ @Service public class DeptServiceImpl implements DeptService{ @Autowired private DeptDao deptDao; @Override public boolean addDept(Dept dept) { return deptDao.addDept(dept); } @Override public Dept findDept(Long id) { return deptDao.findDept(id); } @Override public List<Dept> findAll() { return deptDao.findAll(); } } 4、controller注：hystrix熔断需开启注解@HystrixCommand(fallbackMethod = "hystrixGet")，且提供熔断后的备选方法package com.sw.springcloud.controller; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.sw.springcloud.pojo.Dept; import com.sw.springcloud.service.DeptService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; /** * @Author suaxi * @Date 2020/12/28 21:44 * 提供RestFul服务 */ @RestController public class DeptController { @Autowired private DeptService deptService; @GetMapping("/dept/get/{id}") @HystrixCommand(fallbackMethod = "hystrixGet") public Dept get(@PathVariable("id")Long id){ Dept dept = deptService.findDept(id); if (dept==null){ throw new RuntimeException("不存在id为"+id+"的用户，或者信息无法找到"); } return dept; } //熔断后的备选方法 public Dept hystrixGet(@PathVariable("id")Long id){ Dept dept = deptService.findDept(id); return new Dept() .setDeptno(id) .setDname("不存在id为"+id+"的用户，或者信息无法找到,null-->@Hystrix") .setDb_source("No message in MySQL"); } } 5、SpringBoot启动类开启Hystrix注解支持package com.sw.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; /** * @Author suaxi * @Date 2020/12/28 21:49 */ @SpringBootApplication @EnableEurekaClient //服务启动后自动注册到Eureka中 @EnableDiscoveryClient //开启服务发现 @EnableCircuitBreaker //开启熔断器支持 public class DeptProviserHystrix_8088 { public static void main(String[] args) { SpringApplication.run(DeptProviserHystrix_8088.class,args); } } 6、appliction.yml配置（此处以单个节点配置为例，实际开发中的微服务节点不止一个）server: port: 8088 #mybatis配置 mybatis: type-aliases-package: com.sw.springcloud.pojo config-location: classpath:mybatis/mybatis-config.xml mapper-locations: classpath:mybatis/mapper/*.xml #Spring配置 spring: application: name: springcloud-provider-dept datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db01?useUnicode=true&character=UFT-8 username: root password: 123456 #配置Eureka，配置服务注册到哪里 eureka: client: service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ instance: instance-id: springcloud-provider_dept-hystrix_8088 #修改Eureka默认描述信息 #info配置 info: app.name: springcloud-demo company.name: suaxi 当整个微服务调用出现问题时，前端反馈给用户的信息不是错误代码，而是熔断后的备选方法中定义的信息（即快速返回出现错误的响应信息）。用户正常查询信息：查询数据库中不存在的信息，即服务调用出现异常：
Feign负载均衡Feign是声明式的web service客户端，Spring Cloud集成了Ribbon和Eureka，可以在使用Feign时提供负载均衡的http客户端。相较于Ribbon，Feign通过创建接口和注解使用，即可完成对服务提供方的接口绑定，简化了使用Spring Cloud Ribbon时，自动封装服务调用客户端的开发量。具体实例：1、导入依赖<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> <version>1.4.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> <version>1.4.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.4.7.RELEASE</version> </dependency> <dependency> <groupId>com.sw</groupId> <artifactId>springcloud-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>2、Controllerpackage com.sw.springcloud.controller; import com.sw.springcloud.pojo.Dept; import com.sw.springcloud.service.DeptClientService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.util.List; /** * @Author suaxi * @Date 2020/12/28 22:16 */ @RestController public class DeptConsumerController { @Autowired private DeptClientService client; @RequestMapping(value = "/consumer/dept/add",produces = {"application/json;charset=UTF-8"}) public boolean add(Dept dept){ return this.client.addDept(dept); } @RequestMapping(value = "/consumer/dept/get/{id}",produces = {"application/json;charset=UTF-8"}) public Dept get(@PathVariable("id")Long id){ return this.client.findById(id); } @RequestMapping(value = "/consumer/dept/list",produces = {"application/json;charset=UTF-8"}) public List<Dept> list(){ return this.client.findAll(); } } 3、注册到Spring容器中package com.sw.springcloud.config; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; /** * @Author suaxi * @Date 2020/12/28 22:15 */ @Configuration public class ConfigBean { @Bean @LoadBalanced //注册Ribbon public RestTemplate getRestTemplate(){ return new RestTemplate(); } } 4、SpringBoot启动类开启Feign注解package com.sw.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.ComponentScan; /** * @Author suaxi * @Date 2020/12/28 22:29 */ @SpringBootApplication @EnableEurekaClient @EnableFeignClients(basePackages = {"com.sw.springcloud"}) public class FeignDeptConsumer_80 { public static void main(String[] args) { SpringApplication.run(FeignDeptConsumer_80.class,args); } }