一、介绍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
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); } }
RibbonSpring Cloud Ribbon是基于NetFlix Ribbon实现的客户端负载均衡工具主要功能是提供客户端的软件负载均衡算法，将NetFlix的中间层服务连接在一起。在配置文件中列出LoadBalancer后面的所有机器，Ribbon会自动帮助你基于某些规则（轮询、随机数轮询、随机连接等）去连接这些服务。自定义算法：package com.sw.myrule; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; import java.util.List; import java.util.concurrent.ThreadLocalRandom; /** * @Author suaxi * @Date 2020/12/29 16:44 */ public class MyRandomRule extends AbstractLoadBalancerRule { //自定义算法：每个服务访问5次之后，换下一个 private int total = 0; //被调用的次数 private int currentIndex = 0; //当前是谁在提供服务 public Server choose(ILoadBalancer lb, Object key) { if (lb == null) { return null; } Server server = null; while (server == null) { if (Thread.interrupted()) { return null; } List<Server> upList = lb.getReachableServers(); //获取活着的服务 List<Server> allList = lb.getAllServers(); //获取所有服务 int serverCount = allList.size(); if (serverCount == 0) { return null; } // int index = chooseRandomInt(serverCount); //生成区间随机数 // server = upList.get(index); //从活着的服务中，随机获取一个 if (total<5){ server = upList.get(currentIndex); total++; }else { total = 0; currentIndex++; if (currentIndex>upList.size()){ currentIndex = 0; //当提供服务的人的执行次数大于活着的服务数量时，重置为0 } server = upList.get(currentIndex); //从或者的服务中来获取指定的服务 } if (server == null) { Thread.yield(); continue; } if (server.isAlive()) { return (server); } server = null; Thread.yield(); } return server; } protected int chooseRandomInt(int serverCount) { return ThreadLocalRandom.current().nextInt(serverCount); } @Override public Server choose(Object key) { return choose(getLoadBalancer(), key); } @Override public void initWithNiwsConfig(IClientConfig clientConfig) { // TODO Auto-generated method stub } } 注册到Spring容器中package com.sw.myrule; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.RoundRobinRule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @Author suaxi * @Date 2020/12/29 16:12 */ @Configuration public class TestRule { @Bean public IRule myRule(){ return new MyRandomRule(); } } 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 { //配置负载均衡实现RestTemplate //IRule //RoundRobinRule 轮询 //RandomRule 随机数 //AvailabilityFilteringRule 先过滤不可用的服务，对剩下的进行轮询 //RetryRule 先按照轮询获取服务，如果获取服务失败，则会在指定的时间内进行重试 @Bean @LoadBalanced //注册Ribbon public RestTemplate getRestTemplate(){ return new RestTemplate(); } } 开启自定义Ribbon注解package com.sw.springcloud; import com.sw.myrule.TestRule; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.ribbon.RibbonClient; /** * @Author suaxi * @Date 2020/12/28 22:29 */ @SpringBootApplication @EnableEurekaClient //在微服务启动的时候加载自定义的Ribbon类 @RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT",configuration = TestRule.class) public class DeptConsumer_80 { public static void main(String[] args) { SpringApplication.run(DeptConsumer_80.class,args); } }
Eureka1、什么是Eureka？Eureka是Netflix开发的服务发现框架，本身是一个基于REST的服务，主要用于定位运行在AWS域中的中间层服务，以达到负载均衡和中间层服务故障转移的目的。SpringCloud将它集成在其子项目spring-cloud-netflix中，以实现SpringCloud的服务发现功能。2、Eureka包含两个组件：Eureka Server和Eureka Client。Eureka Server提供注册服务Eureka Client是一个Java客户端，用于简化与Eureka Server的交互3、Eureka自我保护机制某时刻一个为服务不可用了，Eureka不会立刻清理该服务，依旧会对该服务的信息进行保存。一般情况下，EurekaServer与微服务实例之间存在心跳机制，节点默认90秒未收到微服务的心跳则会注销该实例；当EurekaServer在短时间内丢失过多的客户端，此时这个节点就会进入自我保护模式，保护服务注册表中的信息，不会注销任何微服务，直至故障恢复当节点收到的心跳重新恢复到阈值以上时，会自动退出自我保护机制在application.yml配置中可以添加eureka.server.enable-self-preservation = false禁用自我保护机制（不推荐）4、CAP理论C（Consistency）：强一致性 A（Availability）：可用性 P（Parition tolerance）：分区容错性==一个分布式系统不可能同时满足一致性==NoSQL数据库分为CA、CP、AP三大类原则：CA：满足一致性、可用性，通常可扩展性较差CP：满足一致性，分区容错性的系统，通常性能不是特别高AP：满足可用性、分区容错性的系统，通常对一致性要求较低5、Zookeeper与EurekaZookeeper：（CP）当主节点因为网络故障或其他原因与节点失去联系时，剩余节点会进行leader选举，这个选举过程耗时较长，且在选举期间，整个集群都处于不可用状态Eureka：（AP）eureka各个节点都是平等的，只要有一个节点还在，就能保持服务注册与发现的可用性；如果在15分钟内超过85%的节点都没有正常的心跳，eureka就会认为客户端与注册中心出现了故障，会出现以下几种状况：不再从注册列表移除长时间没有收到心跳而应该过期的服务仍能接收新的服务注册与发现请求，但不会同步到其他节点上，以保证当前节点的可用性当网络稳定，故障恢复时，同步信息到其他EurekaServer节点
微服务1、什么是微服务？将传统的一站式应用，拆分为单个的服务，彻底去耦合，每一个微服务提供单个业务功能的服务，能够自行单独启动和销毁，拥有自己独立的数据库。2、微服务与微服务架构微服务强调服务的大小，它关注的是某一个点，即解决具体问题/提供对应服务的一个服务应用，类似于IDEA一个项目中的单个Moudel微服务架构强调架构模式，它提倡将单一应用程序划分成一组小的服务，服务之间互相协调，互相配合，且每个服务围绕着具体的业务进行构建，并且能被独立的部署到生产环境中。对具体的一个服务而言，应根据业务上下文，选择合适的语言，工具对其进行构建。3、现有的微服务解决方案（举例）Spring Cloud Netflix（一站式解决方案）API网关，Zuul组件Feign服务注册与发现：EurekaHystrix熔断机制Apache Dubbo Zookeeper第三方组件APIDubbo服务注册与发现：Zookeeper借助Hystrix熔断机制Spring Cloud Alibaba全家桶4、微服务优缺点优点：单一职责每个服务足够内聚，足够小，聚焦一个指定的业务功能或需求能够被2-5人的小团队单独开发耦合低，在开发或部署阶段都是独立的多语言开发易于和第三方集成，微服务允许容易且灵活的方式集成自动部署，通过持续集成工具，例如：jenkins、Hudson、bamboo微服务更多的是关注业务逻辑层面的代码，不会和前端混合每个微服务都有自己的存储能力，可以有自己的数据库，也可以有统一的数据库易于开发人员的理解，修改和维护……缺点：分布式系统较复杂多服务维护难度大、成本高系统部署依赖服务间通信成本数据一致性系统集成测试性能监控……微服务技术栈