手记

自定义feign配置与服务调用的安全验证

feign的使用,可以简化服务之间的调用,让服务之间调用更加优雅,本文从feign自定义配置和创建feign完成服务之间复杂权限验证,来进一步理解和定制feign。

本文示例参考了《Spring Cloud与Docker微服务架构实践》

自定义配置

  • 创建Feign的配置类

@Configurationpublic 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接口引用配置

@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.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </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 475572229@qq.com
 * @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<GrantedAuthority> 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后,可以见到页面需要输入用户名和密码才能访问。


权限认证.png

  • 服务消费者hello-auth-feign修改
    1)去掉启动类上的@EnableFeignClients注解
    2)去掉接口的@FeignClient的注解

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 475572229@qq.com
 * @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 475572229@qq.com
 * @create 17-8-26
 */@Import(FeignClientsConfiguration.class)@RestControllerpublic 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/1http://localhost:8031/user/2则可以完成服务之间授权的调用。

在实际业务中会根据不同的角色,执行不同的业务操作,基于以上示例可以在服务之间完成复杂的服务鉴权。



作者:billJiang
链接:https://www.jianshu.com/p/755b15ff0249


0人推荐
随时随地看视频
慕课网APP