You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shardingsphere.apache.org by wu...@apache.org on 2020/12/07 01:34:04 UTC

[shardingsphere-elasticjob] branch master updated: Add cloud interface authentication (#1764)

This is an automated email from the ASF dual-hosted git repository.

wuweijie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere-elasticjob.git


The following commit(s) were added to refs/heads/master by this push:
     new f36dbec  Add cloud interface authentication (#1764)
f36dbec is described below

commit f36dbec98210fc8a01b2f3972896e6ecbefbf59b
Author: Yanjie Zhou <zh...@aliyun.com>
AuthorDate: Mon Dec 7 09:26:46 2020 +0800

    Add cloud interface authentication (#1764)
    
    * Add cloud interface authentication
    
    * fix code style
---
 .../elasticjob/cloud/console/ConsoleBootstrap.java |  3 +
 .../console/security/AuthenticationConstants.java  | 28 +++++++
 .../console/security/AuthenticationFilter.java     | 87 ++++++++++++++++++++++
 .../cloud/console/security/AuthenticationInfo.java | 31 ++++++++
 .../console/security/AuthenticationService.java    | 57 ++++++++++++++
 .../cloud/scheduler/env/AuthConfiguration.java     | 34 +++++++++
 .../cloud/scheduler/env/BootstrapEnvironment.java  | 15 +++-
 .../conf/elasticjob-cloud-scheduler.properties     |  6 ++
 .../elasticjob/cloud/console/HttpTestUtil.java     | 58 ++++++++++++++-
 .../cloud/console/controller/CloudLoginTest.java   | 67 +++++++++++++++++
 10 files changed, 382 insertions(+), 4 deletions(-)

diff --git a/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/java/org/apache/shardingsphere/elasticjob/cloud/console/ConsoleBootstrap.java b/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/java/org/apache/shardingsphere/elasticjob/cloud/console/ConsoleBootstrap.java
index 1b2c324..3c08965 100644
--- a/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/java/org/apache/shardingsphere/elasticjob/cloud/console/ConsoleBootstrap.java
+++ b/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/java/org/apache/shardingsphere/elasticjob/cloud/console/ConsoleBootstrap.java
@@ -21,6 +21,8 @@ import org.apache.shardingsphere.elasticjob.cloud.console.config.advice.ConsoleE
 import org.apache.shardingsphere.elasticjob.cloud.console.controller.CloudAppController;
 import org.apache.shardingsphere.elasticjob.cloud.console.controller.CloudJobController;
 import org.apache.shardingsphere.elasticjob.cloud.console.controller.CloudOperationController;
+import org.apache.shardingsphere.elasticjob.cloud.console.security.AuthenticationFilter;
+import org.apache.shardingsphere.elasticjob.cloud.console.security.AuthenticationService;
 import org.apache.shardingsphere.elasticjob.cloud.scheduler.env.RestfulServerConfiguration;
 import org.apache.shardingsphere.elasticjob.cloud.scheduler.mesos.ReconcileService;
 import org.apache.shardingsphere.elasticjob.cloud.scheduler.producer.ProducerManager;
@@ -43,6 +45,7 @@ public class ConsoleBootstrap {
         NettyRestfulServiceConfiguration restfulServiceConfiguration = new NettyRestfulServiceConfiguration(config.getPort());
         restfulServiceConfiguration.addControllerInstances(new CloudJobController(), new CloudAppController(), new CloudOperationController());
         restfulServiceConfiguration.addExceptionHandler(Exception.class, new ConsoleExceptionHandler());
+        restfulServiceConfiguration.addFilterInstances(new AuthenticationFilter(new AuthenticationService()));
         restfulService = new NettyRestfulService(restfulServiceConfiguration);
     }
     
diff --git a/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/java/org/apache/shardingsphere/elasticjob/cloud/console/security/AuthenticationConstants.java b/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/java/org/apache/shardingsphere/elasticjob/cloud/console/security/AuthenticationConstants.java
new file mode 100644
index 0000000..1b1c0a2
--- /dev/null
+++ b/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/java/org/apache/shardingsphere/elasticjob/cloud/console/security/AuthenticationConstants.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.elasticjob.cloud.console.security;
+
+/**
+ * Authentication constants.
+ */
+public final class AuthenticationConstants {
+
+    public static final String LOGIN_URI = "/api/login";
+
+    public static final String HEADER_NAME = "accessToken";
+}
diff --git a/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/java/org/apache/shardingsphere/elasticjob/cloud/console/security/AuthenticationFilter.java b/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/java/org/apache/shardingsphere/elasticjob/cloud/console/security/AuthenticationFilter.java
new file mode 100644
index 0000000..b368a4a
--- /dev/null
+++ b/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/java/org/apache/shardingsphere/elasticjob/cloud/console/security/AuthenticationFilter.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.elasticjob.cloud.console.security;
+
+import com.google.common.base.Strings;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.FullHttpResponse;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http.HttpUtil;
+import java.util.Collections;
+import java.util.Optional;
+import lombok.RequiredArgsConstructor;
+import org.apache.shardingsphere.elasticjob.infra.json.GsonFactory;
+import org.apache.shardingsphere.elasticjob.restful.Filter;
+import org.apache.shardingsphere.elasticjob.restful.Http;
+import org.apache.shardingsphere.elasticjob.restful.deserializer.RequestBodyDeserializer;
+import org.apache.shardingsphere.elasticjob.restful.deserializer.RequestBodyDeserializerFactory;
+import org.apache.shardingsphere.elasticjob.restful.filter.FilterChain;
+
+/**
+ * Authentication filter.
+ */
+@RequiredArgsConstructor
+public final class AuthenticationFilter implements Filter {
+
+    private final AuthenticationService authenticationService;
+
+    @Override
+    public void doFilter(final FullHttpRequest httpRequest, final FullHttpResponse httpResponse, final FilterChain filterChain) {
+        if (AuthenticationConstants.LOGIN_URI.equals(httpRequest.uri())) {
+            handleLogin(httpRequest, httpResponse);
+        } else {
+            String accessToken = httpRequest.headers().get(AuthenticationConstants.HEADER_NAME);
+            if (!Strings.isNullOrEmpty(accessToken) && accessToken.equals(authenticationService.getToken())) {
+                filterChain.next(httpRequest);
+            } else {
+                respondWithUnauthorized(httpResponse);
+            }
+        }
+    }
+
+    private void handleLogin(final FullHttpRequest httpRequest, final FullHttpResponse httpResponse) {
+        byte[] bytes = ByteBufUtil.getBytes(httpRequest.content());
+        String mimeType = Optional.ofNullable(HttpUtil.getMimeType(httpRequest))
+            .orElseGet(() -> HttpUtil.getMimeType(Http.DEFAULT_CONTENT_TYPE)).toString();
+        RequestBodyDeserializer deserializer = RequestBodyDeserializerFactory.getRequestBodyDeserializer(mimeType);
+        AuthenticationInfo authenticationInfo = deserializer.deserialize(AuthenticationInfo.class, bytes);
+        boolean result = authenticationService.check(authenticationInfo);
+        if (result) {
+            String token = GsonFactory.getGson().toJson(Collections.singletonMap(AuthenticationConstants.HEADER_NAME,
+                    authenticationService.getToken()));
+            respond(httpResponse, HttpResponseStatus.OK, token.getBytes());
+        } else {
+            respondWithUnauthorized(httpResponse);
+        }
+    }
+
+    private void respondWithUnauthorized(final FullHttpResponse httpResponse) {
+        String result = GsonFactory.getGson().toJson(Collections.singletonMap("message", "Unauthorized."));
+        respond(httpResponse, HttpResponseStatus.UNAUTHORIZED, result.getBytes());
+    }
+
+    private void respond(final FullHttpResponse httpResponse, final HttpResponseStatus status, final byte[] result) {
+        httpResponse.setStatus(status);
+        httpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE, Http.DEFAULT_CONTENT_TYPE);
+        httpResponse.content().writeBytes(result);
+        HttpUtil.setContentLength(httpResponse, httpResponse.content().readableBytes());
+        HttpUtil.setKeepAlive(httpResponse, true);
+    }
+}
diff --git a/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/java/org/apache/shardingsphere/elasticjob/cloud/console/security/AuthenticationInfo.java b/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/java/org/apache/shardingsphere/elasticjob/cloud/console/security/AuthenticationInfo.java
new file mode 100644
index 0000000..e4f246f
--- /dev/null
+++ b/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/java/org/apache/shardingsphere/elasticjob/cloud/console/security/AuthenticationInfo.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.elasticjob.cloud.console.security;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public final class AuthenticationInfo {
+
+    private String username;
+
+    private String password;
+
+}
diff --git a/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/java/org/apache/shardingsphere/elasticjob/cloud/console/security/AuthenticationService.java b/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/java/org/apache/shardingsphere/elasticjob/cloud/console/security/AuthenticationService.java
new file mode 100644
index 0000000..cf6adb9
--- /dev/null
+++ b/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/java/org/apache/shardingsphere/elasticjob/cloud/console/security/AuthenticationService.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.elasticjob.cloud.console.security;
+
+import com.google.common.base.Strings;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.shardingsphere.elasticjob.cloud.scheduler.env.AuthConfiguration;
+import org.apache.shardingsphere.elasticjob.cloud.scheduler.env.BootstrapEnvironment;
+import org.apache.shardingsphere.elasticjob.infra.json.GsonFactory;
+
+/**
+ * User authentication service.
+ */
+public final class AuthenticationService {
+
+    private final Base64 base64 = new Base64();
+
+    private final BootstrapEnvironment env = BootstrapEnvironment.getINSTANCE();
+
+    /**
+     * Check auth.
+     *
+     * @param authenticationInfo authentication info
+     * @return check success or failure
+     */
+    public boolean check(final AuthenticationInfo authenticationInfo) {
+        if (null == authenticationInfo || Strings.isNullOrEmpty(authenticationInfo.getUsername()) || Strings.isNullOrEmpty(authenticationInfo.getPassword())) {
+            return false;
+        }
+        AuthConfiguration userAuthConfiguration = env.getUserAuthConfiguration();
+        return userAuthConfiguration.getAuthUsername().equals(authenticationInfo.getUsername()) && userAuthConfiguration.getAuthPassword().equals(authenticationInfo.getPassword());
+    }
+
+    /**
+     * Get user authentication token.
+     *
+     * @return authentication token
+     */
+    public String getToken() {
+        return base64.encodeToString(GsonFactory.getGson().toJson(env.getUserAuthConfiguration()).getBytes());
+    }
+}
diff --git a/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/java/org/apache/shardingsphere/elasticjob/cloud/scheduler/env/AuthConfiguration.java b/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/java/org/apache/shardingsphere/elasticjob/cloud/scheduler/env/AuthConfiguration.java
new file mode 100644
index 0000000..c11aadd
--- /dev/null
+++ b/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/java/org/apache/shardingsphere/elasticjob/cloud/scheduler/env/AuthConfiguration.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.elasticjob.cloud.scheduler.env;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * Auth config.
+ */
+@RequiredArgsConstructor
+@Getter
+public class AuthConfiguration {
+
+    private final String authUsername;
+
+    private final String authPassword;
+
+}
diff --git a/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/java/org/apache/shardingsphere/elasticjob/cloud/scheduler/env/BootstrapEnvironment.java b/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/java/org/apache/shardingsphere/elasticjob/cloud/scheduler/env/BootstrapEnvironment.java
index 8acd829..0e03923 100755
--- a/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/java/org/apache/shardingsphere/elasticjob/cloud/scheduler/env/BootstrapEnvironment.java
+++ b/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/java/org/apache/shardingsphere/elasticjob/cloud/scheduler/env/BootstrapEnvironment.java
@@ -121,6 +121,15 @@ public final class BootstrapEnvironment {
     public FrameworkConfiguration getFrameworkConfiguration() {
         return new FrameworkConfiguration(Integer.parseInt(getValue(EnvironmentArgument.JOB_STATE_QUEUE_SIZE)), Integer.parseInt(getValue(EnvironmentArgument.RECONCILE_INTERVAL_MINUTES)));
     }
+
+    /**
+     * Get user auth config.
+     *
+     * @return the user auth config.
+     */
+    public AuthConfiguration getUserAuthConfiguration() {
+        return new AuthConfiguration(getValue(EnvironmentArgument.AUTH_USERNAME), getValue(EnvironmentArgument.AUTH_PASSWORD));
+    }
     
     /**
      * Get tracing configuration.
@@ -213,7 +222,11 @@ public final class BootstrapEnvironment {
 
         EVENT_TRACE_RDB_PASSWORD("event_trace_rdb_password", "", false),
     
-        RECONCILE_INTERVAL_MINUTES("reconcile_interval_minutes", "-1", false);
+        RECONCILE_INTERVAL_MINUTES("reconcile_interval_minutes", "-1", false),
+
+        AUTH_USERNAME("auth_username", "root", true),
+
+        AUTH_PASSWORD("auth_password", "pwd", true);
         
         private final String key;
         
diff --git a/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/resources/conf/elasticjob-cloud-scheduler.properties b/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/resources/conf/elasticjob-cloud-scheduler.properties
index 7e17d93..6f78c07 100755
--- a/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/resources/conf/elasticjob-cloud-scheduler.properties
+++ b/elasticjob-cloud/elasticjob-cloud-scheduler/src/main/resources/conf/elasticjob-cloud-scheduler.properties
@@ -60,3 +60,9 @@ job_state_queue_size=10000
 # Enable/Disable mesos partition aware feature
 
 # enable_partition_aware=false
+
+# Auth username
+auth_username=root
+
+# Auth password
+auth_password=pwd
diff --git a/elasticjob-cloud/elasticjob-cloud-scheduler/src/test/java/org/apache/shardingsphere/elasticjob/cloud/console/HttpTestUtil.java b/elasticjob-cloud/elasticjob-cloud-scheduler/src/test/java/org/apache/shardingsphere/elasticjob/cloud/console/HttpTestUtil.java
index ab0d3a9..16081da 100644
--- a/elasticjob-cloud/elasticjob-cloud-scheduler/src/test/java/org/apache/shardingsphere/elasticjob/cloud/console/HttpTestUtil.java
+++ b/elasticjob-cloud/elasticjob-cloud-scheduler/src/test/java/org/apache/shardingsphere/elasticjob/cloud/console/HttpTestUtil.java
@@ -23,23 +23,34 @@ import java.util.Map;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 import org.apache.http.HttpEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpDelete;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.client.methods.HttpPut;
+import org.apache.http.client.methods.HttpRequestBase;
 import org.apache.http.client.utils.URIBuilder;
 import org.apache.http.entity.StringEntity;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
 import org.apache.http.util.EntityUtils;
+import org.apache.shardingsphere.elasticjob.cloud.console.security.AuthenticationConstants;
+import org.apache.shardingsphere.elasticjob.cloud.console.security.AuthenticationService;
 import org.apache.shardingsphere.elasticjob.cloud.scheduler.exception.HttpClientException;
+import org.apache.shardingsphere.elasticjob.infra.json.GsonFactory;
 
 /**
  * Http utils.
  */
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 public final class HttpTestUtil {
-    
+
+    private static final AuthenticationService AUTH_SERVICE = new AuthenticationService();
+
+    private static void setAuth(final HttpRequestBase httpRequestBase) {
+        httpRequestBase.setHeader(AuthenticationConstants.HEADER_NAME, AUTH_SERVICE.getToken());
+    }
+
     /**
      * Send post request.
      *
@@ -49,12 +60,13 @@ public final class HttpTestUtil {
     public static int post(final String url) {
         try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
             HttpPost httpPost = new HttpPost(url);
+            setAuth(httpPost);
             return httpClient.execute(httpPost).getStatusLine().getStatusCode();
         } catch (IOException e) {
             throw new HttpClientException("send a post request for '%s' failed", e, url);
         }
     }
-    
+
     /**
      * Send post request.
      *
@@ -65,6 +77,7 @@ public final class HttpTestUtil {
     public static int post(final String url, final String content) {
         try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
             HttpPost httpPost = new HttpPost(url);
+            setAuth(httpPost);
             StringEntity entity = new StringEntity(content, "utf-8");
             entity.setContentEncoding("UTF-8");
             entity.setContentType("application/json");
@@ -74,7 +87,7 @@ public final class HttpTestUtil {
             throw new HttpClientException("send a post request for '%s' with parameter '%s' failed", e, url, content);
         }
     }
-    
+
     /**
      * Send put request.
      *
@@ -85,6 +98,7 @@ public final class HttpTestUtil {
     public static int put(final String url, final String content) {
         try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
             HttpPut httpPut = new HttpPut(url);
+            setAuth(httpPut);
             StringEntity entity = new StringEntity(content, "utf-8");
             entity.setContentEncoding("UTF-8");
             entity.setContentType("application/json");
@@ -104,6 +118,7 @@ public final class HttpTestUtil {
     public static String get(final String url) {
         try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
             HttpGet httpGet = new HttpGet(url);
+            setAuth(httpGet);
             HttpEntity entity = httpClient.execute(httpGet).getEntity();
             return EntityUtils.toString(entity);
         } catch (IOException e) {
@@ -125,6 +140,7 @@ public final class HttpTestUtil {
                 uriBuilder.addParameter(entry.getKey(), entry.getValue());
             }
             HttpGet httpGet = new HttpGet(uriBuilder.build());
+            setAuth(httpGet);
             HttpEntity entity = httpClient.execute(httpGet).getEntity();
             return EntityUtils.toString(entity);
         } catch (IOException | URISyntaxException e) {
@@ -141,9 +157,45 @@ public final class HttpTestUtil {
     public static int delete(final String url) {
         try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
             HttpDelete httpDelete = new HttpDelete(url);
+            setAuth(httpDelete);
             return httpClient.execute(httpDelete).getStatusLine().getStatusCode();
         } catch (IOException e) {
             throw new HttpClientException("send a delete request for '%s' failed", e, url);
         }
     }
+
+    /**
+     * Send post request.
+     *
+     * @param url url
+     * @param content content
+     * @return http response
+     */
+    public static CloseableHttpResponse unauthorizedPost(final String url, final Map<String, String> content) {
+        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+            HttpPost httpPost = new HttpPost(url);
+            StringEntity entity = new StringEntity(GsonFactory.getGson().toJson(content), "utf-8");
+            entity.setContentEncoding("UTF-8");
+            entity.setContentType("application/json");
+            httpPost.setEntity(entity);
+            return httpClient.execute(httpPost);
+        } catch (IOException e) {
+            throw new HttpClientException("send a post request for '%s' with parameter '%s' failed", e, url, content);
+        }
+    }
+
+    /**
+     * Send get request.
+     *
+     * @param url url
+     * @return http status code
+     */
+    public static int unauthorizedGet(final String url) {
+        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+            HttpGet httpGet = new HttpGet(url);
+            return httpClient.execute(httpGet).getStatusLine().getStatusCode();
+        } catch (IOException e) {
+            throw new HttpClientException("send a get request for '%s' failed", e, url);
+        }
+    }
 }
diff --git a/elasticjob-cloud/elasticjob-cloud-scheduler/src/test/java/org/apache/shardingsphere/elasticjob/cloud/console/controller/CloudLoginTest.java b/elasticjob-cloud/elasticjob-cloud-scheduler/src/test/java/org/apache/shardingsphere/elasticjob/cloud/console/controller/CloudLoginTest.java
new file mode 100644
index 0000000..f7e25ee
--- /dev/null
+++ b/elasticjob-cloud/elasticjob-cloud-scheduler/src/test/java/org/apache/shardingsphere/elasticjob/cloud/console/controller/CloudLoginTest.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.elasticjob.cloud.console.controller;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import com.google.gson.JsonObject;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.util.EntityUtils;
+import org.apache.shardingsphere.elasticjob.cloud.console.AbstractCloudControllerTest;
+import org.apache.shardingsphere.elasticjob.cloud.console.HttpTestUtil;
+import org.apache.shardingsphere.elasticjob.cloud.console.security.AuthenticationConstants;
+import org.apache.shardingsphere.elasticjob.cloud.console.security.AuthenticationService;
+import org.apache.shardingsphere.elasticjob.infra.json.GsonFactory;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CloudLoginTest extends AbstractCloudControllerTest {
+    
+    @Test
+    public void assertLoginSuccess() throws IOException {
+        Map<String, String> authInfo = new HashMap<>();
+        authInfo.put("username", "root");
+        authInfo.put("password", "pwd");
+        CloseableHttpResponse actual = HttpTestUtil.unauthorizedPost("http://127.0.0.1:19000/api/login", authInfo);
+        assertThat(actual.getStatusLine().getStatusCode(), is(200));
+        AuthenticationService authenticationService = new AuthenticationService();
+        String entity = EntityUtils.toString(actual.getEntity());
+        String token = GsonFactory.getGson().fromJson(entity, JsonObject.class).get(AuthenticationConstants.HEADER_NAME).getAsString();
+        assertThat(token, is(authenticationService.getToken()));
+    }
+
+    @Test
+    public void assertLoginFail() {
+        Map<String, String> authInfo = new HashMap<>();
+        authInfo.put("username", "root");
+        authInfo.put("password", "");
+        CloseableHttpResponse actual = HttpTestUtil.unauthorizedPost("http://127.0.0.1:19000/api/login", authInfo);
+        assertThat(actual.getStatusLine().getStatusCode(), is(401));
+    }
+
+    @Test
+    public void assertUnauthorized() {
+        assertThat(HttpTestUtil.unauthorizedGet("http://127.0.0.1:19000/api/unauthorized"), is(401));
+    }
+}