pom.xml
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.6.RELEASEversion>
parent>
<groupId>com.demogroupId>
<artifactId>parent-demoartifactId>
<version>1.0-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
dependencies>
pom.xml
<parent>
<groupId>com.demogroupId>
<artifactId>parent-demoartifactId>
<version>1.0-SNAPSHOTversion>
parent>
<artifactId>user-demo-01artifactId>
application.yml
spring:
application:
name: user-demo
datasource:
url: jdbc:mysql:///bank?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
username: root
password: 123456
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);
}
}
pom.xml
<parent>
<groupId>com.demogroupId>
<artifactId>parent-demoartifactId>
<version>1.0-SNAPSHOTversion>
parent>
<artifactId>gift-demoartifactId>
application.yml
spring:
application:
name: gift-demo
server:
port: 8090
@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);
}
}
parent-demo 的 pom.xml 加入
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Greenwich.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
pom.xml
<dependency>
<groupId>org.glassfish.jaxbgroupId>
<artifactId>jaxb-runtimeartifactId>
dependency>
Eureka服务器所依赖的JAXB模块已在JDK 11中删除。如果要在运行Eureka服务器时使用JDK 11,则必须在POM或Gradle文件中包括这些依赖项。
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
dependencies>
本地搭建的时候修改 windows 的 hosts 文件 C:\Windows\System32\drivers\etc
127.0.0.1 eureka-server-01
127.0.0.1 eureka-server-02
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);
}
}
pom.xml
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
application.yml
eureka:
client:
service-url:
defaultZone: http://eureka-server-01:8701/eureka,http://eureka-server-02:8702/eureka
复制一份 user-demo-01
RestTemplate 上添加注解 @LoadBalanced
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
设置 user-demo 微服务的请求方式为随机
user-demo:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
操作 gift-demo 微服务(服务的消费方)
pom.xml
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
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 = RestTemplate + Ribbon + Hystrix
pom.xml
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
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();
}
Spring Cloud 网关需要 Spring Boot 和 Spring Webflux 提供的 Netty 运行时。它不能在传统的 Servlet 容器中或作为 WAR 构建。
pom.xml
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
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;
}
}
pom.xml
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-config-serverartifactId>
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: 123456
# 分支
label: master
eureka:
client:
service-url:
defaultZone: http://eureka-server-01:8701/eureka,http://eureka-server-02:8702/eureka
将 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.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
bootstrap.yml
management:
endpoints:
web:
exposure:
include: "*"
pom.xml
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-zipkinartifactId>
dependency>
调整日志等级
application.yml
logging:
level:
org.springframework.web.servlet.DispatcherServlet: debug
org.springframework.cloud.sleuth: debug
pom.xml
<dependency>
<groupId>io.zipkin.javagroupId>
<artifactId>zipkin-serverartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-log4j2artifactId>
exclusion>
exclusions>
<version>2.12.3version>
dependency>
<dependency>
<groupId>io.zipkin.javagroupId>
<artifactId>zipkin-autoconfigure-uiartifactId>
<version>2.12.3version>
dependency>
启动类需要 @EnableZipkinServer 注解
server:
port: 9400
management:
metrics:
web:
server:
# 需要设置为 false
auto-time-requests: false
pom.xml
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-zipkinartifactId>
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
pom.xml
<dependency>
<groupId>io.zipkin.javagroupId>
<artifactId>zipkin-autoconfigure-storage-mysqlartifactId>
<version>2.12.3version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
dependency>
application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/zipkin?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
username: root
password: 123456
zipkin:
storage:
# 指定存储方式
type: mysql
zipkin 库的建表语句见:
https://github.com/openzipkin/zipkin/blob/master/zipkin-storage/mysql-v1/src/main/resources/mysql.sql
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;
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-oauth2artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.security.oauth.bootgroupId>
<artifactId>spring-security-oauth2-autoconfigureartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.security.oauth.bootgroupId>
<artifactId>spring-security-oauth2-autoconfigureartifactId>
<version>2.1.11.RELEASEversion>
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("123456")
// 指定客户端能够访问的资源 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", "123456", new ArrayList<>());
auth.inMemoryAuthentication().withUser(user).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
pom.xml
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-oauth2artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.security.oauth.bootgroupId>
<artifactId>spring-security-oauth2-autoconfigureartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.security.oauth.bootgroupId>
<artifactId>spring-security-oauth2-autoconfigureartifactId>
<version>2.1.11.RELEASEversion>
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("123456");
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();
}
}
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>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
application.yml
spring:
datasource:
url: jdbc:mysql:///bank?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
username: root
password: 123456
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());
}
@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