前言
前几日在浏览
devblogs.microsoft.com
的时候,看到了一篇名为
Image to Text with Semantic Kernel and HuggingFace
的文章。这篇文章大致的内容讲的是,使用
Semantic Kernel
结合
HuggingFace
来实现图片内容识别。注意,这里说的是图片内容识别,并非是
OCR
,而是它可以大致的描述图片里的主要内容。我个人对这些还是有点兴趣的,于是就尝试了一下,本文就是我体验过程的记录。
示例
话不多说,直接展示代码。按照文档上说的,使用
HuggingFace ImageToText
构建自己的应用程序时,需要使用以下的包
- Microsoft.SemanticKernel
- Microsoft.SemanticKernel.Connectors.HuggingFace
第一个包是
SemanticKernel
包,提供构建
AI
应用的基础能力。第二个包是
HuggingFace
包,提供
HuggingFace
的
API
,方便我们调用
HuggingFace
的模型。需要注意的是这个包是预发行版,所以在用
VS
添加的时候需要在
VS
勾选
包括预发行版
。使用起来也非常简单,代码如下所示
var kernel = Kernel.CreateBuilder().AddHuggingFaceImageToText("Salesforce/blip-image-captioning-base").Build();
IImageToTextService service = kernel.GetRequiredService<IImageToTextService>();
var imageBinary = File.ReadAllBytes(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "demo.jpg"));
var imageContent = new ImageContent(imageBinary) { MimeType = "image/jpeg" };
var textContent = await service.GetTextContentAsync(imageContent);
Console.WriteLine($"已识别图片中描述的内容: {textContent.Text}");
代码很简单,运行起来试试效果,发现是直接报错了,报错信息如下:
Microsoft.SemanticKernel.HttpOperationException:“由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。 (api-inference.huggingface.co:443)”
原因也很简单,我本地连接不了
huggingface
,这个需要换种上网方式才能解决。看来默认是请求的
api-inference.huggingface.co:443
这个地址,在源码中求证了一下
HuggingFaceClient.cs#L41
,发现确实是这样
internal sealed class HuggingFaceClient
{
private readonly IStreamJsonParser _streamJsonParser;
private readonly string _modelId;
private readonly string? _apiKey;
private readonly Uri? _endpoint;
private readonly string _separator;
private readonly HttpClient _httpClient;
private readonly ILogger _logger;
internal HuggingFaceClient(
string modelId,
HttpClient httpClient,
Uri? endpoint = null,
string? apiKey = null,
IStreamJsonParser? streamJsonParser = null,
ILogger? logger = null)
{
Verify.NotNullOrWhiteSpace(modelId);
Verify.NotNull(httpClient);
//默认请求地址
endpoint ??= new Uri("https://api-inference.huggingface.co");
this._separator = endpoint.AbsolutePath.EndsWith("/", StringComparison.InvariantCulture) ? string.Empty : "/";
this._endpoint = endpoint;
this._modelId = modelId;
this._apiKey = apiKey;
this._httpClient = httpClient;
this._logger = logger ?? NullLogger.Instance;
this._streamJsonParser = streamJsonParser ?? new TextGenerationStreamJsonParser();
}
}
它只是默认情况下请求的
api-inference.huggingface.co
这个地址,如果想要请求其他地址的话,需要自己实现一个
api
,然后通过
SemanticKernel
调用。
曲线实现
上面提到了既然是
huggingface
的
api
我们访问不到,而且我不是很喜欢这种在线方式,太依赖三方接口的稳定性了,我更喜欢本地可以部署的,这样的话就不用考虑网络和稳定性问题了。于是想到了一个曲线的方式,那是不是可以自己实现一个
api
,然后通过
SemanticKernel
调用呢?答案是肯定的。
blip-image-captioning-base模型
通过上面的示例我们可以看到它使用
ImageToText
图片识别模型使用的是
Salesforce/blip-image-captioning-base
这个模型,我们可以自行下载这个模型到本地。上面说了
huggingface
需要换种上网方式,不过没关系这个国内是有镜像网站的
https://hf-mirror.com/
,找到模型地址
Salesforce/blip-image-captioning-base
选择
Files and versions
标签把里面的所有文件下载到本地文件夹即可,大概是
1.84 G
左右。比如我是放到我的
D:\Users\User\blip-image-captioning-base
文件夹内,目录结构如下所示
这个模型没有特殊要求,我的电脑是
16G内存
和
i5
处理器都可以运行起来。接下来用调用这个模型试一试,该模型是适配了
transformers
框架,所以调用起来比较加单,代码如下所示
from PIL import Image
from transformers import BlipProcessor, BlipForConditionalGeneration
processor = BlipProcessor.from_pretrained("D:\\Users\\User\\blip-image-captioning-base")
model = BlipForConditionalGeneration.from_pretrained("D:\\Users\\User\\blip-image-captioning-base")
img_url = '01f8115545963d0000019ae943aaad.jpg@1280w_1l_2o_100sh.jpg'
raw_image = Image.open(img_url).convert('RGB')
inputs = processor(raw_image, return_tensors="pt")
out = model.generate(**inputs)
en_text = processor.decode(out[0], skip_special_tokens=True)
print(f'已识别图片中描述的内容:{en_text}')
然后我使用了我本地的一张图片
运行这段代码之后输出信息如下所示
已识别图片中描述的内容:a kitten is standing on a tree stump
识别的结果描述的和图片内容大致来说是一致的,看来简单的图片效果还是不错的。不过美中不足的是,它说的是英文,给中国人看说英文这明显不符合设定。所以还是得想办法把英文翻译成中文。
opus-mt-en-zh模型
上面我们看到了
blip-image-captioning-base
模型效果确实还可以,只是它返回的是英文内容,这个对于英文不足六级的人来说读起来确实不方便。得想办法解决把英文翻译成中文的问题。因为不想调用翻译接口,所以这里我还是想使用模型的方式来解决这个问题。使用
Bing
搜索了一番,发现推荐的
opus-mt-en-zh
模型效果不错,于是打算试一试。还是在
hf-mirror.com
上下载模型到本地文件夹内,方式方法如上面的
blip-image-captioning-base
模型一致。它的大小大概在
1.41 GB
左右,也是
CPU
可运行的,比如我的是下载到本地
D:\Users\User\opus-mt-en-zh
路径下,内容如下所示
接下来还是老规矩,调用一下这个模型看看效果,不过在
huggingface
对应的仓库里并没有给出如何使用模型的示例,于是去
stackoverflow
上找到两个类似的内容参考了一下
通过上面的连接可以看到,非常好的地方就是,这个模型也是兼容
transformers
框架的,所以调用起来非常简单,把上面的英文内容拿过来试一试, 代码如下所示
from transformers import AutoTokenizer, AutoModelWithLMHead
model = AutoModelWithLMHead.from_pretrained("D:\\Users\\User\\opus-mt-en-zh")
tokenizer = AutoTokenizer.from_pretrained("D:\\Users\\User\\opus-mt-en-zh")
# 英文文本
en_text='a kitten is standing on a tree stump'
encoded = tokenizer([en_text], return_tensors="pt")
translation = model.generate(**encoded)
# 翻译后的中文内容
zh_text = tokenizer.batch_decode(translation, skip_special_tokens=True)[0]
print(f'已识别图片中描述的内容:\r\n英文:{en_text}\r\n中文:{zh_text}')
运行这段代码之后输出信息如下所示
已识别图片中描述的内容:
英文:a kitten is standing on a tree stump
中文:一只小猫站在树桩上
这下看着舒服了,至少不用借助翻译工具了。模型的部分到此就差不多了,接下来看如何整合一下模型的问题。
结合Microsoft.SemanticKernel.Connectors.HuggingFace
上面我们调研了图片内容识别的模型和英文翻译的模型,接下来我们看一下如何使用
Microsoft.SemanticKernel.Connectors.HuggingFace
去整合我们本地的模型。我们通过上面了解到了他说基于
http
的方式去调用了,这就很明确了。只需要知道调用的路径、请求参数、返回参数就可以自己写接口来模拟了。这个就需要去看一下
SemanticKernel
里面涉及的代码了。核心类就是
HuggingFaceClient
类,我们来看下它的
GenerateTextAsync
方法的代码
public async Task<IReadOnlyList<TextContent>> GenerateTextAsync(
string prompt,
PromptExecutionSettings? executionSettings,
CancellationToken cancellationToken)
{
string modelId = executionSettings?.ModelId ?? this._modelId;
var endpoint = this.GetTextGenerationEndpoint(modelId);
var request = this.CreateTextRequest(prompt, executionSettings);
using var httpRequestMessage = this.CreatePost(request, endpoint, this._apiKey);
string body = await this.SendRequestAndGetStringBodyAsync(httpRequestMessage, cancellationToken)
.ConfigureAwait(false);
var response = DeserializeResponse<TextGenerationResponse>(body);
var textContents = GetTextContentFromResponse(response, modelId);
return textContents;
}
//组装请求路径方法
private Uri GetTextGenerationEndpoint(string modelId)
=> new($"{this._endpoint}{this._separator}models/{modelId}");
private HttpRequestMessage CreateImageToTextRequest(ImageContent content, PromptExecutionSettings? executionSettings)
{
var endpoint = this.GetImageToTextGenerationEndpoint(executionSettings?.ModelId ?? this._modelId);
var imageContent = new ByteArrayContent(content.Data?.ToArray());
imageContent.Headers.ContentType = new(content.MimeType);
var request = new HttpRequestMessage(HttpMethod.Post, endpoint)
{
Content = imageContent
};
this.SetRequestHeaders(request);
}
private Uri GetImageToTextGenerationEndpoint(string modelId)
=> new($"{this._endpoint}{this._separator}models/{modelId}");
通过上面的
GenerateTextAsync
方法代码我们可以得到我们自定义接口时所需要的全部信息
- 首先是请求路径问题, 我们通过
GetTextGenerationEndpoint
和
GetImageToTextGenerationEndpoint
方法可以看到,拼接的路径地址
服务地址/models/模型id
,比如我们上面调用的是
Salesforce/blip-image-captioning-base
模型,拼接的路径就是
models/Salesforce/blip-image-captioning-base
。
- 其次通过
CreateImageToTextRequest
方法我们可以得知,请求参数的类型是
ByteArrayContent
,请求参数的
ContentType
是
image/jpeg
。也就是把我们的图片内容转换成字节数组放到请求
body
请求体里即可,然后
POST
到具体的服务里即可。
- 通过
TextGenerationResponse
返回类型我们可以知道这个承载的是返回参数的类型里。
我们来看下
TextGenerationResponse
类的定义
internal sealed class TextGenerationResponse : List<GeneratedTextItem>
{
internal sealed class GeneratedTextItem
{
[JsonPropertyName("generated_text")]
public string? GeneratedText { get; set; }
}
}
这个参数比较简单,就是返回一个包含
generated_text
字段的数组即可对应成
json格式
的话就是
[{"generated_text":"识别结果"}]
。接下来我们需要做的是把模型整合换成
http接口
,这样的话
Microsoft.SemanticKernel.Connectors.HuggingFace
就可以调用这个接口了。这里我选择使用的是python的
fastapi
web框架去整合成
webapi
服务,其他框架也可以,只要入参返回的结果把握住就可以,整合后效果如下所示
import io
import uvicorn
from fastapi import FastAPI, Request
from PIL import Image
from transformers import BlipProcessor, BlipForConditionalGeneration, AutoTokenizer, AutoModelWithLMHead
app = FastAPI()
# 图片内容识别模型
processor = BlipProcessor.from_pretrained("D:\\Users\\User\\blip-image-captioning-base")
blipModel = BlipForConditionalGeneration.from_pretrained("D:\\Users\\User\\blip-image-captioning-base")
# 英文翻译模型
tokenizer = AutoTokenizer.from_pretrained("D:\\Users\\User\\opus-mt-en-zh")
opusModel = AutoModelWithLMHead.from_pretrained("D:\\Users\\User\\opus-mt-en-zh")
# 定义接口函数
@app.post("/models/Salesforce/blip-image-captioning-base", summary="图片内容识别")
async def blip_image_captioning_base(request: Request):
# 获取请求参数
request_object_content: bytes = await request.body()
# 转换图片内容
raw_image = Image.open(io.BytesIO(request_object_content)).convert('RGB')
# 识别图片内容
inputs = processor(raw_image, return_tensors="pt")
out = blipModel.generate(**inputs)
en_text = processor.decode(out[0], skip_special_tokens=True)
# 英译汉
encoded = tokenizer([en_text], return_tensors="pt")
translation = opusModel.generate(**encoded)
zh_text = tokenizer.batch_decode(translation, skip_special_tokens=True)[0]
return [{"generated_text": zh_text}]
if __name__ == '__main__':
# 运行fastapi程序
uvicorn.run(app="snownlpdemo:app", host="0.0.0.0", port=8000, reload=True)
这里我们把服务暴露到
8000
端口上去,等待服务启动成功即可,然后我们去改造
Microsoft.SemanticKernel.Connectors.HuggingFace
的代码如下所示
//这里我们传递刚才自行构建的fastapi服务地址
var kernel = Kernel.CreateBuilder().AddHuggingFaceImageToText("Salesforce/blip-image-captioning-base", new Uri("http://127.0.0.1:8000")).Build();
IImageToTextService service = kernel.GetRequiredService<IImageToTextService>();
var imageBinary = File.ReadAllBytes(Path.Combine(Directory.GetCurrentDirectory(), "01f8115545963d0000019ae943aaad.jpg@1280w_1l_2o_100sh.jpg"));
var imageContent = new ImageContent(imageBinary) { MimeType = "image/jpeg" };
var textContent = await service.GetTextContentAsync(imageContent);
Console.WriteLine($"已识别图片中描述的内容: {textContent.Text}");
这样的话代码改造完成,需要注意的是得先运行
fastapi
服务等待服务启动成功之后,再去然后运行
dotnet
项目,运行起来效果如下所示
已识别图片中描述的内容: 一只小猫站在树桩上
改造成插件
我们使用上面的方式是比较生硬古板的,熟悉
SemanticKernel
的同学都清楚它是支持自定插件的,这样的话它可以根据我们的提示词来分析调用具体的插件,从而实现调用我们自定义的接口。这是一个非常实用的功能,让
SemanticKernel
的调用更加灵活,是对
AIGC
能力的扩展,可以让他调用我们想调用的接口或者服务等等。话不多说,我们定义一个插件让它承载我们识别图片的内容,这样的话就可以通过
SemanticKernel
的调用方式去调用这个插件了。定义插件的代码如下所示
public class ImageToTextPlugin
{
private IImageToTextService _service;
public ImageToTextPlugin(IImageToTextService service)
{
_service = service;
}
[KernelFunction]
[Description("根据图片路径分析图片内容")]
public async Task<string> GetImageContent([Description("图片路径")] string imagePath)
{
var imageBinary = File.ReadAllBytes(imagePath);
var imageContent = new ImageContent(imageBinary) { MimeType = "image/jpeg" };
var textContent = await _service.GetTextContentAsync(imageContent);
return $"图片[{imagePath}]分析内容为:{textContent.Text!}";
}
}
这里需要注意的是我们定义的方法的
Description
和参数的
Description
,其中
GetImageContent
方法的
Description
是
SemanticKernel
的提示词,这样在调用的时候就可以通过提示词来调用这个方法了。参数
imagePath
的
Description
这样
OpenAI
就知道如何在提示词里提取出来对应的参数信息了。好了接下来我们看下如何使用这个插件
using HttpClient httpClient = new HttpClient(new RedirectingHandler());
var executionSettings = new OpenAIPromptExecutionSettings()
{
ToolCallBehavior = ToolCallBehavior.EnableKernelFunctions,
Temperature = 1.0
};
var builder = Kernel.CreateBuilder().AddHuggingFaceImageToText("Salesforce/blip-image-captioning-base", new Uri("http://127.0.0.1:8000"));
var kernel = builder.Build();
ImageToTextPlugin imageToTextPlugin = new ImageToTextPlugin(kernel.GetRequiredService<IImageToTextService>());
kernel.Plugins.AddFromObject(imageToTextPlugin);
var chatCompletionService = new OpenAIChatCompletionService("gpt-3.5-turbo-0125", "你的apiKey", httpClient: httpClient);
Console.WriteLine("现在你可以开始和我聊天了,输入quit退出。等待你的问题:");
do
{
var prompt = Console.ReadLine();
if (!string.IsNullOrWhiteSpace(prompt))
{
if (prompt.ToLowerInvariant() == "quit")
{
Console.WriteLine("非常感谢!下次见。");
break;
}
else
{
var history = new ChatHistory();
history.AddUserMessage(prompt);
//调用gpt的chat接口
var result = await chatCompletionService.GetChatMessageContentAsync(history,
executionSettings: executionSettings,
kernel: kernel);
//判断gpt返回的结果是否是调用插件
var functionCall = ((OpenAIChatMessageContent)result).GetOpenAIFunctionToolCalls().FirstOrDefault();
if (functionCall != null)
{
kernel.Plugins.TryGetFunctionAndArguments(functionCall, out KernelFunction? pluginFunction, out KernelArguments? arguments);
var content = await kernel.InvokeAsync(pluginFunction!, arguments);
Console.WriteLine(content);
}
else
{
//不是调用插件这直接输出返回结果
Console.WriteLine(result.Content);
}
}
}
} while (true);
这里需要注意自定义的
RedirectingHandler
,如果你不是使用
OpenAI
的接口而是自己对接或者代理的
OpenAI
的接口,就需要自行定义
HttpClientHandler
来修改请求的
GPT
的服务地址。
public class RedirectingHandler : HttpClientHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
request.RequestUri = new UriBuilder(request.RequestUri!) { Scheme = "http", Host = "你的服务地址", Path= "/v1/chat/completions" }.Uri;
return base.SendAsync(request, cancellationToken);
}
}
这样的话我们就可以在于
GPT
的交互中调用我们自定义的插件了,当我们输入相关的提示词
OpenAI
的接口就可以根据提示词和插件信息返回调用哪个插件。使用了几张我本地的图片试了一下效果还是不错的,能分析出大致的图片内容,如下所示
这样使用起来就比较灵活了,在对话的过程中就可以使用本地的功能,不得不说有了插件化的能力
SemanticKernel
的功能就更加丰富了。关于插件化的实现原理也是比较简单,这是利用
OpenAI
对话接口的能力,我们只需要定义好插件和相关的提示词就可以,比如我们上面示例,使用
Fiddler
或
Charles
拦截一下发出的请求即可,它是发起的
HTTP请求
,请求格式如下
{
"messages": [
{
"content": "Assistant is a large language model.",
"role": "system"
},
{
"content": "请帮我分析这张图片的内容D:\\Software\\AI.Lossless.Zoomer-2.1.0-x64\\Release\\output\\20200519160906.png",
"role": "user"
}
],
"temperature": 1,
"top_p": 1,
"n": 1,
"presence_penalty": 0,
"frequency_penalty": 0,
"model": "gpt-3.5-turbo-0125",
"tools": [
{
"function": {
"name": "ImageToTextPlugin-GetImageContent",
"description": "根据图片路径分析图片内容",
"parameters": {
"type": "object",
"required": [
"imagePath"
],
"properties": {
"imagePath": {
"type": "string",
"description": "图片路径"
}
}
}
},
"type": "function"
}
],
"tool_choice": "auto"
}
通过请求
OpenAI
的
/v1/chat/completions
接口的请求参数我们可以大致了解它的工作原理,
SemanticKernel
通过扫描我们定义的插件的元数据比如
类_方法
、
方法的描述
、
参数的描述
来放入请求的
JSON
数据里,我们定义的
Description
里的描述作为提示词拆分来具体匹配插件的依据。接下来我们再来看一下这个接口的返回参数的内容
{
"id": "chatcmpl-996IuJbsTrXHcHAM3dqtguwNi9M3Z",
"object": "chat.completion",
"created": 1711956212,
"model": "gpt-35-turbo",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_4aN9xUhly2cEbNmzRcIh1it0",
"type": "function",
"function": {
"name": "ImageToTextPlugin-GetImageContent",
"arguments": "{\"imagePath\":\"D:\\\\Software\\\\AI.Lossless.Zoomer-2.1.0-x64\\\\Release\\\\output\\\\20200519160906.png\"}"
}
}
]
},
"finish_reason": "tool_calls"
}
],
"usage": {
"prompt_tokens": 884,
"completion_tokens": 49,
"total_tokens": 933
},
"system_fingerprint": "fp_2f57f81c11"
}
OpenAI
接口给我们返回了它选择的插件信息,告诉我们可以调用
ImageToTextPlugin-GetImageContent
这个方法,传递的参数则是
{\"imagePath\":\"D:\\\\Software\\\\AI.Lossless.Zoomer-2.1.0-x64\\\\Release\\\\output\\\\20200519160906.png\"}
,这是
GPT
帮我们分析的结果,
SemanticKernel
根据这个信息来调用我们本地的插件,执行具体操作。这里
GPT
的起到的作用就是,我们请求的时候提交插件的元数据,
GPT
根据提示词和插件的元数据帮我分析我们可以调用哪个插件,并且把插件参数帮我们分析出来,这样我们就可以根据返回的插件元数据来调用我们本地的插件了。
需要注意的,目前我尝试的是只有
OpenAI
或
AzureOpenAI
提供的对话接口支持插件的能力,国内的模型我试了一下比如
文心一言
、
讯飞星火
、
通义千问
、
百川
都不支持,至少通过
OneApi
对接过来的不支持,不知道是不是我姿势不对。
参考连接
以下是学习研究过程中参考的一些连接,在这里展示出来供大家参考。涉及到学习参考、解决问题、查找资源相关。毕竟人生地不熟的,需要找到方向
总结
本文缘起来于在
devblogs
上看到的一篇文章,感觉比较有趣,便动手实践一下。其中遇到了问题,便部署本地模型来实现,最终实现了
Microsoft.SemanticKernel.Connectors.HuggingFace
调用本地模型实现图片内容识别。最终把它定义成一个插件,这样在
SemanticKernel
中就可以通过调用插件的方式来调用本地模型,实现图片内容识别。这些可以在本地运行的实现特定功能的模型还是比较有意思的,模型本身不大,本地可运行,适合初学者或者有兴趣的人使用。
我始终倡导大家积极接触和学习新技术。这并不意味着我们必须深入钻研,毕竟人的精力有限,无法将所有精力都投入到这些领域。但至少,我们应该保持好奇心,对这些新技术有所了解,理解其基本原理。这样,当有一天我们需要应用这些技术时,就能更加得心应手。即使我们不能成为某个领域的专家,但对这些技术的了解也会成为我们思考的一部分,让我们在解决问题时拥有更多的选择和思路。因此,不要害怕尝试新事物,保持好奇心和学习态度,这将是我们不断进步的关键。
前言
如果大家有接触过ADFS或者认证协议,肯定会对五花八门的名词看的眼花缭乱,比如WS-FED、SAML、SAML Token、OAuth、OpenID Connect、Kerbros以及NTLM等, 但实际上我们可以高屋建瓴的学习它们。
拆分
作为程序员或者工程师,我们都擅长将问题拆分和类比,在认证协议上我们同样可以如此分为三方面,登录协议,验证过程以及令牌类型。举个例子,当我们坐飞机时,首先去柜台办理登记手续,出示自己的身份证,然后工作人员会返回给你机票,然后可以拿着身份证去登机口登机。那么这三个方面都是什么?
登录协议,整个登机的过程就是一个登录协议,规定了整套登机的流程。而WS-FED、SAML、OAuth、OpenID Connect属于登录协议。
验证过程,在柜台出示身份证,然后交换机票的这个过程被称为验证过程。普通的password认证,以及Kerbros和NTLM属于验证过程。
令牌,在整个流程中,机票就是属于令牌。 而在认证协议中令牌也有很多种,最常见的是OAuth中的JWT,Json Web Token。需要注意SAML Token此时属于令牌而不是协议。
WS-FED
WS-FED是ADFS Server的登录协议,从三方面来解释这个协议
登录协议,属于WS-FED
验证过程,支持多种验证过程,包括Forms-based即password, Kerbros 和NTLM。
令牌,注意用WS-Fed时,令牌一般是SAML 1.1 Token
那么WS-FED的URL一般是这样的,
https://sts.example.com/adfs/ls/
? wa=wsignin1.0 & wtrealm=... & wctx=...
参数分别代表的内容如下:
wa=wsignin1.0,通知ADFS server登录,这是一个登录操作
Wtrealm, 通知ADFS server, 我要获取什么, 比如跟ADFS Server配置的relying party相对应
wctx, 认证之后返回去的session data
拓展知识:
Kerbros和NTLM都是windows认证,其中NTLM时属于比较旧的协议,相对于Kerbros是不安全的协议,所以一般作为Kerbros的Fallback
SAML
SAML也是ADFS Server的登录协议,还是从三方面来解释这个协议
登录协议,属于SAML
验证过程,支持多种验证过程,包括Forms-based即password, Kerbros 和NTLM。
令牌,注意用WS-Fed时,令牌一般是SAML 2.0 Token
那么SAML的URL一般是这样的,
https://sts.example.com/adfs/ls/
? SAMLRequest=... & SigAlg=... & RelayState=... & Signature=...
参数分别代表的内容如下:
SAMLRequest, Base64格式,请求内容
SigAlg, 签名算法
Signature, 对request的签名
RelayState, 认证之后返回去的session data
OAuth和OpenID Connect
OAuth 是一种开放标准的授权协议,允许用户让第三方应用访问他们存储在另外服务提供商上的信息而无需将用户名和密码提供给第三方应用。OAuth 作为授权框架,允许第三方应用限定的访问权限,这样用户就不需要与第三方应用共享登录凭据。
举例来说,一个网站(称为客户端)可能允许用户通过Github账户登录,用户可以点击一键登录的按钮,然后被重定向到Github的登录页。在那里,用户输入自己的登录凭据,然后Github会提示用户是否授权那个网站访问其某些信息。一旦用户同意,Github会将用户重定向回原来的网站,同时提供一个授权码。最后,该网站可以使用这个授权码向 Github 请求访问令牌,并使用这个令牌来获取用户信息。
OpenID Connect 是建立在 OAuth 2.0 协议之上的一个身份层,提供了一种身份验证的方式。它允许客户端应用程序依靠授权服务器来验证最终用户的身份,并获取基本的个人资料信息。OpenID Connect 是一个简单的身份验证协议,并且可以和OAuth 2.0无缝集成。
与OAuth 2.0 主要是为了授权访问资源不同,OpenID Connect 提供了更丰富的身份认证信息,通常包含用户的ID令牌和用户信息(比如用户的姓名、邮箱地址等)。
还是从三方面来解释这个协议
登录协议, OAuth, OIDC
验证过程,支持多种验证过程,包括Forms-based即password, MFA等
令牌,JWT即Json Web Token
OAuth 2.0支持很多flow,比如Code flow, Client Credentials Grant flow, Device Code flow等, Code flow的URL一般是
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize?
client_id=535fb089-9ff3-47b6-9bfb-4f1264799865
&response_type=code
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F
&response_mode=query
&scope=https%3A%2F%2Fgraph.microsoft.com%2Fmail.read
&state=12345
而OpenID Connect的URL一般是
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize?
client_id=535fb089-9ff3-47b6-9bfb-4f1264799865
&response_type=id_token
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F
&response_mode=form_post
&scope=openid
&state=12345
&nonce=678910
JVM—对象的创建流程与内存分配
创建流程
对象创建的流程图如下:
对象的内存分配方式
内存分配的方式有两种:
- 指针碰撞(Bump the Pointer)
- 空闲列表(Free List)
分配方式
|
说明
|
收集器
|
指针碰撞(Bump the Pointer) |
内存地址是连续的(新生代) |
Serial和ParNew收集器 |
空闲列表(Free List) |
内存地址不连续(老年代) |
CMS收集器和Mark-Sweep收集器 |
指针碰撞
指针碰撞示意图如下:
内存分配安全问题:
虚拟机给A线程分配内存的过程中,指针未修改,此时B线程同时使用了该内存,就会出现问题。
解决方式:
- CAS乐观锁:JVM虚拟机采用CAS失败重试的方式保证更新操作的原子性;
- TLAB (Thread Local Allocation Buffer)本地线程分配缓存,预分配。
分配主流程
首先从TLAB里面分配,如果分配不到,再使用CAS从堆里面划分。
对象如何进入老年代
对象进入老年代流程如下:
新对象大多数默认都进入Eden;
对象进入老年代的四种情况:
年龄太大
MinorGC15次
【
-XX:MaxTenuringThreshold
】;
动态年龄判断:MinorGC后会动态判断年龄,将符合要求对象移入老年代;
MinorGC之后,发现Survivor区中的一批对象的总大小大于了这块Survivor区的50%,那么就会将此时大于等于这批对象年龄最大值的所有对象,直接进入老年代。
例子: Survivor区中有一批对象,年龄分别为年龄1+年龄2+年龄n的多个对象,对象总和大小超过了Survivor区域的50%,此时就会把年龄n及以上的对象都放入老年代。希望那些可能是长期存活的对象,尽早进入老年代。
比率可以由-XX:TargetsurvivorRatio指定
大对象直接进入老年代1M【
-XX:PretenureSizeThreshold
】;(前提是Serial和ParNew收集器)
为了避免大对象分配内存时的复制操作降低效率。
避免了Eden和Survivor区的复制。
MinorGC后存活对象太多无法放入Survivor。
空间担保机制
空间担保机制:当新生代无法分配内存的时候,我们想把新生代的老对象转移到老年代,然后把新对象放入腾空的新生代。此种机制我们称之为内存担保。
空间担保流程图如下:
对象内存布局
对象内存布局示意图如下:
对象里的三个区
堆内存中,一个对象在内存中存储的布局可以分为三块区域:
堆内存中,一个对象在内存中存储的布局可以分为三块区域:
对象头(Header) : Java对象头占8byte。如果是数组则占12byte。因为JVM里数组size需要使用4byte存储。
实例数据(Instance Data):生成对象的时候,对象的非静态成员变量也会存入堆空间
对齐填充(Padding) :JVM内对象都采用8byte对齐,不够8byte的会自动补齐。
如何访问一个对象
有两种方式:
句柄:稳定,对象被移动只要修改句柄中的地址
直接指针:访问速度快,节省了一次指针定位的开销
- « 前一页
- 1
- ...
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- ...
- 83
- 后一页 »