2026年5月4日 星期一

沒事把node.js簽發的jwt拿來呼叫.net core web API時踩了一堆坑的故事

 照理說dot net 的 web api與nextjs整合應該是前端用Browser -->next.js --> .net web api (讓dot net的microservice放在另一個網段)
不過如果直接讓node.js簽發給browser client的jwt拿來call .net web api會發生什麼事 (為什麼要這樣做?不為什麼,就只是想知道罷了)

這一路走來我遇到了狀況,因為很有趣,所以把它做個記錄.

狀況一.401錯誤--起源是錯用了AOT,而不是傳統的Web API專案
原因: VS 2026 建專案時選到了 "Web API (Native AOT)" 模板,使用 CreateSlimBuilder + PublishAot。AOT 的 trimmer 會把 JWT 需要的 crypto provider 砍掉。



所以最初在設計Web API時,一定要選對專案型態 ,不是AOT,而是最上面那種傳統Web API型態的專案
AOT與一般web API專案的差別:

                    Regular              AOT
─────────────────────────────────────────────────
Builder           CreateBuilder        CreateSlimBuilder
編譯方式           IL → JIT             直接編譯成機器碼
啟動速度           ~500ms+              ~50ms
發布大小           需要 Runtime          單一檔案 (~10MB)
Reflection         ✅ 完整支援           ❌ 受限
JSON 序列化        System.Text.Json      需要 Source Generator
JWT/Crypto         ✅ 全部可用           ⚠️ 可能被 Trim 掉
NuGet 相容性       幾乎全部              部分不支援
適用場景           一般 API、企業應用     微服務、Serverless

-----------------------------------------------------------------------
(小的沒在用AOT,如有先進有補充建議的亦請不吝賜教)

重新增加一個web api project,修改program.cs
-----------------------------

...
...
var
key = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(Configuration["JWT_SECRET"]) );
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = key, ValidateIssuer = false, ValidateAudience = false, }; });
....
...
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
-----------------------------
Action in Sample controller
-------------------------
[Authorize]
[HttpGet]
public IActionResult Get() {
    var username = User.FindFirst("username")?.Value;
    var role = User.FindFirst("role")?.Value;
    return Ok();
}
----------------------------
之後....
狀況二.錯誤訊息 IDX10517: Signature validation failed. The token's kid is missing.
------------------------------------------
原因: .NET 10 的 Microsoft.IdentityModel 套件收緊了驗證規則,要求 token header 中必須有 kid(Key ID)。而 Node.js 的 jose 預設不產生 kid
解法:
這要在nextjs 簽發JWT及解析端(.net core web API)都要再加一個kid
------JWT.ts-----------
export async function signJWT(user:AuthUser):Promise<string> {
    return new SignJWT(
       {
        id:user.id,
        username:user.username,
        role:user.role
       }
    ).setProtectedHeader({alg:'HS256') <--Before
    ).setProtectedHeader({alg:'HS256', kid: 'MyKidKey'}) <--after.
    .setIssuedAt()
    .setExpirationTime('8h')
    .sign(SECRET); <---SECRET是從.node 設定檔來的,跟program.cs的 Configuration["JWT_SECRET"]相呼應
}
------Program.cs------
var key = new SymmetricSecurityKey(
    Encoding.UTF8.GetBytes(Configuration["JWT_SECRET"])
);
key.KeyId = "MyKidKey";  <----KID set here.
--------------------------
這下子,總該可以了吧?
再次用postman設定好bearer token後,試著request controller 時
出現新的錯誤錯誤--
狀況三:「The signature is invalid」錯誤
這東西真的是超難解的,最後是把兩邊的code都送給AI去看的結果才知是「長度」問題:

原因: HS256 要求 key 至少 256 bits(32 bytes)。原本的 secret 只有 27 字元,jose 和 .NET 對不足長度的 key 可能有不同的 padding 行為。

解法: Secret 改成 32 字元以上,兩邊同步更新。

最後,終於在controller的GET action中,可以讀取到User資訊了
但是「User.FindFirst("role")?.Value」回傳null,這樣還是不完成,
改成「User.FindFirst(ClaimTypes.Role)?.Value」,就會得到role值了

別忘了,在[Authorize...]指定需要的角色權限.ex  [Authorize(Roles = "manager,admin")]

有些知識真的是靠踩坑得來的,不是AI直接給你就會了解的.





2025年1月16日 星期四

天啊,Serilog只輸出到console( for .net 6),卻沒存到file ?如何是好?

 不要在appsettings.json的Serilog區段改path了,那個沒用,
直接在Program.cs裡改才有用啦

===============
       string exePath = AppDomain.CurrentDomain.BaseDirectory;
        string logFolderPath = Path.Combine(exePath, "logs");
        string logFilePath = Path.Combine(logFolderPath, "log-.txt");
        // Ensure the logs folder exists
        if (!Directory.Exists(logFolderPath))
        {
            Directory.CreateDirectory(logFolderPath);
        }
        // Configure Serilog
        Log.Logger = new LoggerConfiguration()
            .WriteTo.Console()
            .WriteTo.File(
                path: logFilePath,
                rollingInterval: RollingInterval.Day,
                outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"
            )
            .CreateLogger();
================

試了很久才試出來.....不知道之後的版本有沒有改掉這個bug

2024年12月13日 星期五

for infomix,string to date,and get diff days count

 假設create_date是以yyyyMMddHHmmss的方式存放...  
呃...這樣說不好懂,假設是20241113134118 這樣的方式存放的文字
如何查出與系統目前時間差多少天?
 「我知道,問chatGPT....」
最好是問了會有用,就是因為沒用我才在這做筆記
首先,你要先把日期字串轉成日期物件
這是就要用to_date函數:
注意格式字串
 to_ Date(hn_create_date, "%Y%m%d%H%M%S")
很巧,只c#剛好相反,別想錯了

「那"差幾天"要怎麼取值?」
「人客你住巷仔內的--內行哦,這就是重點了」

如果直接用current-to_date(....)
你會得到一個「12 11:22:33.000」這樣的東西
是的就是幾天 然後幾旳幾分幾秒的東西,那東西叫interval 
偏偏這東西就是麻煩,要先轉成字串再轉其他方式使用
直接給你答案了

cast(
trim(
    (
     current-
     to_ Date(create_date, "%Y%m%d%H%M%S")
    ):: INTERVAL day (9) to days varchar (10)
   )
) as integer  .... 

(然後看你要加上什麼天數限制條件了)

參考
to_date
還有這個
interval to char

OS:不知不覺從砍人式系統來到金融圈已經一年半了.應該是回不去了


2024年10月3日 星期四

pi zero2w to setup QT5.14

ref:
https://www.waveshare.net/study/article-1042-1.html

2024年我的pi zero 2W裝的是bulleye,但QT官方說QT4只有buster,沒有bulleye

問題,與排除:

1「sudo apt-get build-dep libqt5gui5」會遇到「you need to put some deb-src URIs in your sources.list」錯誤,要你加入apt source

此時,請到「/etc/apt/sources.list」把
「#deb-src http://raspbian.raspberrypi.org/raspbian/ bullseye main contrib non-free rpi」
的「#」拿掉,再用apt-update,才會找到套件

之後,請務必照以下的步驟找到/置放設定檔
-------------------------

下载树莓派的编译配置文件

~ $ git clone https://github.com/oniongarlic/qt-raspberrypi-configuration.git

放到Qt的源代码中。具体为:

common/raspberrypi.conf 放到 /qtbase/mkspecs/common/

linux-rpi2-g++ , linux-rpi3-g++ , linux-rpi-g++ , linux-rpi-vc4-g++ , linux-rpi4-v3d-g++ 这4个文件夹放到 /qtbase/mkspecs/中

------------------上述這過程一定要做,別偷懶!!-------

2.configure的指令要改一下,因為不是用pi4,是用pi zero,所以要小改

(其中build $是路徑,不是指令)
----------------------

build $ PKG_CONFIG_LIBDIR=/usr/lib/arm-linux-gnueabihf/pkgconfig:/usr/share/pkgconfig \

../qt-everywhere-src-5.14.2/configure -platform linux-rpi-g++ \

-D__ARM_ARCH_5TEJ__ \

-v \

-opengl es2 -eglfs \

-no-gtk \

-opensource -confirm-license -release \

-reduce-exports \

-force-pkg-config \

-nomake examples -no-compile-examples \

-skip qtwayland \

-skip qtwebengine \

-no-feature-geoservices_mapboxgl \

-qt-pcre \

-no-pch \

-ssl \

-evdev \

-system-freetype \

-fontconfig \

-glib \

-prefix /opt/Qt5.14 \

-qpa eglfs \

-qt-xcb

----------------------- 

3.make中,遇到錯誤
  cannot find -lGLESv2 
   cannot find -lbrcmGLESv2
解法:請到/home/john/qt-everywhere-src-5.14.2/qtbase/mkspecs/commonqt-everywhere-src-5.14.2/qtbase/mkspecs/common修改「raspberrypi.conf」
-----

QMAKE_LIBS_EGL          = -lbrcmEGL -lbrcmGLESv2

QMAKE_LIBS_OPENVG       = -lbrcmEGL -lbrcmOpenVG -lbrcmGLESv2

QMAKE_LIBS_OPENGL_ES2   = -lbrcmGLESv2 -lbrcmEGL

QMAKE_LIBS_BCM_HOST     = -lbcm_host


EGLFS_DEVICE_INTEGRATION = eglfs_brcm

-----
save後,再去 make

4.再make,再遇到錯誤
----------------------------

qeglfsbrcmintegration.cpp:74:5: error: ‘EGL_DISPMANX_WINDOW_T’ was not declared in this scope

   74 |     EGL_DISPMANX_WINDOW_T *eglWindow = new EGL_DISPMANX_WINDOW_T;

----------------------------
這要改一點地方,先用bulleye xwin之下的檔案總管找「eglplatform.h」
每一個eglplatform.h都加上這個
-----

typedef uint32_t DISPMANX_ELEMENT_HANDLE_T;
typedef struct {
  DISPMANX_ELEMENT_HANDLE_T element;
  int width;   /* This is necessary because dispmanx elements are not queriable. */
  int height;
} EGL_DISPMANX_WINDOW_T;

-----
然後,把這個檔案qeglfsbrcmintegration.cpp  蓋到 /home/john/qt-everywhere-src-5.14.2/qtbase/src/plugins/platforms/eglfs/deviceintegration/eglfs_brcm 裡去
(當然,這還是會有error,所以...也要把「qeglfsbrcmintegration.h」加上上面那些typedef...宣告,才能繼續給他make下去)

------------

/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qeglfsbrcmintegration.h"
#include <bcm_host.h>


QT_BEGIN_NAMESPACE

static DISPMANX_DISPLAY_HANDLE_T dispman_display = 0;

static EGLNativeWindowType createDispmanxLayer(const QPoint &pos, const QSize &size, int z, DISPMANX_FLAGS_ALPHA_T flags)
{
    VC_RECT_T dst_rect;
    dst_rect.x = pos.x();
    dst_rect.y = pos.y();
    dst_rect.width = size.width();
    dst_rect.height = size.height();

    VC_RECT_T src_rect;
    src_rect.x = 0;
    src_rect.y = 0;
    src_rect.width = size.width() << 16;
    src_rect.height = size.height() << 16;

    DISPMANX_UPDATE_HANDLE_T dispman_update = vc_dispmanx_update_start(0);

    VC_DISPMANX_ALPHA_T alpha;
    alpha.flags = flags;
    alpha.opacity = 0xFF;
    alpha.mask = 0;

    DISPMANX_ELEMENT_HANDLE_T dispman_element = vc_dispmanx_element_add(
            dispman_update, dispman_display, z, &dst_rect, 0, &src_rect,
            DISPMANX_PROTECTION_NONE, &alpha, (DISPMANX_CLAMP_T *)NULL, (DISPMANX_TRANSFORM_T)0);

    vc_dispmanx_update_submit_sync(dispman_update);

    EGL_DISPMANX_WINDOW_T *eglWindow = new EGL_DISPMANX_WINDOW_T;
    eglWindow->element = dispman_element;
    eglWindow->width = size.width();
    eglWindow->height = size.height();

    return (EGLNativeWindowType)eglWindow;
}

static void destroyDispmanxLayer(EGLNativeWindowType window)
{
    EGL_DISPMANX_WINDOW_T *eglWindow = (EGL_DISPMANX_WINDOW_T*)(window);
    DISPMANX_UPDATE_HANDLE_T dispman_update = vc_dispmanx_update_start(0);
    vc_dispmanx_element_remove(dispman_update, eglWindow->element);
    vc_dispmanx_update_submit_sync(dispman_update);
    delete eglWindow;
}

void QEglFSBrcmIntegration::platformInit()
{
    bcm_host_init();
}

static int getDisplayId()
{
    // As defined in vc_dispmanx_types.h
    // DISPMANX_ID_MAIN_LCD     0
    // DISPMANX_ID_AUX_LCD      1
    // DISPMANX_ID_HDMI         2
    // DISPMANX_ID_SDTV         3
    // DISPMANX_ID_FORCE_LCD    4
    // DISPMANX_ID_FORCE_TV     5
    // DISPMANX_ID_FORCE_OTHER  6 /* non-default display */
    static const int dispmanxId = qEnvironmentVariableIntValue("QT_QPA_EGLFS_DISPMANX_ID");
    return (dispmanxId >= 0 && dispmanxId <= 6) ? dispmanxId : 0;
}

EGLNativeDisplayType QEglFSBrcmIntegration::platformDisplay() const
{
    dispman_display = vc_dispmanx_display_open(getDisplayId());
    return EGL_DEFAULT_DISPLAY;
}

void QEglFSBrcmIntegration::platformDestroy()
{
    vc_dispmanx_display_close(dispman_display);
}

QSize QEglFSBrcmIntegration::screenSize() const
{
    uint32_t width, height;
    graphics_get_display_size(getDisplayId(), &width, &height);
    return QSize(width, height);
}

EGLNativeWindowType QEglFSBrcmIntegration::createNativeWindow(QPlatformWindow *window, const QSize &size, const QSurfaceFormat &format)
{
    Q_UNUSED(window)
    return createDispmanxLayer(QPoint(0, 0), size, 1, format.hasAlpha() ? DISPMANX_FLAGS_ALPHA_FROM_SOURCE : DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS);
}

void QEglFSBrcmIntegration::destroyNativeWindow(EGLNativeWindowType window)
{
    destroyDispmanxLayer(window);
}

bool QEglFSBrcmIntegration::hasCapability(QPlatformIntegration::Capability cap) const
{
    switch (cap) {
        case QPlatformIntegration::ThreadedPixmaps:
        case QPlatformIntegration::OpenGL:
        case QPlatformIntegration::ThreadedOpenGL:
        case QPlatformIntegration::BufferQueueingOpenGL:
            return true;
        default:
            return false;
    }
}

QT_END_NAMESPACE
-------------------------------------
之後就沒遇到什麼錯誤(然後花了三天才make 完)
先裝起來,日後試出在他台ubuntu做cross link & 在EGFL模式下跑都正常的話,再來向大家報告

------2024/10/31:颱風天,讀書天--繼續玩我們的QT大法-------
在ubuntu 22.04 做Cross Link的設定,可參考這篇,我測過可用
https://blog.csdn.net/weixin_44658484/article/details/118655494

(在這次的建構中,我是把zero 2W設定為跟pi3一樣等級的板子,
所以configure時, -device是以「-device linux-rasp-pi3-g++」設定的)
惟一要注意的是在configure階段時會遇到類似以下的訊息:
/qfloat16.h:300:7: error: 'numeric_limits' is not a class template
300 | class numeric limits<QT PREPEND_NAMESPACE(qfloat16)> : public numeric limits<float>
這時要在qglobal.h加入<limits>,不是隨便加,要看#if條件加的
----------------
#ifdef __cplusplus
#  include <type_traits>
#  include <cstddef>
#  include <utility>
#  include <limits>
#endif
----------------
,save後,再去configure,就正確了
之後,你會在make階段會遇到「 : error: ‘EGL_DISPMANX_WINDOW_T’ was not declared in this scope 」錯誤
沒錯,就是照之前寫的那樣去改.h & .c 檔,就可以完成make & make install了

(颱風後要去修屋簷了...)



2024年6月25日 星期二

在BBB中安裝TOTOLINK A650UA驅動程式

sudo apt-get install dkms

sudo apt install -y linux-headers-$(uname -r) build-essential bc dkms git libelf-dev rfkill iwsudo apt install -y linux-headers-$(uname -r) build-essential bc dkms git libelf-dev rfkill iw


git clone https://github.com/brektrou/rtl8821CU
cd rtl8821CU
make 
sudo make install
參考
還有這個
(裝完driver記得重開機才會載入driver,用ifconfig才會看到「wlan0」)

2024年4月24日 星期三

在ubuntu啟動.net core webapp https時,遇到「No server certificate was specified, and the default developer certificate could not be found or is out of date.」如何解決

 這個錯誤的全文是這樣的:

-----------------

sudo dotnet 你的DotNetMvcApp.dll
crit: Microsoft.AspNetCore.Server.Kestrel[0]
      Unable to start Kestrel.
System.InvalidOperationException: Unable to configure HTTPS endpoint. No server certificate was specified, and the default developer certificate could not be found or is out of date.
To generate a developer certificate run 'dotnet dev-certs https'. To trust the certificate (Windows and macOS only) run 'dotnet dev-certs https --trust'.
For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054.
   at Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader.Load()
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer.ValidateOptions()
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken)
Unhandled exception. System.InvalidOperationException: Unable to configure HTTPS endpoint. No server certificate was specified, and the default developer certificate could not be found or is out of date.
To generate a developer certificate run 'dotnet dev-certs https'. To trust the certificate (Windows and macOS only) run 'dotnet dev-certs https --trust'.
For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054.
   at Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader.Load()
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer.ValidateOptions()
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken)
   at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host)
   at 你的DotNetMvcApp.Program.Main(String[] args) in P:\你的DotNetMvcApp\你的DotNetMvcApp\Program.cs:line 49
Aborted
-----------------
這時,你可以用這些指令去重新產生並查詢憑證
----
dotnet dev-certs https --clean
dotnet dev-certs https
sudo dotnet dev-certs https --check --verbose
---
參考這個連結
大家一起加油吧

2023年10月28日 星期六

informix之.net core EF 套用.

 直接參考這文章做就差不多了
很多人會卡在Scaffold-DbContext這一關
重點是要注意一下PORT ,因為informix EF Core用的是DRDA的port,所以不是之前我們用odbc的(預設)9088而是9089.
如同上述內文說的:Please note: The IBM Data Server providers only works with the Distributed Relational Database Architecture™ (DRDA) protocols, and you therefore need to ensure that your Informix server is configured accordingly.


(這點你要跟你們家的DBA問一下給不給連,也要問網管開不開port,說真的,我也沒把握一次要他們從DEV、UAT到Production這樣一次開這麼多東西給你,所以本單元真的就是自己練功用的)


上述超連結提到的package有點舊了,我實驗的package有update,也都正常
-----------project target framework是.net 6.0------------------



-----------------------------
這是我localhost的scaffold-Dbcontext script,留做我日後小抄用
------------------------------------------------------------- 
Scaffold-DbContext -Connection "user id=informix;server=localhost:9089;database=db_with_log;Password=xxxOOO" -Provider IBM.EntityFrameworkCore -OutputDir Models  -Force -UseDatabaseNames -Tables test_user,test_data

--------------------------------------------
彩蛋一下:
這份PDF不錯,如果你是.net+informix的重度使用者,可以看一下

OS:明明用到的機會渺茫.我還是堅持到底地把它試出來玩了幾番,這也是種職業病吧