start

Key words: Spring Cloud, Spring Boot, Eureka, Zuul, Feign, Oauth2

One for newbies on the server

Spring Cloud is a complete set of microservice framework based on Spring Boot, including service discovery registration, configuration center, message bus, load balancing, circuit breaker, data monitoring and a series of components, can be simple and fast into the pit microservice architecture.

The version of Spring Cloud corresponds to Spring Boot to some extent. Up to now, the latest stable version should be Spring Cloud Greenwich + Spring Boot 2.1.x

Service governance

Why is service governance needed

With the continuous growth of services, in order to pursue higher performance to support services, the introduction of clusters has greatly increased the complexity of service architecture. Large clusters are prone to all sorts of problems:

  1. Too many service urls are difficult to configure
  2. Load balancing Cluster deployment is also required when the nodes are overloaded
  3. Service dependencies are chaotic and startup sequence is unclear
  4. Too many services make performance indicator analysis difficult and need to be monitored

To put it simply, through service governance, services can be accessed by service name instead of url, which is conducive to load balancing and decoupling between services.

Eureka introduction

  1. Maven rely on
<! -- Server -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<! -- Client -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
Copy the code
  1. The server application. Yml
spring:
  application:
    # service name
    name: eureka-server

server:
  # port
  port: 8001

eureka:
  instance:
    hostname: localhost
  client:
    register-with-eureka: false
    fetch-registry: false
Copy the code
  1. The client application. Yml
spring:
  application:
    # service name
    name: xxx

eureka:
  client:
    serviceUrl:
      Specify the location of the service registry
      defaultZone: http://localhost:8001/eureka/
Copy the code
  1. The server annotation @enableeurekaserver
// import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

// Start a service registry for other applications to talk to
@EnableEurekaServer
@SpringBootApplication
public class EurekaServiceApplication {

    public static void main(String[] args) { SpringApplication.run(EurekaServiceApplication.class, args); }}Copy the code
  1. The client annotation @enableeurekaclient
@EnableEurekaClient
@SpringBootApplication
public class EurekaServiceApplication {

    public static void main(String[] args) { SpringApplication.run(EurekaServiceApplication.class, args); }}Copy the code
  1. Discover services (View registered services)
@Slf4j
@RestController
public class DcController {

    @Autowired
    DiscoveryClient mDiscoveryClient;

    @GetMapping("/dc")
    public Result dc(a) throws Exception {
        // Discover the service
        String services = "Services: " + mDiscoveryClient.getServices();
        log.info(services);
        return ResultUtils.resultData(ResultEnum.SUCCESS, "Data returned by Eureka-client:"+ services); }}Copy the code
  1. other

The web direct access server can see which services are currently registered

ex: http://localhost:8001/

The service gateway

Introduction to the

Service gateways are an integral part of microservices architecture. The service gateway provides REST apis for external systems to implement functions such as service routing, filtering, and load balancing, as well as user authentication. Currently, Zuul and Spring Cloud Gateway are widely used. Spring Cloud Gateway relies on The Netty Runtime provided by Spring Boot and Spring Webflux, and is currently officially recommended Gateway. However, THERE is something wrong with OAuth when I use it, so I still use Zuul.

Zuul

  1. Maven rely on
    <! -- Eureka client -->
    <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-netflix-zuul</artifactId>
    </dependency>
Copy the code
  1. application.yml
server:
  port: 8002

spring:
  application:
    # specify the name of the microservice
    name: api-gateway

zuul:
  host:
    connect-timeout-millis: 20000
    socket-timeout-millis: 20000
  ignoredServices: The '*'
  prefix: /api # set a common prefix
  routes:
    auth-service:
      path: /auth/**
      sensitiveHeaders:
      serviceId:  service-auth
    consumer-service:
      path: /consumer/**
      sensitiveHeaders:
      serviceId:  eureka-consumer
    client-service:
      path: /client/**
      sensitiveHeaders:
      serviceId:  eureka-client
  add-proxy-headers: true
  include-debug-header: true

eureka:
  client:
    serviceUrl:
      Specify the location of the service registry
      defaultZone: http://localhost:8001/eureka/

logging:
  level:
    com.netflix: DEBUG
Copy the code
  1. Add the @enableZuulProxy annotation to start the gateway service
@EnableZuulProxy
@EnableEurekaClient
@SpringBootApplication
public class ApiZuulApplication {

    public static void main(String[] args) { SpringApplication.run(ApiZuulApplication.class, args); }}Copy the code
  1. other

If you access the service directly by the service name without using routing, I encountered the problem that Oauth kept showing authentication failure during the test.

Service communication

Introduction to the

The communication between services is actually carried out through Url (RestFul) communication. Through service governance, we can carry out the communication between services through service names and other ways

The following methods all invoke the same method of other services, the service name is Eureka-client

@Slf4j
@RestController
public class DcController {

    @Autowired
    DiscoveryClient mDiscoveryClient;

    @GetMapping("/dc")
    public Result dc(a) throws Exception {
        // Discover the service
        String services = "Services: " + mDiscoveryClient.getServices();
        log.info(services);
        return ResultUtils.resultData(ResultEnum.SUCCESS, "Data returned by Eureka-client:"+ services); }}Copy the code

LoadBalancerClient

LoadBalancerClient is the most basic inter-service communication component with load balancing

  1. A launch configuration
// Join service governance
@EnableEurekaClient
@SpringBootApplication
public class Application {

  // Initialize the RestTemplate to actually initiate REST requests
	@Bean
	public RestTemplate restTemplate(a) {
		return new RestTemplate();
	}

	public static void main(String[] args) {
		new SpringApplicationBuilder(Application.class).web(true).run(args); }}Copy the code
  1. Method of use
@Slf4j
@RequestMapping("/lbc")
@RestController
public class LbcController {

    @Autowired
    LoadBalancerClient loadBalancerClient;
    @Autowired
    RestTemplate restTemplate;

    /** * load balancing to select an eureka-client ServiceInstance using the choose function of loadBalancerClient. * the basic information of this ServiceInstance is stored in ServiceInstance. The detailed address to access the/DC interface is then concatenated from the information in these objects, and finally the call to the service provider interface is implemented using the RestTemplate object. * /
    @GetMapping("/consumer")
    public Result dc(a) {
        ServiceInstance serviceInstance = loadBalancerClient.choose("eureka-client");
        String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/dc";
        log.info(url);
        returnrestTemplate.getForObject(url, Result.class); }}Copy the code

Ribbn

Spring Cloud Ribbon is a client load balancing tool based on Netflix Ribbon. It is a client load balancer based on HTTP and TCP.

  1. Maven rely on
<! -- Ribbn -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-ribbon</artifactId>
    <version>1.4.7. RELEASE</version>
</dependency>
Copy the code
  1. A launch configuration
// Add only one @loadBalanced annotation to LoadBalancerClient
@EnableEurekaClient
@SpringBootApplication
public class Application {

	@Bean
	@LoadBalanced
	public RestTemplate restTemplate(a) {
		return new RestTemplate();
	}

	public static void main(String[] args) {
		new SpringApplicationBuilder(Application.class).web(true).run(args); }}Copy the code
  1. Method of use
// The LoadBalancerClient call is a bit simpler
@Slf4j
@RequestMapping("/ribbon")
@RestController
public class RibbonController {

    @Autowired
    RestTemplate restTemplate;

    @GetMapping("/consumer")
    public Result consumer(a) {
        return restTemplate.getForObject("http://eureka-client/dc", Result.class); }}Copy the code

Feign

This is my preferred method. It is easy to call. You can also use RequestInterceptor to set the Header for user authentication

  1. Maven rely on
<! -- Feign -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
    <version>1.4.7. RELEASE</version>
</dependency>
Copy the code
  1. To start the configuration, you only need to add a comment
// Enable scanning for Spring Cloud Feign clients with the @enableFeignClients annotation
@EnableFeignClients
@EnableEurekaClient
@SpringBootApplication
public class EurekaConsumerApplication {

    public static void main(String[] args) { SpringApplication.run(EurekaConsumerApplication.class, args); }}Copy the code
  1. Method of use

  2. The first step is to define an interface file

/** * Create a Feign client interface definition. * use@FeignClientAnnotations specify the name of the service to be invoked by this interface, and functions defined in the interface can bind to the service provider's REST interface using Spring MVC annotations * <p> * *@authorChang chin *@date2019/10/31 * /
@FeignClient(name = "eureka-client")
public interface DcClient {

    @GetMapping("/dc")
    Result consumer(a);

}
Copy the code
  1. The second step is to call the defined interface directly
@RequestMapping("/feign")
@RestController
public class FeignController {

    @Autowired
    DcClient mDcClient;

    @GetMapping("/consumer")
    public Result consumer(a) {
        returnmDcClient.consumer(); }}Copy the code
  1. throughRequestInterceptorThe interceptor adds the Oauth2 authentication parameter to the service request
@Component
public class SecuringRequestInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String authorization = request.getHeader("Authorization");
        if(! StringUtils.isEmpty(authorization)) { requestTemplate.header("Authorization", authorization); }}}Copy the code

Oauth2.0

Oauth2.0 authentication takes up most of this article, but it is also simple to use. Later, we will use hydrology in a complete Spring Cloud Oauth2. Spring Cloud Oauth relies on Spring Security, so role permissions are authenticated using Spring Security. In this paper, only Redis is used to store oAuth2-related data

Authorized service

  1. Authorization server Maven file
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<! -- Monitor system health -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<! Spring-cloud-starter-security, spring-security-oauth2, spring-security-jWT, spring-security-oauth2, spring-security-jWT
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.1</version>
</dependency>
Copy the code
  1. Oauth2 has three basic tables, which can be added according to the service. The table structure is as follows:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`id`))ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) NOT NULL.`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `UK_sb8bbouer5wak8vyiiy4pf2bx` (`username`))ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
  `user_id` bigint(20) NOT NULL.`role_id` bigint(20) NOT NULL.KEY `FKa68196081fvovjhkek5m97n3y` (`role_id`),
  KEY `FK859n2jvi8ivhui0rl0esws6o` (`user_id`),
  CONSTRAINT `FK859n2jvi8ivhui0rl0esws6o` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`),
  CONSTRAINT `FKa68196081fvovjhkek5m97n3y` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`))ENGINE=InnoDB DEFAULT CHARSET=utf8;

SET FOREIGN_KEY_CHECKS = 1;
Copy the code
  1. So let’s write it down to get user information

    Oauth2 default call org. Springframework. Security. Core. Populated userdetails. UserDetailsService obtaining user information, So we inherit the UserDetailsService and rewrite the loadUserByUsername method to get the user.

    1. To create aAuthUserDetailsServiceclass
    @Slf4j
    @Service("userDetailService")
    public class AuthUserDetailsService implements UserDetailsService {
    
        @Autowired
        private UserDao mUserDao;
        @Autowired
        private UserRoleDao mUserRoleDao;
    
        @Override
        public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
            List<UserDo> userDos = mUserDao.listUserByUserName(userName);
            if (userDos == null || userDos.size() == 0) {
                throw new UsernameNotFoundException("Use nonexistence.");
            }
            UserDo userDo = userDos.get(0);
            List<RoleDo> roleDos = mUserRoleDao.listRoleByUserId(userDo.getId());
            userDo.setAuthorities(roleDos);
            log.info(userDo.toString());
            returnuserDo; }}Copy the code
    1. UserDoTo achieve theUserDetailsRelated interfaces of
    @Data
    public class UserDo implements UserDetails.Serializable {
    
        private Long id;
        private String username;
        private String password;
        private List<RoleDo> authorities;
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return authorities;
        }
    
        /** * Expiration :true: not expired false: expired **@return* /
        @Override
        public boolean isAccountNonExpired(a) {
            return true;
        }
    
        /** * Lock qualitative :true: unlocked false: locked **@return* /
        @Override
        public boolean isAccountNonLocked(a) {
            return true;
        }
    
        /** * Validity :true: the certificate is valid false: the certificate is invalid **@return* /
        @Override
        public boolean isCredentialsNonExpired(a) {
            return true;
        }
    
        /** * Availability :true: available false: unavailable **@return* /
        @Override
        public boolean isEnabled(a) {
            return true; }}Copy the code
    1. RoleDo

    In the code above, we can see that the objects in the set data returned by the Getathorities method inherit the GrantedAuthority interface, so our RoleDo implements the GrantedAuthority interface

    @Data
    public class RoleDo implements GrantedAuthority.Serializable {
    
        private Long id;
        private String name;
    
        @Override
        public String getAuthority(a) {
            returnname; }}Copy the code
  2. WebSecurityConfigurerAdapter

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthUserDetailsService userDetailService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.requestMatchers()
                .anyRequest()
                .and()
                .authorizeRequests()
                // release /oauth/ Api below
                .antMatchers("/oauth/**")
                .permitAll();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailService).passwordEncoder(new BCryptPasswordEncoder());
    }

    /** * no password grant_type **@return
     * @throws Exception
     */
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean(a) throws Exception {
        return super.authenticationManagerBean();
    }

    // TODO:User password encryption mode
    public static void main(String[] args) {
        System.out.println(new BCryptPasswordEncoder().encode("123456")); }}Copy the code
  1. OAuth2AuthorizationConfig
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Autowired
    private AuthUserDetailsService userDetailService;

    private static final String finalSecret = "{bcrypt}" + new BCryptPasswordEncoder().encode("sdwfqin");

    @Bean
    public TokenStore tokenStore(a) {
        return new RedisTokenStore(redisConnectionFactory);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // Store the client information in memory
        clients.inMemory()
                // Create a client named Android
                .withClient("android")
                .secret(finalSecret)
                // Configure the authentication type
                .authorizedGrantTypes("password"."refresh_token")
                // Configure the client domain
                .scopes("mobile")
                .and()
                .withClient("service")
                .secret(finalSecret)
                .authorizedGrantTypes("client_credentials"."refresh_token")
                .scopes("service");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // Configure the Token storage mode
        endpoints
                // Read the user's authentication information
                .userDetailsService(userDetailService)
                // Inject the bean configured with WebSecurityConfig
                .authenticationManager(authenticationManager)
                .tokenStore(tokenStore())
                .tokenServices(redisTokenServices());
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                // Allow form authentication
                .allowFormAuthenticationForClients()
                // Requests for tokens are no longer intercepted
                .tokenKeyAccess("permitAll()")
                // Verify To obtain Token authentication information
                .checkTokenAccess("isAuthenticated()");
    }

    @Bean
    public DefaultTokenServices redisTokenServices(a) {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(tokenStore());
        tokenServices.setSupportRefreshToken(true);
        // Token validity period User-defined, 12 hours by default
        tokenServices.setAccessTokenValiditySeconds(60 * 60 * 12);
        // refresh_token defaults to 30 days
        tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);
        returntokenServices; }}Copy the code
  1. Expose interfaces to get user information for other services
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping(value = "/current", method = RequestMethod.GET)
    public Principal getUser(Principal principal) {
        log.info("> > > > > > > > > > > > > > > > > > > > > > > >");
        log.info(principal.toString());
        log.info("> > > > > > > > > > > > > > > > > > > > > > > >");
        return principal;
    }

    @GetMapping("/register")
    public Result register(a) {
        return ResultUtils.resultData(ResultEnum.SUCCESS, "Registered"); }}Copy the code
  1. Resource service configuration on authorization serviceResourceServerConfig
@Slf4j
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        // Return when permissions are insufficient
        resources.accessDeniedHandler((request, response, e) -> {
            log.error(AccessDeniedHandler {}", e.getMessage());
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            // The unified authentication failed
            response.getWriter()
                    .write(objectMapper.writeValueAsString(ResultUtils.errorData(ResultEnum.AUTHORITY_ERROR)));
        });
        // Return if token is incorrect
        resources.authenticationEntryPoint((request, response, e) -> {
            log.error([authenticationEntryPoint] {}", e.getMessage());
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            response.getWriter()
                    .write(objectMapper.writeValueAsString(ResultUtils.errorData(ResultEnum.TOKEN_ERROR)));
        });
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        // Configure which requests need validation
        http.csrf().disable()
                .httpBasic().disable()
                .authorizeRequests()
                / / release the start
                .antMatchers("/user/register")
                .permitAll()
                / / release the end
                / / = = = = = = = = = =
                / / start.anyRequest() .authenticated(); }}Copy the code

Resource server authentication

  1. Maven files
<! Spring-cloud-starter-security, spring-security-oauth2, spring-security-jWT, spring-security-oauth2, spring-security-jWT
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
Copy the code
  1. The configuration of ResourceServerConfig is similar to that of the preceding configuration
@Slf4j
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        // Set the id of the resource server, which must correspond to the authentication server
        // resources.resourceId("service-auth");
        // Return when permissions are insufficient
        resources.accessDeniedHandler((request, response, e) -> {
            log.error(AccessDeniedHandler {}", e.getMessage());
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            response.getWriter()
                    .write(objectMapper.writeValueAsString(ResultUtils.errorData(ResultEnum.AUTHORITY_ERROR)));
        });
        // Return if token is incorrect
        resources.authenticationEntryPoint((request, response, e) -> {
            log.error([authenticationEntryPoint] {}", e.getMessage());
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            response.getWriter()
                    .write(objectMapper.writeValueAsString(ResultUtils.errorData(ResultEnum.TOKEN_ERROR)));
        });
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        // Configure which requests need validationhttp.csrf().disable() .httpBasic().disable() .authorizeRequests() .anyRequest() .authenticated(); }}Copy the code
  1. application.yml

Access via zuul route.

security:
    oauth2:
        resource:
            user-info-uri: http://localhost:8002/api/auth/user/current
        client:
            client-id: service
            client-secret: sdwfqin
            access-token-uri: http://localhost:8002/api/auth/oauth/token
            user-authorization-uri: http://localhost:8002/api/auth/oauth/authorize
            scope: service
Copy the code

Refer to the article and corresponding Demo

  1. There is no interface invocation example in this document. If you want to view it, download Demo and run it and import the interface JSON file to Postman

  2. For Demo: github.com/sdwfqin/Spr…

  3. Reference article:

    1. Spring Cloud from Beginner to Master (Programmer DD- Zhai Yongchao)
    2. Unified authentication and authorization for microservices implemented by SpringCloud+SpringBoot+OAuth2+Spring Security+Redis (myCat,)