@@ -82,8 +82,10 @@ protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest original)
8282 String acrMethod = original .headers ().get (HttpHeaderNames .ACCESS_CONTROL_REQUEST_METHOD );
8383 String acrHeaders = original .headers ().get (HttpHeaderNames .ACCESS_CONTROL_REQUEST_HEADERS );
8484
85- // Allow methods: prefer configured list, otherwise echo requested or use sensible defaults
86- String allowMethods = configuration .getOrDefault ("cors.allowed.methods" , acrMethod != null ? acrMethod : "GET,POST,PUT,DELETE,OPTIONS" );
85+ // Allow methods: prefer configured list, otherwise echo requested or use
86+ // sensible defaults
87+ String allowMethods = configuration .getOrDefault ("cors.allowed.methods" ,
88+ acrMethod != null ? acrMethod : "GET,POST,PUT,DELETE,OPTIONS,PATCH" );
8789 response .headers ().set (HttpHeaderNames .ACCESS_CONTROL_ALLOW_METHODS , allowMethods );
8890
8991 // Allow headers: prefer configured list, otherwise echo requested or common headers
@@ -109,9 +111,16 @@ protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest original)
109111 this .service (ctx , request , context , keepAlive );
110112 }
111113
112- private void service (final ChannelHandlerContext ctx , final Request <FullHttpRequest , Object > request , final Context context , boolean keepAlive ) {
114+ private void service (final ChannelHandlerContext ctx , final Request <FullHttpRequest , Object > request ,
115+ final Context context , boolean keepAlive ) {
116+ // Compute CORS headers FIRST — they must be present on every response,
117+ // including error responses returned before any further processing.
118+ Object origin = request .headers ().get (Header .ORIGIN );
119+ String allowOrigin = configuration .getOrDefault ("cors.allowed.origins" ,
120+ origin != null ? origin .toString () : "*" );
121+
113122 if (!authenticateRequest (request , context )) {
114- sendErrorResponse (ctx , HttpResponseStatus .UNAUTHORIZED , "Invalid or expired token." );
123+ sendErrorResponse (ctx , HttpResponseStatus .UNAUTHORIZED , "Invalid or expired token." , allowOrigin );
115124 return ;
116125 }
117126
@@ -126,8 +135,6 @@ private void service(final ChannelHandlerContext ctx, final Request<FullHttpRequ
126135 ResponseBuilder response = new ResponseBuilder (new DefaultFullHttpResponse (HTTP_1_1 , status ), ctx );
127136
128137 // Set CORS headers on the actual response
129- Object origin = request .headers ().get (Header .ORIGIN );
130- String allowOrigin = configuration .getOrDefault ("cors.allowed.origins" , origin != null ? origin .toString () : "*" );
131138 response .addHeader (HttpHeaderNames .ACCESS_CONTROL_ALLOW_ORIGIN .toString (), allowOrigin );
132139 if (origin != null ) {
133140 response .addHeader (HttpHeaderNames .VARY .toString (), "Origin" );
@@ -266,15 +273,36 @@ private boolean authenticateRequest(Request<FullHttpRequest, Object> request, Co
266273 String authHeader = authorization .toString ();
267274 if (authHeader != null && authHeader .startsWith ("Bearer " )) {
268275 String token = authHeader .substring (7 );
276+
277+ String secret = configuration .get ("jwt.secret" );
278+ if (secret == null || secret .trim ().isEmpty ()) {
279+ // jwt.secret is not configured — cannot validate Bearer token.
280+ // Log a warning and reject the request to avoid using a weak/empty key.
281+ System .err .println ("[WARNING] jwt.secret is not configured. " +
282+ "Bearer token authentication is disabled. " +
283+ "Please set jwt.secret (>= 256-bit) in application.properties." );
284+ return false ;
285+ }
286+
269287 JWTManager jwtManager = new JWTManager ();
270- jwtManager .withSecret (configuration .get ("jwt.secret" ));
288+ jwtManager .withBase64Secret (secret );
289+
290+ String timezone = configuration .get ("jwt.timezone" );
291+ if (timezone != null && !timezone .trim ().isEmpty ()) {
292+ try {
293+ jwtManager .withTimezone (timezone );
294+ } catch (NumberFormatException e ) {
295+ System .err .println ("[WARNING] Invalid jwt.timezone value: " + timezone );
296+ }
297+ }
271298
272299 try {
273300 Jws <Claims > claims = jwtManager .parseToken (token );
274301 context .setAttribute ("CLAIMS" , claims );
275302 return true ;
276303 } catch (JwtException e ) {
277304 // Log authentication failure
305+ System .err .println ("[WARNING] JWT validation failed: " + e .getMessage ());
278306 return false ;
279307 }
280308 }
@@ -347,11 +375,17 @@ private void handleSSE(final ChannelHandlerContext ctx, final Request<FullHttpRe
347375 }
348376 }
349377
350- private void sendErrorResponse (ChannelHandlerContext ctx , HttpResponseStatus status , String message ) {
378+ private void sendErrorResponse (ChannelHandlerContext ctx , HttpResponseStatus status , String message ,
379+ String allowOrigin ) {
351380 ByteBuf content = copiedBuffer (message , CharsetUtil .UTF_8 );
352381 FullHttpResponse response = new DefaultFullHttpResponse (HTTP_1_1 , status , content );
353382 response .headers ().set (HttpHeaderNames .CONTENT_TYPE , "text/plain; charset=UTF-8" );
354383 response .headers ().set (HttpHeaderNames .CONTENT_LENGTH , content .readableBytes ());
384+ // CORS header must be present even on error responses so the browser
385+ // can read the status code and body instead of reporting a CORS failure.
386+ if (allowOrigin != null && !allowOrigin .isEmpty ()) {
387+ response .headers ().set (HttpHeaderNames .ACCESS_CONTROL_ALLOW_ORIGIN , allowOrigin );
388+ }
355389 ctx .writeAndFlush (response ).addListener (ChannelFutureListener .CLOSE );
356390 }
357391
0 commit comments