这是我参与更文挑战的第14天,活动详情查看: 更文挑战
Eureka Server 在实际使用过程中必须考虑安全问题,比如未认证的用户 不允许其随意调用 Eureka Server的 API;还有一个则是 未认证的 Eureka Client 也禁止其注册到Eureka Server中来,这些都是可以在工程中进行配置的,当然这也是最基本的安全认证措施,那么如果Eureka Server启用了安全认证,那么Eureka Server rest api该如何调用?我们需要做哪些改造和支持?在本篇文章中,我将待大家一起学习下在java中Eureka Server添加Security Basic,以及client如何注册到Eureka Server,java/golang如何调用开启安全认证的Eureka Server rest api。下面是oracle官网给出的HTTP Basic Authentication的流程图:
Eureka Server开启Security Basic
在本篇文章我推荐两种方式,两种方式都是基于spring boot进行结合使用。在这里我还是比较推荐方案一,即完全基于yml的方式,这种方式对于我们后端工程师来说使用的最多也是最简单没有任何代码量的。一切基于配置。但最终使用的方案可行性。还需要结合自身的业务去考虑。
方案一
-
Eureka Server开启Spring Security Basic认证首先需要在 Eureka Server中引入Spring Security组件:spring-boot-starter-security
-
接下来关键的一步则是配置Eureka Server工程的yml配置文件,加入和认证相关的信息:
server:
port: 8888
spring:
security:
user:
name: root
password: root
eureka:
client:
registerWithEureka: false
fetchRegistry: false
spring.security配置的意图应该很明确,需要用户名和密码方可认证通过。 既然上面的 Eureka Server已开启认证环节,则相应的Eureka Client也需要对应的配置,方可通过认证再注册到Eureka Server中来,搭建好Eureka Client工程后,需要在项目配置文件中加入类似Eureka Server的配置,在这里我只贴出关键性配置样例:
server:
port: 8889
spring:
application:
name: eureka-client
eureka:
client:
security:
basic:
user: root
password: root
serviceUrl:
defaultZone: http://${eureka.client.security.basic.user}:${eureka.client.security.basic.password}@localhost:8888/eureka/
这样就完成了基于Spring Security Basic也就是我们方案一的基础认证。
在浏览器中输入 http://127.0.0.1:8888
启动 Eureka Server后,再也不能裸进 Eureka Server管理界面了,需要输入用户名/密码认证.
方案二
我们使用 SpringBoot和Spring Security 简单的在Eureka Server中搭建一个具有 HTTP Basic Authentication 的服务。具体的搭建过程我就不陈述了,我在这里先贴出关键代码,便于大家的理解: 配置 BasicAuthenticationEntryPoint
@Component
public class MyBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.addHeader("WWW-Authenticate", "Basic realm=" + getRealmName());
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter printWriter = new PrintWriter(response.getOutputStream());
printWriter.write("Http Status 401: " + authException.getLocalizedMessage());
}
@Override
public void afterPropertiesSet() throws Exception {
setRealmName("developlee");
super.afterPropertiesSet();
}
}
配置 WebSecurityConfigurer
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyBasicAuthenticationEntryPoint authenticationEntryPoint;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
// 开启httpBasic
.httpBasic()
// 设置 BasicAuthenticationFilter
.authenticationEntryPoint(authenticationEntryPoint);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("root").password(passwordEncoder().encode("root")).authorities("ROLE_USER");
}
@Bean
protected PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
编写 Controller
@RestController
public class WebController {
@RequestMapping(path = "/hello")
public String hello(){
return "验证通过";
}
}
启动项目,访问 http://127.0.0.1:8080/hello
至此,我们第二种Eureka server启用Security Basic已完成。
Eureka Server开启Security Basic之rest api接口认证
我们知道在上面教程方案一中,Eureka server启用了Security Basic,Eureka client注册在yml中进行配置了用户名以及密码还有服务的注册地址,在服务启动的过程中,eureka client其实是调用了Eureka server的rest api接口(关于Eureka server rest api详见:Eureka REST operations)。下面我们将通过java api进行深度的去了解增加认证之后的eureka server api该如何完成调用。
方案一、http basic auth模式
直接使用http basic auth,即我们在方案一中eureka client的注册到eureka server的方式就是使用了URL中的凭证进行访问。
但是我们可以看到,官网不建议我们使用这种方式,我们可以看到如下的描述信息:
方案二、java http client
http client 标准模式 如下则是基础模式的代码样例:
private String URL_SECURED_BY_BASIC_AUTHENTICATION = "http://127.0.0.1:8888/eureka/apps";
private String DEFAULT_USER = "root";
private String DEFAULT_PASS = "root";
@Test
public void CredentialsProvider()throws Exception{
// 创建用户信息
CredentialsProvider provider = new BasicCredentialsProvider();
UsernamePasswordCredentials credentials
= new UsernamePasswordCredentials(DEFAULT_USER, DEFAULT_PASS);
provider.setCredentials(AuthScope.ANY, credentials);
// 创建客户端的时候进行身份验证
HttpClient client = HttpClientBuilder.create()
.setDefaultCredentialsProvider(provider)
.build();
HttpResponse response = client.execute(
new HttpGet(URL_SECURED_BY_BASIC_AUTHENTICATION));
int statusCode = response.getStatusLine()
.getStatusCode();
Assert.assertEquals(statusCode,200);
}
http client 抢先模式
@Test
public void PreemptiveBasicAuthentication()throws Exception{
// 先进行身份验证
HttpHost targetHost = new HttpHost("127.0.0.1", 8888, "http");
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(DEFAULT_USER, DEFAULT_PASS));
AuthCache authCache = new BasicAuthCache();
// 将身份验证放入缓存中
authCache.put(targetHost, new BasicScheme());
HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(credsProvider);
context.setAuthCache(authCache);
HttpClient client = HttpClientBuilder.create().build();
HttpResponse response = client.execute(
new HttpGet(URL_SECURED_BY_BASIC_AUTHENTICATION), context);
int statusCode = response.getStatusLine().getStatusCode();
Assert.assertEquals(statusCode,200);
}
http client 原生Http Basic模式
@Test
public void HttpBasicAuth()throws Exception{
HttpGet request = new HttpGet(URL_SECURED_BY_BASIC_AUTHENTICATION);
// 手动构建验证信息
String auth = DEFAULT_USER + ":" + DEFAULT_PASS;
byte[] encodedAuth = Base64.encodeBase64(
auth.getBytes(StandardCharsets.UTF_8));
String authHeader = "Basic " + new String(encodedAuth);
// 将验证信息放入到 Header
request.setHeader(HttpHeaders.AUTHORIZATION, authHeader);
HttpClient client = HttpClientBuilder.create().build();
HttpResponse response = client.execute(request);
int statusCode = response.getStatusLine().getStatusCode();
Assert.assertEquals(statusCode,200);
}
其中第三种方式还是比较简单的,我们可以看到,可以将明文的用户名密码拼接成如 username:password的方式,然后在通过base64进行转换,最后将在heder中加上以下信息即可,value需要拼接上Basic加上空格拼接上转换后的base64的认证即可实现安全认证。
Authorization:Basic cm9vdDpyb290
postman调用样例如下截图:
另外在postman中调用也可以输入明文用户名和密码进行验证如: