#経緯
ちょっとLINE WORKSと連携して作ろうかと思って、無料で使える範囲で接続して何ができるのか調べてました。結果、私のしたいことができそうにないのであきらめたのですが、認証を通すために苦労したので、いつか使うかもしれない時のメモ書きとして残しときます。
ほかでも「RSA SHA-256」で暗号化したJWTを送信するときに参考になるかと思います。
#今回の内容
まず、このページを見るということは、LINE WORKSは何かわかっていると思いますが、どうやらAPIは大きく以下の2つのパートに分かれているようです。
- サーバーAPI:個々のメンバーのデータを処理しない。
- サービスAPI:個々のメンバーのログインを必要とする。
このうち、無料で使えるのはサーバーAPIの中のトークBot関連の処理のみです。この処理は、最初にサーバーに認証を得てトークンを取得する必要があるのですが、この時の処理の実装にちょっと苦労したので、それを記載しています。実際のBotは作ってませんし、作りませんのであしからず。
#参照
LINE WORKS APIについては「LINE WORKS Developers」にいろいろ書かれています。
#実装
準備
先に「LINE WORKS」の「Developer Console」で「API ID」と「Server List(ID登録タイプ)」を発行してください。「Server List(固定IPタイプ)」の場合はおそらく認証ももう少し楽なのですが、開発時の事などを考えると固定IPは難しいので、たいていは「Server List(ID登録タイプ)」を使うかと思います。
※ 認証までなら「Server API Consumer Key」は不要です。以降の処理をするには必要です。
##NuGetで追加するパッケージ
「System.IdentityModel.Tokens.Jwt」を追加してください。
##パラメータの設定
「appsettings.json」に以下の内容を追加します。
"LINE": {
"ApiId": "「Developer Console」の「API ID」",
"ServerID": "「Developer Console」の「Server List(固定IPタイプ)」の「ID」",
"PrivateKey": "「Developer Console」の「Server List(固定IPタイプ)」の認証キーでヘッダ「-----BEGIN RSA PUBLIC KEY-----」とフッタ「-----END RSA PUBLIC KEY-----」を除いて改行を除いて1行にしたもの"
}
パラメータクラス
LINEのパラメータクラスを以下のように追加
namespace CareScheduler.Areas.Identity.LINEWORKS
{
public class LINEParams
{
public string ApiId { get; set; }
public string ServerID { get; set; }
public string PrivateKey { get; set; }
}
}
「Startup.cs]の「ConfigureServices()」に以下の1行を追加
public void ConfigureServices(IServiceCollection services)
{
...
// LINE パラメータのDI設定
services.Configure<LINEParams>Configuration.GetSection("LINE"));
...
}
##認証エラーのための例外作成
認証時のエラーの為に以下の例外クラスを作っておきました。このあたりはお好きなように。
namespace CareScheduler.Areas.Identity.LINEWORKS
{
public class LineWorksAuthenticationException : Exception
{
public string ErrorCode { get; set; }
public string Detail { get; set; }
public LineWorksAuthenticationException(LineWorksAuthenticationError jsonError):base(jsonError.Message)
{
ErrorCode = jsonError.Code;
Detail = jsonError.Detail;
}
}
}
##処理するクラスのサービスインターフェース
サービスにするためにインターフェースを作成します。
namespace CareScheduler.Areas.Identity.LINEWORKS
{
/// <summary>
/// LINE WORKS サーバーAPIを利用するクラスのインターフェース(DI用)
/// </summary>
public interface ILINEWORKSServer
{
string GetAccessToken();
}
}
##接続用のクラス
実行クラスは以下の通りです。
このクラスを実施するところでDIで取り込んで、GetAccessToken()を実行すると、アクセストークンが取得できます。
ネットでいろいろ探して、ようやく「System.Security.Cryptography.RSA」を利用してシンプルに送信用トークンの署名に利用する「signingCredentials」が作れました。認証キーを取り込む際に、認証キーのヘッダとフッタを外すことと、取込みメソッドが「ImportPkcs8PrivateKey()」であるのにくづくまで少しかかりました。
これ以外でも外部でRSAでトークン作成のための認証キーを利用することがあるかなーと思います。
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using System;
using System.Collections.Specialized;
using System.Net;
using System.Security.Cryptography;
using System.Text;
namespace CareScheduler.Areas.Identity.LINEWORKS
{
/// <summary>
/// LINE WORKS サーバーAPIを利用するクラスのインターフェース(DI用)
/// </summary>
public interface ILINEWORKSServer
{
string GetAccessToken();
}
/// <summary>
/// LINE WORKS サーバーAPIを利用するクラス
/// </summary>
public class LINEWORKSServer : ILINEWORKSServer
{
// 認証処理のグラント
const string GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer";
// 認証処理APIのURL
string _authenticationUrl = "https://auth.worksmobile.com/b/{API ID}/server/token";
/// <summary>
/// LINE WORKS サーバーAPI用のパラメータ
/// </summary>
LINEParams _lineParams;
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="lineParams"></param>
public LINEWORKSServer(IOptions<LINEParams> lineParams)
{
_lineParams = lineParams.Value;
_authenticationUrl = _authenticationUrl.Replace("{API ID}", _lineParams.ApiId);
}
/// <summary>
/// LINE WORKS サーバーAPのアクセストークンを取得する
/// </summary>
/// <returns></returns>
public string GetAccessToken()
{
// RSA クラスを利用して認証キーから署名情報を作成する
using RSA rsa = RSA.Create();
rsa.ImportPkcs8PrivateKey(Convert.FromBase64String(_lineParams.PrivateKey), out _);
var signingCredentials = new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256);
// トークン文字列の作成の生成
var descriptor = new SecurityTokenDescriptor
{
Issuer = _lineParams.ServerID,
SigningCredentials = signingCredentials,
IssuedAt = DateTime.UtcNow,
Expires = (DateTime.UtcNow).AddMinutes(10),
};
var handler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler();
var token = handler.CreateJwtSecurityToken(descriptor);
var tokenString = handler.WriteToken(token);
// LINE WORKSのサーバーAPIの認証APIにPOST
var client = new WebClient() { Encoding = Encoding.UTF8 };
var content = new NameValueCollection();
content["assertion"] = tokenString;
content["grant_type"] = GRANT_TYPE;
string response = Encoding.UTF8.GetString(client.UploadValues(_authenticationUrl, "POST", content));
// 認証結果を取得
var result = JsonConvert.DeserializeObject<LineWorksAuthenticationResultToken>(response);
if (string.IsNullOrEmpty(result.AccessToken))
{
throw new LineWorksAuthenticationException(JsonConvert.DeserializeObject<LineWorksAuthenticationError>(response));
}
return result.AccessToken;
}
/// <summary>
/// LINE WORKS サーバーAPIのアクセストークン取得結果のJSONデータ取得用クラス
/// </summary>
class LineWorksAuthenticationResultToken
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
}
}
public class LineWorksAuthenticationError
{
[JsonProperty("code")]
public string Code { get; set; }
[JsonProperty("message")]
public string Message { get; set; }
[JsonProperty("detail")]
public string Detail { get; set; }
}
}
##利用方法
使うクラスのコンストラクタ引数に「ILINEWORKSServer lineWork」を追加して「LINEWORKSServer」の「GetAccessToken()」を実行します。
以下は「Index.cshtml」のRazorPageのモデルのプロパティ「APIToken」に取り込んでみるサンプルです。botを操作するには、ここで得たトークンをAPI呼び出しでHTMLヘッダにBearerトークンとして設定する必要があります。
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
public string APIToken { get; set; }
ILINEWORKSServer _lineWorks;
public IndexModel(ILogger<IndexModel> logger, ILINEWORKSServer lineWorks)
{
_logger = logger;
_lineWorks = lineWorks;
}
public void OnGet()
{
APIToken = _lineWorks.GetAccessToken();
}
}