SpringCloud系列十二:手动创建Feign

1. 回顾

  上文讲解了自定义Feign。但是在某些场景下,前文自定义Feign的方式满足不了需求,此时可使用Feign Builder API手动创建Feign。

  本文围绕以下场景,为大家讲解如何手动创建Feign。

  • 用户微服务的接口需要登录后才能调用,并且对于相同的API,不同角色的用户有不同的行为。
  • 让电影微服务中的同一个Feign接口,使用不同的账号登录,并调用用户微服务的接口。

2. 修改用户微服务

  > 复制项目 microservice-provider-user,将 ArtifactId 修改为 microservice-provider-user-with-auth

  > 添加Spring Security依赖

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>

  > 创建 Spring Security的配置类

package com.itmuch.cloud.microserviceprovideruserwithauth.security;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;

public class SecurityUser implements UserDetails {

    private static final long serialVersoinUID = 1L;

    private Long id;
    private String username;
    private String password;
    private String role;

    public SecurityUser() {
    }

    public SecurityUser(String username, String password, String role) {
        this.username = username;
        this.password = password;
        this.role = role;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
        SimpleGrantedAuthority authority = new SimpleGrantedAuthority(this.role);
        authorities.add(authority);
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }
}
package com.itmuch.cloud.microserviceprovideruserwithauth.security;

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

@Component
public class CustomUserDetailsService implements UserDetailsService {

    /**
     * 模拟两个账户
     * ① 账号是user,密码是password1,角色是user-role
     * ② 账号时候admin,密码是password1,角色是admin-role
     * @param username
     *                  用户名
     * @return
     *
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if ("user".equals(username)) {
            return new SecurityUser("user", "password1", "user-role");
        } else if ("admin".equals(username)) {
            return new SecurityUser("admin", "password2", "admin-role");
        } else {
            return null;
        }
    }
}
package com.itmuch.cloud.microserviceprovideruserwithauth.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 所有的请求,都需要经过HTTP basic认证
        http
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .httpBasic();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        // 明文编码器。这个一个不做任何操作的密码编码器,是Spring提供给我们做明文测试的
        return NoOpPasswordEncoder.getInstance();
    }

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

  > 修改Controller,在其中打印当前登录的用户信息

package com.itmuch.cloud.microserviceprovideruserwithauth.controller;

import com.itmuch.cloud.microserviceprovideruserwithauth.dao.UserRepository;
import com.itmuch.cloud.microserviceprovideruserwithauth.pojo.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.Collection;

@RestController
public class UserController {

    private static final Logger LOGGER = LoggerFactory.getLogger(UserController.class);

    @Autowired
    private UserRepository userRepository;

    @GetMapping("/{id}")
    public User findById(@PathVariable Long id) {
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (principal instanceof UserDetails) {
            UserDetails user = (UserDetails) principal;
            Collection<? extends GrantedAuthority> collections = user.getAuthorities();
            for (GrantedAuthority ga: collections) {
                // 打印当前登录用户的信息
                UserController.LOGGER.info("当前用户是{}, 角色是{}", user.getUsername(), ga.getAuthority());
            }
        } else {
            UserController.LOGGER.warn("ε=(´ο`*)))唉,出现问题了");
        }
        User findOne = userRepository.findById(id).get();
        return findOne;
    }

}

  > 启动 microservice-discovery-eureka

  > 启动 microservice-provider-user-with-auth

  > 访问 http://localhost:8000/1,弹出登录对话框

 

  > 使用 user/password1 登录,可在控制台看到如下日志

  > 使用 admin/password2 登录,可在控制台看到如下日志

3. 修改电影微服务

  > 复制项目 microservice-consumer-movie-feign,将 ArtifactId 修改为 microservice-consumer-movie-feign-manual

  > 去掉Feign接口 UserFeignClient上的@FeignClient注解

package com.itmuch.cloud.microserviceconsumermoviefeignmanual.feign;

import com.itmuch.cloud.microserviceconsumermoviefeignmanual.pojo.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

public interface UserFeignClient {

    @GetMapping(value = "/{id}")
    User findById(@PathVariable("id") Long id);

}

  > 去掉启动类上的 @EnableFeignClients 注解

package com.itmuch.cloud.microserviceconsumermoviefeignmanual;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableDiscoveryClient
public class MicroserviceConsumerMovieFeignManualApplication {

    public static void main(String[] args) {
        SpringApplication.run(MicroserviceConsumerMovieFeignManualApplication.class, args);
    }

}

  > 修改 Controller

package com.itmuch.cloud.microserviceconsumermoviefeignmanual.controller;

import com.itmuch.cloud.microserviceconsumermoviefeignmanual.feign.UserFeignClient;
import com.itmuch.cloud.microserviceconsumermoviefeignmanual.pojo.User;
import feign.Client;
import feign.Contract;
import feign.Feign;
import feign.auth.BasicAuthRequestInterceptor;
import feign.codec.Decoder;
import feign.codec.Encoder;
import org.springframework.cloud.openfeign.FeignClientsConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@Import(FeignClientsConfiguration.class) // Spring Cloud为Feign默认提供的配置类
@RestController
public class MovieController {

    private UserFeignClient userUserFeignClient;
    private UserFeignClient adminUserFeignClient;

    public MovieController(Decoder decoder, Encoder encoder, Client client, Contract contract) {
        this.userUserFeignClient = Feign.builder().client(client).encoder(encoder).decoder(decoder).contract(contract)
                .requestInterceptor(new BasicAuthRequestInterceptor("user", "password1"))
                .target(UserFeignClient.class, "http://microservice-provider-user/");
        this.adminUserFeignClient = Feign.builder().client(client).encoder(encoder).decoder(decoder).contract(contract)
                .requestInterceptor(new BasicAuthRequestInterceptor("admin", "password2"))
                .target(UserFeignClient.class, "http://microservice-provider-user/");
    }

    @GetMapping("/user-user/{id}")
    public User findByIdUser(@PathVariable Long id) {
        return this.userUserFeignClient.findById(id);
    }

    @GetMapping("/user-admin/{id}")
    public User findByIdAdmin(@PathVariable Long id) {
        return this.adminUserFeignClient.findById(id);
    }

}

  > 启动 microservice-discovery-eureka

  > 启动 microservice-provider-user-with-auth

  > 启动 microservice-consumer-movie-feign-manual

  > 访问 http://localhost:8010/user-user/1,可正常获取结果,并且在用户微服务控制台打印如下日志

  > 访问 http://localhost:8010/user-admin/1,可正常获取结果,并且在用户微服务控制台打印如下日志

4. 总结

  由测试不难发现,手动创建Feign的方式更加灵活。

  下文将讲解Feign对集成的支持、对压缩的支持、日志、构造多参数请求等。敬请期待~~~

5. 参考

  周立 --- 《Spring Cloud与Docker微服务架构与实战》

posted @ 2018-03-28 16:23  禁忌夜色153  阅读(5681)  评论(0编辑  收藏  举报