23

Tôi đã nghiên cứu sâu rộng về cách xác thực ứng dụng của bạn (Android, iOS, web-app) với Cloud Endpoints mà không cần yêu cầu người dùng của bạn sử dụng thông tin đăng nhập tài khoản Google của họ cách documentation hiển thị cho bạn.Xác thực khách hàng của bạn với Cloud Endpoints mà không cần đăng nhập Tài khoản Google

Lý do cho điều này là tôi muốn bảo mật API của mình hoặc "khóa nó" chỉ với các khách hàng được chỉ định của tôi. Đôi khi tôi sẽ có một ứng dụng không có đăng nhập người dùng. Tôi sẽ ghét người dùng của tôi phải đăng nhập ngay bây giờ để API của tôi an toàn. Hoặc vào những lúc khác, tôi chỉ muốn quản lý người dùng của riêng mình như trên trang web và không sử dụng Google+, Facebook hoặc bất kỳ xác thực đăng nhập nào khác.

Để bắt đầu, hãy để tôi hiển thị đầu tiên cách bạn có thể xác thực ứng dụng Android bằng API điểm cuối đám mây bằng thông tin đăng nhập Tài khoản Google như được chỉ định trong documentation. Sau đó tôi sẽ cho bạn thấy những phát hiện của tôi và một khu vực tiềm năng cho một giải pháp mà tôi cần giúp đỡ.

(1) Chỉ định ID ứng dụng khách (clientIds) của ứng dụng được ủy quyền để thực hiện yêu cầu đối với chương trình phụ trợ API của bạn và (2) thêm thông số Người dùng vào tất cả các phương pháp được hiển thị để được bảo vệ theo ủy quyền.

public class Constants { 
     public static final String WEB_CLIENT_ID = "1-web-apps.apps.googleusercontent.com"; 
     public static final String ANDROID_CLIENT_ID = "2-android-apps.googleusercontent.com"; 
     public static final String IOS_CLIENT_ID = "3-ios-apps.googleusercontent.com"; 
     public static final String ANDROID_AUDIENCE = WEB_CLIENT_ID; 

     public static final String EMAIL_SCOPE = "https://www.googleapis.com/auth/userinfo.email"; 
    } 


import com.google.api.server.spi.auth.common.User; //import for the User object 

    @Api(name = "myApi", version = "v1", 
     namespace = @ApiNamespace(ownerDomain = "${endpointOwnerDomain}", 
     ownerName = "${endpointOwnerDomain}", 
     packagePath="${endpointPackagePath}"), 
     scopes = {Constants.EMAIL_SCOPE}, 
     clientIds = {Constants.WEB_CLIENT_ID, Constants.ANDROID_CLIENT_ID, 
         Constants.IOS_CLIENT_ID, 
         Constants.API_EXPLORER_CLIENT_ID}, 
         audiences = {Constants.ANDROID_AUDIENCE}) 

    public class MyEndpoint { 

     /** A simple endpoint method that takes a name and says Hi back */ 
     @ApiMethod(name = "sayHi") 
     public MyBean sayHi(@Named("name") String name, User user) throws UnauthorizedException { 
      if (user == null) throw new UnauthorizedException("User is Not Valid"); 
      MyBean response = new MyBean(); 
      response.setData("Hi, " + name); 

      return response; 
     } 

    } 

(3) Trong Android gọi phương thức API trong một AsyncTask đảm bảo để vượt qua trong biến credential trong Builder:

class EndpointsAsyncTask extends AsyncTask<Pair<Context, String>, Void, String> { 
     private static MyApi myApiService = null; 
     private Context context; 

     @Override 
     protected String doInBackground(Pair<Context, String>... params) { 
      credential = GoogleAccountCredential.usingAudience(this, 
      "server:client_id:1-web-app.apps.googleusercontent.com"); 
      credential.setSelectedAccountName(settings.getString(PREF_ACCOUNT_NAME, null)); 
      if(myApiService == null) { // Only do this once 
       MyApi.Builder builder = new MyApi.Builder(AndroidHttp.newCompatibleTransport(), 
         new AndroidJsonFactory(), credential) 
        // options for running against local devappserver 
        // - 10.0.2.2 is localhost's IP address in Android emulator 
        // - turn off compression when running against local devappserver 
        .setRootUrl("http://<your-app-engine-project-id-here>/_ah/api/") 
        .setGoogleClientRequestInitializer(new GoogleClientRequestInitializer() { 
         @Override 
         public void initialize(AbstractGoogleClientRequest<?> abstractGoogleClientRequest) throws IOException { 
          abstractGoogleClientRequest.setDisableGZipContent(true); 
         } 
        }); 
        // end options for devappserver 

       myApiService = builder.build(); 
      } 

      context = params[0].first; 
      String name = params[0].second; 

      try { 
       return myApiService.sayHi(name).execute().getData(); 
      } catch (IOException e) { 
       return e.getMessage(); 
      } 
     } 

     @Override 
     protected void onPostExecute(String result) { 
      Toast.makeText(context, result, Toast.LENGTH_LONG).show(); 
     } 
    } 

gì đang xảy ra là trong ứng dụng Android của bạn, bạn đang hiển thị bộ chọn tài khoản Google trước tiên, lưu trữ email tài khoản Google đó trong các tùy chọn được chia sẻ của bạn và sau đó đặt nó làm một phần của đối tượng GoogleAccountCredential (thông tin thêm về cách thực hiện điều đó here).

Máy chủ Google App Engine nhận yêu cầu của bạn và kiểm tra. Nếu Máy khách Android là một trong những ứng dụng bạn đã chỉ định trong ký hiệu @Api thì máy chủ sẽ đưa đối tượng com.google.api.server.spi.auth.common.User vào phương pháp API của bạn. Giờ đây, bạn có trách nhiệm kiểm tra xem đối tượng Usernull hoặc không phải bên trong phương thức API của bạn. Nếu đối tượng Usernull, bạn nên ném ngoại lệ trong phương pháp của mình để ngăn không cho nó chạy. Nếu bạn không thực hiện kiểm tra này, phương thức API của bạn sẽ thực thi (không có không nếu bạn đang cố hạn chế quyền truy cập vào nó).

Bạn có thể nhận được ANDROID_CLIENT_ID bằng cách truy cập Bảng điều khiển dành cho nhà phát triển của Google. Ở đó, bạn cung cấp tên gói của Ứng dụng Android và SHA1 tạo cho bạn id ứng dụng khách Android để sử dụng trong chú thích @Api của bạn (hoặc đặt nó trong một lớp Constants như được chỉ định ở trên để biết khả năng sử dụng).

Tôi đã thực hiện một số thử nghiệm rộng rãi với tất cả những điều trên và đây là những gì tôi thấy:

Nếu bạn chỉ định không có thật hoặc không hợp lệ Android ClientId trong @Api chú thích của mình, đối tượng User sẽ null tại của bạn Phương thức API. Nếu bạn đang kiểm tra if (user == null) throw new UnauthorizedException("User is Not Valid"); thì phương thức API của bạn sẽ không chạy. Điều này là đáng ngạc nhiên bởi vì nó xuất hiện có một số xác nhận đằng sau hậu trường đang diễn ra trong Cloud Endpoints kiểm tra xem Android ClientId có hợp lệ hay không. Nếu nó không hợp lệ, nó sẽ không trả về đối tượng User - ngay cả khi người dùng cuối đăng nhập vào tài khoản Google của họ và GoogleAccountCredential hợp lệ.

Câu hỏi của tôi là, có ai biết cách tôi có thể tự kiểm tra loại xác thực ClientId đó trong các phương thức Cloud Endpoints của mình không? Ví dụ: thông tin đó có được chuyển đi trong một ví dụ HttpHeader không?

Loại được chèn khác trong Điểm kết thúc đám mây là javax.servlet.http.HttpServletRequest. Bạn có thể nhận được yêu cầu như thế này trong phương thức API của mình:

@ApiMethod(name = "sayHi") 
      public MyBean sayHi(@Named("name") String name, HttpServletRequest req) throws UnauthorizedException { 

       String Auth = req.getHeader("Authorization");//always null based on my tests 
       MyBean response = new MyBean(); 
       response.setData("Hi, " + name); 

       return response; 
      } 

     } 

Nhưng tôi không chắc chắn liệu thông tin cần thiết có hay cách nhận thông tin đó.

Chắc chắn ở đâu đó phải có một số dữ liệu cho chúng tôi biết nếu Khách hàng là người được ủy quyền và chỉ định trong số @ApiclientIds. Bằng cách này, bạn có thể khóa API của mình xuống ứng dụng Android (và các khách hàng tiềm năng khác) mà không cần phải cho người dùng cuối của bạn đăng nhập (hoặc chỉ cần tạo tên người dùng đơn giản + đăng nhập mật khẩu của riêng bạn).

Đối với tất cả những điều này để làm việc tuy nhiên, bạn sẽ phải vượt qua trong null trong đối số thứ ba của Builder của bạn như thế này:

MyApi.Builder builder = MyApi.Builder mới (AndroidHttp.newCompatibleTransport(), mới AndroidJsonFactory(), null)

Sau đó, trong phương pháp API của bạn trích xuất cuộc gọi đến từ một khách hàng đã được xác thực hay không, hoặc ném ngoại lệ hoặc chạy bất kỳ mã nào bạn muốn.

Tôi biết điều này là có thể bởi vì khi sử dụng một GoogleAccountCredential trong Builder, bằng cách nào đó điểm cuối đám mây biết có hay không cuộc gọi đến từ một ứng dụng xác thực và sau đó, hoặc tiêm đối tượng User của nó vào phương pháp API hay không dựa trên đó.

Thông tin đó có thể ở trong phần đầu hoặc phần thân không? Nếu vậy, làm thế nào tôi có thể làm cho nó ra để sau này kiểm tra xem nó có hay không trong phương pháp API của tôi?

Lưu ý: Tôi đã đọc các bài đăng khác về chủ đề này. Họ cung cấp các cách để chuyển vào mã xác thực của riêng bạn - điều đó là tốt - nhưng .apk của bạn sẽ vẫn không an toàn nếu ai đó giải mã nó. Tôi nghĩ rằng nếu giả thuyết của tôi hoạt động, bạn sẽ có thể khóa API Cloud Endpoints của mình thành ứng dụng khách mà không cần bất kỳ thông tin đăng nhập nào.

Custom Authentication for Google Cloud Endpoints (instead of OAuth2)

Authenticate my "app" to Google Cloud Endpoints not a "user"

Google Cloud Endpoints without Google Accounts

EDIT: Chúng tôi sử dụng Hỗ trợ Vàng cho Nền tảng đám mây của Google và đã được nói chuyện qua lại với nhóm hỗ trợ của họ trong nhiều tuần. Đây là câu trả lời cuối cùng của họ đối với chúng tôi:.

"Thật không may, tôi đã không có bất kỳ may mắn về vấn đề này tôi đã hỏi xung quanh đội của tôi, và kiểm tra tất cả các tài liệu Dường như sử dụng OAuth2 là. Lý do là vì các máy chủ điểm cuối xử lý xác thực trước khi nó đạt đến ứng dụng của bạn.Điều này có nghĩa là bạn sẽ không có thể phát triển quy trình xác thực của riêng bạn và sẽ nhận được kết quả giống như những gì bạn thấy với các mã thông báo.

Tôi rất sẵn lòng gửi yêu cầu tính năng cho bạn. Nếu bạn có thể cung cấp thêm một ít thông tin về việc tại sao dòng chảy OAuth2 không làm việc cho khách hàng của bạn, tôi có thể đưa phần còn lại của thông tin với nhau và nộp cho người quản lý sản phẩm."

(cau mày mặt) - tuy nhiên, có thể vẫn còn có thể?

Trả lời

1

Tôi đã triển khai xác thực điểm cuối bằng tiêu đề tùy chỉnh "Ủy quyền" và nó hoạt động tốt. Trong trường hợp này, mã thông báo được đặt sau khi đăng nhập nhưng sẽ hoạt động giống với Kiểm tra các bài kiểm tra của bạn vì giá trị phải ở đó. Cách để lấy tiêu đề đó thực sự là:

String Auth = req.getHeader("Authorization");

Bạn có thể mang nó một bước xa hơn và xác định triển khai bạn thiết lập một Authenticator và áp dụng nó vào cuộc gọi API an toàn của bạn.

+0

'req.getHeader (" Ủy quyền ")' là 'null' nếu bạn không chuyển bằng chứng chỉ google từ ứng dụng khách Android. Tôi cũng đã cố gắng thực hiện một 'Authenticator' của riêng mình, nhưng vấn đề vẫn tồn tại: nếu ai đó biên dịch ứng dụng của bạn, họ có thể giả mạo ứng dụng của bạn vì' Authenticator' của bạn được viết bằng màu đen và trắng ở đó ... Vì vậy, tôi đã hy vọng có một số cách khác ... – Micro

+0

@ microror thats không đúng sự thật. Tôi đã phổ biến các tiêu đề tùy chỉnh như xác thực với giá trị do người dùng xác định (ví dụ: từ ứng dụng khách góc cạnh) – jirungaray

+0

Phần nào không đúng? Có, bạn có thể cư trú các tiêu đề tùy chỉnh như auth của chính mình, nhưng thực tế vẫn còn: ứng dụng của bạn có thể được biên dịch và giả mạo. Vui lòng đọc lại câu hỏi của tôi - câu hỏi cho thấy có một số loại xác thực mà Cloud Endpoints đang thực hiện để xác minh ứng dụng khách Android mà họ không nói với chúng tôi vì vậy có thể chúng tôi có thể đưa ra câu trả lời. – Micro

0

Vì vậy, bạn không có bất kỳ thông tin cụ thể của người dùng, nhưng chỉ muốn đảm bảo rằng chỉ ứng dụng của bạn có khả năng giao tiếp với phụ trợ của bạn ... Đây là những gì tôi nghĩ,

thay đổi

@Api(name = "myApi", version = "v1", 
     namespace = @ApiNamespace(ownerDomain = "${endpointOwnerDomain}", 
     ownerName = "${endpointOwnerDomain}", 
     packagePath="${endpointPackagePath}"), 
     scopes = {Constants.EMAIL_SCOPE}, 
     clientIds = {Constants.WEB_CLIENT_ID, Constants.ANDROID_CLIENT_ID, 
         Constants.IOS_CLIENT_ID, 
         Constants.API_EXPLORER_CLIENT_ID}, 
         audiences = {Constants.ANDROID_AUDIENCE}) 
{ 
... 
} 

để

@Api(name = "myApi", version = "v1", 
     namespace = @ApiNamespace(ownerDomain = "${endpointOwnerDomain}", 
     ownerName = "${endpointOwnerDomain}", 
     packagePath="${endpointPackagePath}"), 
     scopes = {Constants.EMAIL_SCOPE}, 
     clientIds = {Constants.ANDROID_CLIENT_ID}, 
     audiences = {Constants.ANDROID_AUDIENCE}) 

{ 
... 
} 

Client-ID được tạo ra từ các chữ ký của ứng dụng của bạn. Nó không thể được nhân rộng. Nếu bạn chỉ cho phép thiết bị đầu cuối chấp nhận yêu cầu từ Ứng dụng Android, sự cố của bạn sẽ được giải quyết.

Hãy cho tôi biết điều này có hiệu quả không.

+0

Cảm ơn bạn đã trả lời nhưng điều này không hiệu quả. Xóa 'Constants.WEB_CLIENT_ID, Constants.IOS_CLIENT_ID, Constants.API_EXPLORER_CLIENT_ID' không làm gì cả. – Micro

+0

@MicroR có thể nó chỉ dành cho OAuth. Đối với vấn đề của bạn, bạn sẽ cần xác thực tùy chỉnh. Tại sao không sử dụng một cái gì đó dựa trên RSA? –

1

Đối mặt với cùng một vấn đề để tìm giải pháp gọi API của tôi một cách an toàn từ điểm cuối, mà không sử dụng Tài khoản Google. Chúng ta không thể dịch ngược một IOS App (Bundle), nhưng ngược một App Android là đơn giản như vậy ..

Giải pháp tôi thấy là không hoàn hảo nhưng thực hiện công việc khá tốt:

  1. trên Android APP, Tôi chỉ tạo một biến String không đổi, được đặt tên APIKey, với nội dung đơn giản (Ví dụ "helloworld145698")
  2. Sau đó, tôi mã hóa nó với sha1, md5 tiếp theo và cuối cùng là sha1 (Thứ tự và tần suất mã hóa cho bạn) và lưu trữ biến trên SharedPref (Dành cho Android) ở chế độ riêng tư (Thực hiện hành động này trên một lớp ngẫu nhiên trong ứng dụng của bạn) Kết quả này được mã hóa Tôi ủy quyền trên Chương trình phụ trợ của tôi!
  3. On backend của tôi, tôi chỉ cần thêm một tham số (tên mã thông báo cho dụ) trên tất cả các yêu cầu

Ví dụ:

@ApiMethod(name = "sayHi") 
    public void sayHi(@Named("name") String name, @Named("Token") String token) { 

    if (token == tokenStoreOnAPIServer) { 
     //Allow it 
    } else { 
     //Refuse it and print error 
    } 

} 
  1. On android, ProGuard hoạt động để làm xáo trộn mã của bạn. Nó sẽ thực sự không thể đọc được cho bất cứ ai cố gắng biên soạn lại ứng dụng của bạn (kỹ thuật ngược thực sự là hardcore)

không phải là giải pháp an toàn hoàn hảo, nhưng nó hoạt động, và nó sẽ được thực sự thực sự (thực sự) khó để tìm thấy khóa API thực sự cho bất kỳ ai cố gắng đọc mã của bạn sau khi giải mã.

+0

Cảm ơn bạn đã đề xuất. – Micro

Các vấn đề liên quan