feign的使用,可以简化服务之间的调用,让服务之间调用更加优雅,本文从feign自定义配置和创建feign完成服务之间复杂权限验证,来进一步理解和定制feign。
本文示例参考了《Spring Cloud与Docker微服务架构实践》
@Configuration
public class FeignConfiguration{
@Bean
public Contract feignContract(){
return new feign.Contract.Default();
}
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor(){
return new BasicAuthRequestInterceptor("user","password");
}
}
说明:第一个@Bean配置是在FeignClient中的接口中使用Feign自带的注解; 第二个@Bean配置是在请求接口中要进行基于Http Basic的认证后才能调用。
@FeignClient(name="hello", configuration=FeignConfiguration.class)
public interface HelloService{
@RequestLine("GET /message")
HelloMessage hello();
}
需要注意的是: FeignConfiguration类不能包含在主应用程序上下文的@ComponentScan中,否则该配置会被所有的@FeignClient共享。
Ribbon的自定义配置中也需要注意这个问题。
上面演示了服务之间可以通过自定义配置完成基于Http Basic的认证,但是不能满足根据不同的角色不同的用户执行不同的操作,即服务之间的调用有更复杂的权限要求,就不能满足要求了,这时候要针对feign做进一步的改进。
代码示例见:hello-auth + hello-auth-consumer-feign的项目
以下是操作步骤:
- 引入spring-security(服务提供者)
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
package com.example.helloauth.configuration;
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.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collection;
/**
* @author billjiang [email protected]
* @create 17-8-26
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception{
http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
}
@Bean
public PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
@Autowired
private CustomUserDetailService userDetailService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(this.userDetailService).passwordEncoder(this.passwordEncoder());
}
@Component
class CustomUserDetailService implements UserDetailsService{
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
if("user".equals(username)){
return new SecurityUser("user","123456","user-role");
}else if("admin".equals(username)){
return new SecurityUser("admin","123456","admin-role");
}else{
return null;
}
}
}
class SecurityUser implements UserDetails{
private static final long serialVersionUID=1L;
public SecurityUser(String username,String password,String role){
super();
this.username=username;
this.password=password;
this.role=role;
}
public SecurityUser(){
}
@Override
public Collection extends GrantedAuthority> getAuthorities() {
Collection authorities=new ArrayList<>();
SimpleGrantedAuthority authority=new SimpleGrantedAuthority(this.role);
authorities.add(authority);
return authorities;
}
private Long id;
private String username;
private String password;
private String role;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@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 void setUsername(String username) {
this.username = username;
}
@Override
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
}
}
启动项目hello-auth后,可以见到页面需要输入用户名和密码才能访问。
package com.example.helloauthconsumerfeign.service;
import com.example.helloauthconsumerfeign.model.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* @author billjiang [email protected]
* @create 17-8-23
*/
//@FeignClient(value="hello-auth")
public interface HelloAuthService {
@GetMapping("/{id}")
User findById(@PathVariable("id") Long id);
}
3)编写Controller类如下
package com.example.helloauthconsumerfeign.controller;
import com.example.helloauthconsumerfeign.model.User;
import com.example.helloauthconsumerfeign.service.HelloAuthService;
import feign.Client;
import feign.Contract;
import feign.Feign;
import feign.auth.BasicAuthRequestInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import feign.codec.Decoder;
import feign.codec.Encoder;
import org.springframework.cloud.netflix.feign.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;
/**
* @author billjiang [email protected]
* @create 17-8-26
*/
@Import(FeignClientsConfiguration.class)
@RestController
public class HelloAuthFeignController {
private HelloAuthService userAuthService;
private HelloAuthService adminAuthService;
@Autowired
public HelloAuthFeignController(Decoder decoder, Encoder encoder, Client client, Contract contract){
this.userAuthService= Feign.builder().client(client).encoder(encoder).decoder(decoder).contract(contract)
.requestInterceptor(new BasicAuthRequestInterceptor("user","123456"))
.target(HelloAuthService.class,"http://hello-auth/");
this.adminAuthService= Feign.builder().client(client).encoder(encoder).decoder(decoder).contract(contract)
.requestInterceptor(new BasicAuthRequestInterceptor("admin","123456"))
.target(HelloAuthService.class,"http://hello-auth/");
}
@GetMapping("/user/{id}")
public User findByIdUser(@PathVariable Long id){
return this.userAuthService.findById(id);
}
@GetMapping("/admin/{id}")
public User findByIdAdmin(@PathVariable Long id){
return this.adminAuthService.findById(id);
}
}
http://localhost:8031/admin/1
和http://localhost:8031/user/2
则可以完成服务之间授权的调用。在实际业务中会根据不同的角色,执行不同的业务操作,基于以上示例可以在服务之间完成复杂的服务鉴权。