Spring Cloud 基础(Greenwich版)

Spring Cloud 基础(Greenwich版)

基础环境

parent-demo

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>

user-demo-01

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);
    }
}

gift-demo

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

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.cloudgroupId>
            <artifactId>spring-cloud-dependenciesartifactId>
            <version>Greenwich.RELEASEversion>
            <type>pomtype>
            <scope>importscope>
        dependency>
    dependencies>
dependencyManagement>

Eureka Server 服务端

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>
Eureka Server 高可用

本地搭建的时候修改 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);
    }
}

Eureka Client 客户端

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

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.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 远程调用

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();
}

GateWay 网关

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;
    }
}

Spring Cloud Config 配置中心

Config Server 服务端

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

Config Server 客户端

将 application.yml 改为 bootstrap.yml

bootstrap.yml

spring:  
  cloud:
    config:
      name: local-config
      uri: http://127.0.0.1:9600
配置文件更新后 手动刷新
  1. 在使用到配置文件的类上添加 @RefreshScope 注解

  2. 添加 actuator 依赖

  3. 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: "*"

Sleuth + Zipkin 分布式链路追踪

Sleuth

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

Zipkin Server 服务端

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

Zipkin Client 客户端

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

Zipkin Server 链路持久化到 MySQL

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;

Spring Cloud OAuh2 微服务统一认证

Authorization Server 认证服务器

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();
    }
}

Resource Server 资源服务器

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();
    }
}

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>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());
}

自定义 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

你可能感兴趣的:(spring,cloud,spring,boot,java)