Spring Cloud 基础(Greenwich版)
基础环境
parent-demo
pom.xml
<parent> <groupId>org.springframework.boot
groupId> <artifactId>spring-boot-starter-parent
artifactId> <version>2.1.6.RELEASE
version>
parent> <groupId>com.demo
groupId> <artifactId>parent-demo
artifactId> <version>1.0-SNAPSHOT
version> <dependencies> <dependency> <groupId>org.springframework.boot
groupId> <artifactId>spring-boot-starter
artifactId>
dependency> <dependency> <groupId>org.springframework.boot
groupId> <artifactId>spring-boot-starter-web
artifactId>
dependency> <dependency> <groupId>org.projectlombok
groupId> <artifactId>lombok
artifactId>
dependency>
dependencies>
user-demo-01
pom.xml
<parent> <groupId>com.demo
groupId> <artifactId>parent-demo
artifactId> <version>1.0-SNAPSHOT
version>
parent> <artifactId>user-demo-01
artifactId>
application.yml
spring: application: name: user-demo datasource: url: jdbc:mysql:///bank?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC username: root password: server: port: 8091
controller
@RestController @RequestMapping("/user") public class UserController {
@Autowired private UserDao userDao; @RequestMapping("/{id}") public User findUser(@PathVariable Integer id) {
return userDao.findById(id).orElse(null); } }
gift-demo
pom.xml
<parent> <groupId>com.demo
groupId> <artifactId>parent-demo
artifactId> <version>1.0-SNAPSHOT
version>
parent> <artifactId>gift-demo
artifactId>
application.yml
spring: application: name: gift-demo server: port: 8090
gift-demo 利用 RestTemplate 远程调用 user-demo-01 的接口
@Bean public RestTemplate restTemplate() {
return new RestTemplate(); }
@RestController @RequestMapping("/gift") public class GiftController {
@Autowired private RestTemplate restTemplate; @RequestMapping("/{id}") public User findUser(@PathVariable Integer id) {
String url = "http://127.0.0.1:8091/user/{id}"; URI uri = UriComponentsBuilder.fromHttpUrl(url).build(id); return restTemplate.getForObject(uri, User.class); } }
Eureka 服务注册中心
parent-demo 的 pom.xml 加入
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud
groupId> <artifactId>spring-cloud-dependencies
artifactId> <version>Greenwich.RELEASE
version> <type>pom
type> <scope>import
scope>
dependency>
dependencies>
dependencyManagement>
Eureka Server 服务端
pom.xml
<dependency> <groupId>org.glassfish.jaxb
groupId> <artifactId>jaxb-runtime
artifactId>
dependency>
Eureka服务器所依赖的JAXB模块已在JDK 11中删除。如果要在运行Eureka服务器时使用JDK 11,则必须在POM或Gradle文件中包括这些依赖项。
<dependencies> <dependency> <groupId>org.springframework.cloud
groupId> <artifactId>spring-cloud-starter-netflix-eureka-server
artifactId>
dependency>
dependencies>
Eureka Server 高可用
本地搭建的时候修改 windows 的 hosts 文件 C:\Windows\System32\drivers\etc
application.yml
server: port: 8701 spring: application: name: eureka-server eureka: instance: hostname: eureka-server-01 client: service-url: defaultZone: http://eureka-server-02:8702/eureka/ ============================================================================================================ server: port: 8702 spring: application: name: eureka-server eureka: instance: hostname: eureka-server-02 client: service-url: defaultZone: http://eureka-server-01:8701/eureka/
@SpringBootApplication @EnableEurekaServer public class EurekaServer01Application {
public static void main(String[] args) {
SpringApplication.run(EurekaServer01Application.class, args); } } ============================================================================================================ @SpringBootApplication @EnableEurekaServer public class EurekaServer02Application {
public static void main(String[] args) {
SpringApplication.run(EurekaServer02Application.class, args); } }
Eureka Client 客户端
pom.xml
<dependency> <groupId>org.springframework.cloud
groupId> <artifactId>spring-cloud-starter-netflix-eureka-client
artifactId>
dependency>
application.yml
eureka: client: service-url: defaultZone: http://eureka-server-01:8701/eureka,http://eureka-server-02:8702/eureka
Ribbon 负载均衡
复制一份 user-demo-01
RestTemplate 上添加注解 @LoadBalanced
@Bean @LoadBalanced public RestTemplate restTemplate() {
return new RestTemplate(); }
设置 user-demo 微服务的请求方式为随机
user-demo: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
Hystrix 熔断器
操作 gift-demo 微服务(服务的消费方)
pom.xml
<dependency> <groupId>org.springframework.cloud
groupId> <artifactId>spring-cloud-starter-netflix-hystrix
artifactId>
dependency>
启动类要添加 @EnableCircuitBreaker 注解
@RequestMapping("/portTimeout") // 设置该方法的请求超时时间为 2000ms;默认 1000ms @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000") // Hystrix 的仓壁模式: // 设置 threadPoolKey 以便每个方法都会有自己的线程池;否则所有标注了 @HystrixCommand 的方法公用一个线程池 @HystrixCommand( threadPoolKey = "portTimeout", threadPoolProperties = {
@HystrixProperty(name = "coreSize",value = "1"), @HystrixProperty(name = "maxQueueSize",value = "20") }, // 兜底方法 fallbackMethod = "portTimeoutFallBack") public Integer portTimeout() {
String url = "http://user-demo/user/port"; URI uri = UriComponentsBuilder.fromHttpUrl(url).build().toUri(); return restTemplate.getForObject(uri, Integer.class); } // 当 portTimeout 调用出现超时的时候;会返回该方法的返回值 public Integer portTimeoutFallBack() {
return -1; }
Open Fegin 远程调用
Open Fegin = RestTemplate + Ribbon + Hystrix
pom.xml
<dependency> <groupId>org.springframework.cloud
groupId> <artifactId>spring-cloud-starter-openfeign
artifactId>
dependency>
启动类要添加 @EnableFeignClients 注解
@FeignClient(name = "user-demo",fallback = UserApiFallback.class,path = "/user") public interface UserApi {
//调⽤的请求路径 @RequestMapping(value = "/port") Integer port(); }
打开 Open Fegin 对于 Hystrix 熔断的支持
application.yml
feign: hystrix: enabled: true
@Component public class UserApiFallback implements UserApi {
@Override public Integer port() {
return -2; } }
远程调用
@Autowired private UserApi userApi; public Integer portTimeout() {
return userApi.port(); }
GateWay 网关
Spring Cloud 网关需要 Spring Boot 和 Spring Webflux 提供的 Netty 运行时。它不能在传统的 Servlet 容器中或作为 WAR 构建。
pom.xml
<dependency> <groupId>org.springframework.cloud
groupId> <artifactId>spring-cloud-starter-netflix-eureka-client
artifactId>
dependency> <dependency> <groupId>org.springframework.cloud
groupId> <artifactId>spring-cloud-starter-gateway
artifactId>
dependency>
application.yml
server: port: 9200 spring: application: name: gateway-server cloud: gateway: routes: # 路由id 保持唯一 - id: user-route # 负载均衡的地址 uri: lb://user-demo # 根据请求路径进行过滤 predicates: - Path=/user/ - id: gift-route uri: lb://gift-demo predicates: - Path=/gift/ eureka: client: service-url: defaultZone: http://eureka-server-01:8701/eureka,http://eureka-server-02:8702/eureka
全局过滤器
@Component public class GlobalFilterConfig implements GlobalFilter, Ordered {
@Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest(); String host = request.getRemoteAddress().getHostString(); System.out.println(host); return chain.filter(exchange); } @Override public int getOrder() {
// 返回值表示当前过滤器的顺序(优先级),数值越⼩,优先级越⾼ return 0; } }
Spring Cloud Config 配置中心
Config Server 服务端
pom.xml
<dependency> <groupId>org.springframework.cloud
groupId> <artifactId>spring-cloud-starter-netflix-eureka-client
artifactId>
dependency> <dependency> <groupId>org.springframework.cloud
groupId> <artifactId>spring-cloud-config-server
artifactId>
dependency>
启动类需要添加 @EnableConfigServer 注解
application.yml
server: port: 9600 spring: application: name: config-server cloud: config: server: git: # git 的信息 uri: https://gitee.com/zhangyizhou/learning-config-server.git username: root password: # 分支 label: master eureka: client: service-url: defaultZone: http://eureka-server-01:8701/eureka,http://eureka-server-02:8702/eureka
Config Server 客户端
将 application.yml 改为 bootstrap.yml
bootstrap.yml
spring: cloud: config: name: local-config uri: http://127.0.0.1:9600
配置文件更新后 手动刷新
- 在使用到配置文件的类上添加 @RefreshScope 注解
- 添加 actuator 依赖
- post 方式调用 http://127.0.0.1:8092/actuator/refresh 接口
pom.xml
<dependency> <groupId>org.springframework.boot
groupId> <artifactId>spring-boot-starter-actuator
artifactId>
dependency>
bootstrap.yml
management: endpoints: web: exposure: include: "*"
Sleuth + Zipkin 分布式链路追踪
Sleuth
pom.xml
<dependency> <groupId>org.springframework.cloud
groupId> <artifactId>spring-cloud-starter-zipkin
artifactId>
dependency>
调整日志等级
application.yml
logging: level: org.springframework.web.servlet.DispatcherServlet: debug org.springframework.cloud.sleuth: debug
Zipkin Server 服务端
pom.xml
<dependency> <groupId>io.zipkin.java
groupId> <artifactId>zipkin-server
artifactId>
<exclusions> <exclusion> <groupId>org.springframework.boot
groupId> <artifactId>spring-boot-starter-log4j2
artifactId>
exclusion>
exclusions> <version>2.12.3
version>
dependency> <dependency> <groupId>io.zipkin.java
groupId> <artifactId>zipkin-autoconfigure-ui
artifactId> <version>2.12.3
version>
dependency>
启动类需要 @EnableZipkinServer 注解
server: port: 9400 management: metrics: web: server: # 需要设置为 false auto-time-requests: false
Zipkin Client 客户端
pom.xml
<dependency> <groupId>org.springframework.cloud
groupId> <artifactId>spring-cloud-starter-zipkin
artifactId>
dependency>
application.yml
spring: zipkin: # zipkin server 的地址 base-url: http://127.0.0.1:9400 sender: # 通过什么方式发送到 server 端 type: web sleuth: sampler: # 日志的采样率;默认为 0.1 probability: 1
Zipkin Server 链路持久化到 MySQL
pom.xml
<dependency> <groupId>io.zipkin.java
groupId> <artifactId>zipkin-autoconfigure-storage-mysql
artifactId> <version>2.12.3
version>
dependency> <dependency> <groupId>mysql
groupId> <artifactId>mysql-connector-java
artifactId>
dependency> <dependency> <groupId>org.springframework
groupId> <artifactId>spring-jdbc
artifactId>
dependency>
application.yml
spring: datasource: url: jdbc:mysql://localhost:3306/zipkin?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC username: root password: zipkin: storage: # 指定存储方式 type: mysql
CREATE TABLE IF NOT EXISTS zipkin_spans ( `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit', `trace_id` BIGINT NOT NULL, `id` BIGINT NOT NULL, `name` VARCHAR(255) NOT NULL, `remote_service_name` VARCHAR(255), `parent_id` BIGINT, `debug` BIT(1), `start_ts` BIGINT COMMENT 'Span.timestamp(): epoch micros used for endTs query and to implement TTL', `duration` BIGINT COMMENT 'Span.duration(): micros used for minDuration and maxDuration query', PRIMARY KEY (`trace_id_high`, `trace_id`, `id`) ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci; ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTracesByIds'; ALTER TABLE zipkin_spans ADD INDEX(`name`) COMMENT 'for getTraces and getSpanNames'; ALTER TABLE zipkin_spans ADD INDEX(`remote_service_name`) COMMENT 'for getTraces and getRemoteServiceNames'; ALTER TABLE zipkin_spans ADD INDEX(`start_ts`) COMMENT 'for getTraces ordering and range'; CREATE TABLE IF NOT EXISTS zipkin_annotations ( `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit', `trace_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.trace_id', `span_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.id', `a_key` VARCHAR(255) NOT NULL COMMENT 'BinaryAnnotation.key or Annotation.value if type == -1', `a_value` BLOB COMMENT 'BinaryAnnotation.value(), which must be smaller than 64KB', `a_type` INT NOT NULL COMMENT 'BinaryAnnotation.type() or -1 if Annotation', `a_timestamp` BIGINT COMMENT 'Used to implement TTL; Annotation.timestamp or zipkin_spans.timestamp', `endpoint_ipv4` INT COMMENT 'Null when Binary/Annotation.endpoint is null', `endpoint_ipv6` BINARY(16) COMMENT 'Null when Binary/Annotation.endpoint is null, or no IPv6 address', `endpoint_port` SMALLINT COMMENT 'Null when Binary/Annotation.endpoint is null', `endpoint_service_name` VARCHAR(255) COMMENT 'Null when Binary/Annotation.endpoint is null' ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci; ALTER TABLE zipkin_annotations ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `span_id`, `a_key`, `a_timestamp`) COMMENT 'Ignore insert on duplicate'; ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`, `span_id`) COMMENT 'for joining with zipkin_spans'; ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTraces/ByIds'; ALTER TABLE zipkin_annotations ADD INDEX(`endpoint_service_name`) COMMENT 'for getTraces and getServiceNames'; ALTER TABLE zipkin_annotations ADD INDEX(`a_type`) COMMENT 'for getTraces and autocomplete values'; ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT 'for getTraces and autocomplete values'; ALTER TABLE zipkin_annotations ADD INDEX(`trace_id`, `span_id`, `a_key`) COMMENT 'for dependencies job'; CREATE TABLE IF NOT EXISTS zipkin_dependencies ( `day` DATE NOT NULL, `parent` VARCHAR(255) NOT NULL, `child` VARCHAR(255) NOT NULL, `call_count` BIGINT, `error_count` BIGINT, PRIMARY KEY (`day`, `parent`, `child`) ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
Spring Cloud OAuh2 微服务统一认证
Authorization Server 认证服务器
pom.xml
<dependencies> <dependency> <groupId>org.springframework.cloud
groupId> <artifactId>spring-cloud-starter-netflix-eureka-client
artifactId>
dependency> <dependency> <groupId>org.springframework.cloud
groupId> <artifactId>spring-cloud-starter-oauth2
artifactId> <exclusions> <exclusion> <groupId>org.springframework.security.oauth.boot
groupId> <artifactId>spring-security-oauth2-autoconfigure
artifactId>
exclusion>
exclusions>
dependency> <dependency> <groupId>org.springframework.security.oauth.boot
groupId> <artifactId>spring-security-oauth2-autoconfigure
artifactId> <version>2.1.11.RELEASE
version>
dependency>
dependencies>
application.yml
server: port: 9999 spring: application: name: oauth-server eureka: client: service-url: defaultZone: http://eureka-server-01:8701/eureka,http://eureka-server-02:8702/eureka
@Configuration @EnableAuthorizationServer public class OauthServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired private AuthenticationManager authenticationManager; @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// 允许客户端表单认证 security.allowFormAuthenticationForClients() // 开启端口 /oauth/token_key 的访问权限(允许) .tokenKeyAccess("permitAll()") // 开启端口 /oauth/check_token 的访问权限(允许) .checkTokenAccess("permitAll()"); } // 客户端详情配置 @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 客户端信息存储在什么地方 clients.inMemory() // 添加 client 的配置 .withClient("admin").secret("") // 指定客户端能够访问的资源 id 清单 需要和资源服务器上配置的一致 .resourceIds("gift") // 认证类型/令牌颁发模式 .authorizedGrantTypes("password", "refresh_token") // 客户端的权限范围 .scopes("all"); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 指定 token 的存储方式 endpoints.tokenStore(tokenStore()) // token 生成的细节 .tokenServices(authorizationServerTokenServices()) // 指定认证管理器 .authenticationManager(authenticationManager) .allowedTokenEndpointRequestMethods(HttpMethod.POST, HttpMethod.GET); } public TokenStore tokenStore() {
return new InMemoryTokenStore(); } public AuthorizationServerTokenServices authorizationServerTokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); // 是否开启令牌刷新 defaultTokenServices.setSupportRefreshToken(true); defaultTokenServices.setTokenStore(tokenStore()); // 设置令牌的有效时间 defaultTokenServices.setAccessTokenValiditySeconds(20); // 设置刷新令牌的有效时间 defaultTokenServices.setRefreshTokenValiditySeconds(3600 * 24 * 3); return defaultTokenServices; } }
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception {
UserDetails user = new User("root", "", new ArrayList<>()); auth.inMemoryAuthentication().withUser(user).passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance(); } }
Resource Server 资源服务器
pom.xml
<dependency> <groupId>org.springframework.cloud
groupId> <artifactId>spring-cloud-starter-oauth2
artifactId> <exclusions> <exclusion> <groupId>org.springframework.security.oauth.boot
groupId> <artifactId>spring-security-oauth2-autoconfigure
artifactId>
exclusion>
exclusions>
dependency> <dependency> <groupId>org.springframework.security.oauth.boot
groupId> <artifactId>spring-security-oauth2-autoconfigure
artifactId> <version>2.1.11.RELEASE
version>
dependency>
@Configuration @EnableResourceServer @EnableWebSecurity public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("gift"); RemoteTokenServices remoteTokenServices = new RemoteTokenServices(); // 认证服务器的地址 remoteTokenServices.setCheckTokenEndpointUrl("http://localhost:9999/oauth/check_token"); remoteTokenServices.setClientId("admin"); remoteTokenServices.setClientSecret(""); resources.tokenServices(remoteTokenServices); } @Override public void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) .and().authorizeRequests() .antMatchers("/gift/").authenticated() .antMatchers("/user/").authenticated() .anyRequest().permitAll(); } }
JWT 引入
RemoteTokenServices 远程请求授权服务验证 token,如果访问量较⼤将会影响系统的性能。
认证服务器改造
private final String sign_key = "sign_key"; public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter()); } public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter(); jwtAccessTokenConverter.setSigningKey(sign_key); // 签名密钥 jwtAccessTokenConverter.setVerifier(new MacSigner(sign_key)); // 验证时使用的密钥,和签名密钥保持一致 return jwtAccessTokenConverter; } public AuthorizationServerTokenServices authorizationServerTokenServices() {
// ... defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter()); // ... }
资源服务器改造
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("gift").tokenStore(tokenStore()).stateless(true); }
从数据库读取信息
客户端详情配置
pom.xml
<dependency> <groupId>mysql
groupId> <artifactId>mysql-connector-java
artifactId>
dependency> <dependency> <groupId>org.springframework.boot
groupId> <artifactId>spring-boot-starter-data-jpa
artifactId>
dependency>
application.yml
spring: datasource: url: jdbc:mysql:///bank?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC username: root password:
oauth_client_details
CREATE TABLE `oauth_client_details` ( `client_id` varchar(48) NOT NULL, `resource_ids` varchar(256) DEFAULT NULL, `client_secret` varchar(256) DEFAULT NULL, `scope` varchar(256) DEFAULT NULL, `authorized_grant_types` varchar(256) DEFAULT NULL, `web_server_redirect_uri` varchar(256) DEFAULT NULL, `authorities` varchar(256) DEFAULT NULL, `access_token_validity` int(11) DEFAULT NULL, `refresh_token_validity` int(11) DEFAULT NULL, `additional_information` varchar(4096) DEFAULT NULL, `autoapprove` varchar(256) DEFAULT NULL, PRIMARY KEY (`client_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
// 客户端详情配置 @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(jdbcClientDetailsService()); } @Autowired private DataSource dataSource; @Bean public JdbcClientDetailsService jdbcClientDetailsService() {
return new JdbcClientDetailsService(dataSource); }
客户端认证配置
public interface PayingUserDao extends JpaRepository<PayingUser, Integer> {
}
@Service public class PayingUserService implements UserDetailsService {
@Autowired private PayingUserDao payingUserDao; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 基于数据库进行验证 PayingUser payingUser = new PayingUser(); payingUser.setUsername(username); Example<PayingUser> payingUserExample = Example.of(payingUser); Optional<PayingUser> payingUserOptional = payingUserDao.findOne(payingUserExample); if (payingUserOptional.isEmpty()) {
// 用户名没有找到 System.out.println(username + "没有找到..."); throw new UsernameNotFoundException(username); } PayingUser user = payingUserOptional.get(); // 权限集合 Collection<GrantedAuthority> authorities = new ArrayList<>(); return new User(user.getUsername(), user.getPassword(), authorities); } }
@Autowired private PayingUserService payingUserService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(payingUserService).passwordEncoder(passwordEncoder()); }
自定义 JWT 数据
认证服务器添加数据
@Component public class CustomAccessTokenConverter extends DefaultAccessTokenConverter {
@Override public Map<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); String remoteAddr = requestAttributes.getRequest().getRemoteAddr(); Map<String, String> map = (Map<String, String>) super.convertAccessToken(token, authentication); map.put("clientIp", remoteAddr); return map; } }
资源服务器取出数据
@Component public class CustomAccessTokenConverter extends DefaultAccessTokenConverter {
@Override public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
OAuth2Authentication oAuth2Authentication = super.extractAuthentication(map); oAuth2Authentication.setDetails(map); return oAuth2Authentication; } }
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) SecurityContextHolder.getContext().getAuthentication().getDetails(); Map<String,Object> decodedDetails = (Map<String, Object>) details.getDecodedDetails(); System.out.println(decodedDetails.get("clientIp"));
参考git:https://gitee.com/zhangyizhou/learning-spring-cloud-greenwich-demo.git
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/210846.html原文链接:https://javaforall.net
