日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
gRPC 的兩種不同認(rèn)證方式

在之前的文章中,松哥和小伙伴們聊了 gRPC+JWT 進(jìn)行認(rèn)證,這也是我們常用的認(rèn)證方式之一,考慮到文章內(nèi)容的完整性,今天松哥再來(lái)和小伙伴們聊一聊在 gRPC 中通過(guò) HttpBasic 進(jìn)行認(rèn)證,HttpBasic 認(rèn)證有一些天然的缺陷,這個(gè)在接下來(lái)的文章中松哥也會(huì)和大家進(jìn)行分析。

創(chuàng)新互聯(lián)從2013年開(kāi)始,先為巧家等服務(wù)建站,巧家等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為巧家企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問(wèn)題。

1. 什么是 Basic 認(rèn)證

HTTP Basic authentication 中文譯作 HTTP 基本認(rèn)證,在這種認(rèn)證方式中,將用戶的登錄用戶名/密碼經(jīng)過(guò) Base64 編碼之后,放在請(qǐng)求頭的 Authorization 字段中,從而完成用戶身份的 認(rèn)證。

這是一種在 RFC7235(https://tools.ietf.org/html/rfc7235) 規(guī)范中定義的認(rèn)證方式,當(dāng)客戶端發(fā)起一個(gè)請(qǐng)求之后,服務(wù)端可以針對(duì)該請(qǐng)求返回一個(gè)質(zhì)詢信息,然后客戶端再????供用戶的憑 證信息。具體的質(zhì)詢與應(yīng)答流程如圖所示:

由上圖可以看出,客戶端的用戶名和密碼只是簡(jiǎn)單做了一個(gè) Base64 轉(zhuǎn)碼,然后放到請(qǐng)求頭中就傳輸?shù)椒?wù)端了。

我們?cè)谌粘5拈_(kāi)發(fā)中,其實(shí)也很少見(jiàn)到這種認(rèn)證方式,有的讀者可能在一些老舊路由器中見(jiàn)過(guò)這種認(rèn)證方式;另外,在一些非公開(kāi)訪問(wèn)的 Web 應(yīng)用中,可能也會(huì)見(jiàn)到這種認(rèn)證方式。為什么很少見(jiàn)到這種認(rèn)證方式的應(yīng)用場(chǎng)景呢?主要還是安全問(wèn)題。

HTTP 基本認(rèn)證沒(méi)有對(duì)傳輸?shù)膽{證信息進(jìn)行加密,僅僅只是進(jìn)行了 Base64 編碼,這就造成了很大的安全隱患,所以如果用到了 HTTP 基本認(rèn)證,一般都是結(jié)合 HTTPS 一起使用;同 時(shí),一旦使用 HTTP 基本認(rèn)證成功后,由于令牌缺乏有效期,除非用戶重啟瀏覽器或者修改密碼,否則沒(méi)有辦法退出登錄。

2. gRPC 中的基本認(rèn)證

gRPC 并沒(méi)有為 Http Basic 認(rèn)證提供專門的 API,如果我們需要在 gRPC 中進(jìn)行 Http Basic 認(rèn)證,需要自己手工處理。

不過(guò)相信小伙伴們看了上面的流程圖之后,對(duì)于手工處理 gRPC+Http Basic 也沒(méi)啥壓力。

首先我們先來(lái)看客戶端的代碼:

public class HttpBasicCredential extends CallCredentials {
private String username;
private String password;

public HttpBasicCredential(String username, String password) {
this.username = username;
this.password = password;
}

@Override
public void applyRequestMetadata(RequestInfo requestInfo, Executor executor, MetadataApplier metadataApplier) {
executor.execute(() -> {
try {
String token = new String(Base64.getEncoder().encode((username + ":" + password).getBytes()));
Metadata headers = new Metadata();
headers.put(Metadata.Key.of(AuthConstant.AUTH_HEADER, Metadata.ASCII_STRING_MARSHALLER),
String.format("%s %s", AuthConstant.AUTH_TOKEN_TYPE, token));
metadataApplier.apply(headers);
} catch (Throwable e) {
metadataApplier.fail(Status.UNAUTHENTICATED.withCause(e));
}
});
}

@Override
public void thisUsesUnstableApi() {

}
}
  • 當(dāng)客戶端發(fā)起一個(gè)請(qǐng)求的時(shí)候,我們構(gòu)建一個(gè) HttpBasicCredential 對(duì)象,并傳入用戶名和密碼。
  • 該對(duì)象核心的處理邏輯在 applyRequestMetadata 方法中,我們先按照 username + ":" + password 的形式將用戶名和密碼拼接成一個(gè)字符串,并對(duì)這個(gè)字符串進(jìn)行 Base64 編碼。
  • 最后將編碼結(jié)果放在請(qǐng)求頭中,請(qǐng)求頭的 KEY 就是 AuthConstant.AUTH_HEADER? 變量,對(duì)應(yīng)的具體值是 Authorization,請(qǐng)求頭的 value 是通過(guò) String.format? 函數(shù)拼接出來(lái)的,實(shí)際上就是在 Base64 的編碼的字符串上加上了 Basic 前綴。

這塊就是純手工操作,技術(shù)原理跟我們之前講的 JWT+gRPC 沒(méi)有任何差別,基本上是一模一樣的,所以我就不啰嗦了。

來(lái)看下前端請(qǐng)求該如何發(fā)起:

public class LoginClient {
public static void main(String[] args) throws InterruptedException, SSLException {

File certFile = Paths.get( "certs", "ca.crt").toFile();
SslContext sslContext = GrpcSslContexts.forClient().trustManager(certFile).build();

ManagedChannel channel = NettyChannelBuilder.forAddress("local.javaboy.org", 50051)
.useTransportSecurity()
.sslContext(sslContext)
.build();

LoginServiceGrpc.LoginServiceStub stub = LoginServiceGrpc.newStub(channel).withDeadline(Deadline.after(3, TimeUnit.SECONDS));
sayHello(channel);
}

private static void sayHello(ManagedChannel channel) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
HelloServiceGrpc.HelloServiceStub helloServiceStub = HelloServiceGrpc.newStub(channel);
helloServiceStub
.withCallCredentials(new HttpBasicCredential("javaboy", "123"))
.sayHello(StringValue.newBuilder().setValue("wangwu").build(), new StreamObserver() {
@Override
public void onNext(StringValue stringValue) {
System.out.println("stringValue.getValue() = " + stringValue.getValue());
}

@Override
public void onError(Throwable throwable) {
System.out.println("throwable.getMessage() = " + throwable.getMessage());
}

@Override
public void onCompleted() {
countDownLatch.countDown();
}
});
countDownLatch.await();
}
}

通過(guò) withCallCredentials 方法,在客戶端發(fā)起請(qǐng)求的時(shí)候,把這段認(rèn)證信息攜帶上。

再來(lái)看看服務(wù)端的處理。

服務(wù)端通過(guò)一個(gè)攔截器來(lái)統(tǒng)一處理,從請(qǐng)求頭中提取出來(lái)認(rèn)證信息并解析判斷,邏輯如下:

public class AuthInterceptor implements ServerInterceptor {
private JwtParser parser = Jwts.parser().setSigningKey(AuthConstant.JWT_KEY);

@Override
public ServerCall.Listener interceptCall(ServerCall serverCall, Metadata metadata, ServerCallHandler serverCallHandler) {
String authorization = metadata.get(Metadata.Key.of(AuthConstant.AUTH_HEADER, Metadata.ASCII_STRING_MARSHALLER));
Status status = Status.OK;
if (authorization == null) {
status = Status.UNAUTHENTICATED.withDescription("miss authentication token");
} else if (!authorization.startsWith(AuthConstant.AUTH_TOKEN_TYPE)) {
status = Status.UNAUTHENTICATED.withDescription("unknown token type");
} else {
try {
String token = authorization.substring(AuthConstant.AUTH_TOKEN_TYPE.length()).trim();
String[] split = new String(Base64.getDecoder().decode(token)).split(":");
String username = split[0];
String password = split[1];
if ("javaboy".equals(username) && "123".equals(password)) {
Context ctx = Context.current()
.withValue(AuthConstant.AUTH_CLIENT_ID, username);
return Contexts.interceptCall(ctx, serverCall, metadata, serverCallHandler);
}
} catch (JwtException e) {
status = Status.UNAUTHENTICATED.withDescription(e.getMessage()).withCause(e);
}
}
serverCall.close(status, new Metadata());
return new ServerCall.Listener() {
};
}
}
  1. 首先從請(qǐng)求頭中取出 Base64 編碼之后的令牌。
  2. 如果取出的值為 null,則返回 miss authentication token。
  3. 如果取出的令牌的起始字符不對(duì),則返回 unknown token type。
  4. 如果前面都沒(méi)問(wèn)題,則開(kāi)始對(duì)拿到的字符串進(jìn)行 Base64 解碼,解碼之后做字符串拆分,然后分別判斷用戶名和密碼是否正確,如果正確,則將用戶名存入到 Context 中,在后續(xù)的業(yè)務(wù)邏輯中就可以使用了。

服務(wù)端的啟動(dòng)代碼如下:

public class LoginServer {
Server server;

public static void main(String[] args) throws IOException, InterruptedException {
LoginServer server = new LoginServer();
server.start();
server.blockUntilShutdown();
}

public void start() throws IOException {
int port = 50051;
File certFile = Paths.get( "certs", "server.crt").toFile();
File keyFile = Paths.get("certs", "server.pem").toFile();
server = ServerBuilder.forPort(port)
.addService(ServerInterceptors.intercept(new HelloServiceImpl(), new AuthInterceptor()))
.useTransportSecurity(certFile,keyFile)
.build()
.start();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
LoginServer.this.stop();
}));
}

private void stop() {
if (server != null) {
server.shutdown();
}
}

private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
}

小伙伴們看下,就是用了下這個(gè)攔截器而已。

最后,在業(yè)務(wù)代碼中,也可以直接訪問(wèn)到剛剛認(rèn)證成功的用戶名:

public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {
@Override
public void sayHello(StringValue request, StreamObserver responseObserver) {
String clientId = AuthConstant.AUTH_CLIENT_ID.get();
responseObserver.onNext(StringValue.newBuilder().setValue(clientId + " say hello:" + request.getValue()).build());
responseObserver.onCompleted();
}
}

好啦,大功告成。

3. 小結(jié)

和之前的 JWT 相比,Http Basic 認(rèn)證的缺點(diǎn)還是非常明顯的,但是從認(rèn)證流程來(lái)說(shuō),感覺(jué)兩者差別不大,只是創(chuàng)建令牌和解析令牌的方式不同而已。

感興趣的小伙伴可以嘗試一下哦。

本文松哥只貼出來(lái)了一些關(guān)鍵代碼,完整的代碼小伙伴們可以從 GitHub 上下載:https://github.com/lenve/javaboy-code-samples


當(dāng)前名稱:gRPC 的兩種不同認(rèn)證方式
當(dāng)前URL:http://www.dlmjj.cn/article/djgoige.html