我当前的Android应用程序要求用户使用用户名和密码登录。
Android应用程序调用REST Web服务进行用户登录,我不想将密码作为明文传输。
如何保护用户密码以便服务器端识别/验证每个用户?
我目前正在尝试使用Jasypt库,如下所示: -
ConfigurablePasswordEncryptor passwordEncryptor = new ConfigurablePasswordEncryptor();
passwordEncryptor.setAlgorithm("SHA-1");
passwordEncryptor.setPlainDigest(true);
String encryptedPassword = passwordEncryptor.encryptPassword(userPassword);
...
if (passwordEncryptor.checkPassword(inputPassword, encryptedPassword)) {
// correct!
} else {
// bad login!
}
但是我的服务器端是用.NET编写的,据我所知Jasypt文档,密码加密器使用随机盐。
如何让我的服务器端代码与我发送的哈希用户密码相匹配?
我的所有web服务都有HTTPS端点,这是否保证在交换访问令牌时没有人可以“在飞行中”“看到”我的用户密码?
如果您使用Https(TLS),则任何拦截网络的人都无法访问您的密码。
您应该在服务器端代码中散列密码字符串,而不是在客户端中
你必须要小心自己的所作所为。考虑实现一个通用的双因素密钥共享算法,例如TOTP。 客户端散列是一种非常罕见但非常好的做法。这当然不会阻止黑客登录到用户的帐户,但它会阻止他们获取可能重用的纯文本密码。 我建议更改电子邮件和密码是在重置密码公式下完成的,这样就需要确认电子邮件/短信。 最后,正如您所做的那样,登录发生的连接是安全的非常重要,例如https / tls。
一个好的解决方案是避免使用传统的电子邮件/密码方法进行身份验证,并使用此处建议的OTP或一次性密码的另一个答案。
考虑用户体验:在移动设备上键入电子邮件和密码非常麻烦,烦人且笨拙。然后他们还要记住他们的密码?西方世界的普通人每天可能会使用10到15个应用程序,我们想要在他们的人力记忆库中为另一个密码征税,以便在他们乘坐地铁列车的时候笨拙地输入他们的手机?
尽管拼凑起来具有挑战性,但考虑一次性密码。有了它,用户输入电话号码作为识别令牌。
从理论上讲,每个用户都有自己独特的电话号码,用户可以轻松记住。由于您的用户在他们的Android设备上,到目前为止有道理,对吗?并没有尴尬的电子邮件和密码输入。
在他们输入他们的电话号码之后,我们然后将他们的代码发送到移动设备,这是一个4到6位数字。用户在应用程序中输入该代码,从而证明他们是电话号码所绑定的设备的所有者。
OTP优于电子邮件/密码的好处是它在用户部分需要的内存非常少。是的,它甚至比OAuth更好,因为如果用户从未通过移动浏览器登录Gmail帐户或Github帐户,该怎么办?然后他们回到移动设备的电子邮件/密码尴尬风格认证。
一次性密码是用户友好的。
但是你说没关系,但它是否安全,更重要的是问题......我怎样才能让我的服务器端代码与我发送的哈希用户密码相匹配?
是的,所以One Time Password技术始终是一个进行IMO的雄心勃勃的项目。
因此,我们需要保留用户应该输入到设备中的代码,以便我们可以在将来的某个时刻对其进行比较。当您生成代码时,请将其保存到Firebase,以便将来某个时候您可以回到Firebase并说出电话号码为212-555-1212的用户刚刚向您发送了代码1234,这是正确的代码吗?
因此,Firebase与OTP协同工作的方式是您可以将代码存储在Firebase中。然而,挑战实际上是向用户发送代码。这是一条实际的短信。要解决这个问题,你不能单独使用Firebase,你可以整合极受欢迎的Twilio。 Twilio就是通过手机短信与用户互动,因此我们可以利用Twilio向用户发送代码。
您还可以在Firebase中处理身份验证或用户系统。用户输入OTP后,我们会通过Firebase生成JSON Web令牌。
因此,所有JSON存储和反映用户身份的所有信息都可以保存在Firebase上。
但是我还没有回答这个问题的另一部分:
如何保护用户密码以便服务器端识别/验证每个用户?
好的,所以你需要比较一些服务器上的代码。它不能是Firebase,因为Firebase只是一个数据存储区,它是一个存储JSON数据的地方,它不能让我们运行自定义代码。
那么你是否编写了一个服务器来进行代码比较?我们不想在用户的设备上进行这种比较。
那么我们该怎么办?另外,我们如何生成代码?也不要使用用户的设备。
那么我们在哪里生成代码?我们知道使用Firebase数据存储来存储代码,但我们如何生成它?
这对谷歌云功能来说是一个很好的工作。
因此,Google云端功能是在Google服务器上按需运行一次的代码段。 GCF具有紧密的互操作性以及与Firebase数据存储的集成。
我们可以为Firebase内部的数据添加一些逻辑或处理。 GCF将允许您使用一些自定义逻辑来生成代码并将其保存到Firebase,GCF也可以在用户发送代码后对其进行比较。
AWS Lambda和GCF在功能上几乎完全相同,因此也可以选择。
在客户端(移动应用程序)和服务器之间实现身份验证和授权时,您需要考虑几件事情。首先,您的服务器有什么身份验证和授权机制来请求api端点? (它是双因素身份验证吗?它是基于承载令牌(授权类型的用户名和密码)吗?它是基于承载令牌(grant-type access-token
)吗?
其次,正如您所提到的,服务器编程是基于.Net的,但您是否可以更具体地说明您的服务层(Api)是用WebApi 2还是OData编写的?
最后,您的服务器是否允许使用或不使用SSH进行通信,即HTTP与HTTPS通信?如果是使用SSH,则可以转移用户凭据,即用户名和密码,但它永远不会通过HTTP保护到转移凭证。
然后,只有它在您的结束,即在Android移动应用程序中实现根据服务器要求与api端点通信的身份验证和授权机制。
例如,我的服务器需要实现基于令牌的身份验证(bearer token和grant-type password
)来使每个服务器请求(GET, POST, DELETE, PUT)
,并且我已经使用改进客户端实现如下:
public Retrofit getRetrofitClient() {
// first add the authorization header
OkHttpClient mOkClient = new OkHttpClient.Builder().addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request newRequest = chain.request().newBuilder()
.addHeader("Authorization", "XXXXXXXXXXXX")
.build();
return chain.proceed(newRequest);
}
}).build();
if (retrofit==null) {
retrofit = new Retrofit.Builder()
.client(mOkClient)
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
.build();
}
return retrofit;
}
我的服务是
public interface LoginService {
@POST("/api/token")
@FormUrlEncoded
Call<TokenModel> getToken(@Field("username") String username,
@Field("password") String password,
@Field("grant_type") String grantType);
}
现在我可以在每个请求中使用此令牌与服务器进行通信。我不需要通过公共互联网传输用户名和密码,而是仅使用令牌,它有24小时到期(因为服务器已实现此令牌到期日期)。
希望它能帮助您了解客户端(Android移动应用程序)和服务器之间的身份验证和授权机制。