Java Security Annotation 研究

運用 Annotation 簡化程式碼

(KJH) Kuan-Jung, Huang
7 min readDec 25, 2023
Image Powered by DALLE

前言

在現代軟體開發中,軟體的安全性是一個不可或缺的要素,尤其是在建立 Web App 和API 端點時。最近正在研究如何讓每個 API end point 都不用寫大量程式,即可達到 security。Java 的 Annotation 提供了一種簡潔且有效的方法來增強 API 的安全性。這篇文章將深入探討如何使用 Annotation 來保護API端點,同時減少冗餘的程式碼。

Java Security Annotation 的核心概念

在 Java 中,Security Annotation 是一種標記機制,它讓我們在不改變主要業務邏輯的情況下,運用 Annotation 來幫助我們在函式外面加上安全控制的功能。我們可以新增一個名為 @Secured 的自定義 Security Annotation,並將其應用於需要保護的資源上。

import javax.ws.rs.NameBinding;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@NameBinding
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface Secured { }

接著在AuthenticationFilter上面加入@Secured 標籤,這樣在 HTTP REQUEST 時,確保 header 有對應的資料存在。

import javax.annotation.Priority;
import javax.ws.rs.Priorities;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
import java.io.IOException;

@Secured
@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {

private static final String REALM = "example";
private static final String AUTHENTICATION_SCHEME = "Bearer";

@Override
public void filter(ContainerRequestContext requestContext) throws IOException {

// Get the Authorization header from the request
String authorizationHeader =
requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);

// Validate the Authorization header
if (!isTokenBasedAuthentication(authorizationHeader)) {
abortWithUnauthorized(requestContext);
return;
}

// Extract the token from the Authorization header
String token = authorizationHeader
.substring(AUTHENTICATION_SCHEME.length()).trim();

try {

// Validate the token
validateToken(token);

} catch (Exception e) {
abortWithUnauthorized(requestContext);
}
}

private boolean isTokenBasedAuthentication(String authorizationHeader) {

// Check if the Authorization header is valid
// It must not be null and must be prefixed with "Bearer" plus a whitespace
// The authentication scheme comparison must be case-insensitive
return authorizationHeader != null && authorizationHeader.toLowerCase()
.startsWith(AUTHENTICATION_SCHEME.toLowerCase() + " ");
}

private void abortWithUnauthorized(ContainerRequestContext requestContext) {

// Abort the filter chain with a 401 status code response
// The WWW-Authenticate header is sent along with the response
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED)
.header(HttpHeaders.WWW_AUTHENTICATE,
AUTHENTICATION_SCHEME + " realm=\"" + REALM + "\"")
.build());
}

private void validateToken(String token) throws Exception {
// Check if the token was issued by the server and if it's not expired
// Throw an Exception if the token is invalid
try {
if(token.equals("123")){

} else {
throw new Exception("Invalid Token");
}
} catch(Exception e) {
throw e;
}
}
}

AuthN 驗證

當API端點被訪問時,AuthenticationFilter 會檢查 Header。它首先檢查標頭是否存在並以正確的格式開頭(在這個例子中是使用 Bearer token 進行檢查)。如果 token 無效或不存在,請求將被拒絕,並回傳 401 。

端點安全性管理

除了方便我們做 AuthN 的驗證以外,@Secured 也讓我們輕鬆地在需要的地方實施安全性控制,而不需要在每個 method 中寫一大段的安全控制檢查。舉例來說要保護一個 Get Request,只需簡單地在該 method 的前面加上@Secured tag

@GET
@Secured
@Produces(MediaType.APPLICATION_JSON)
public List<Activity> getAllActivities(){
return activityRepository.findAllActivities();
}

這樣寫程式碼真的變得超級短的,閱讀程式碼的時候也不會花太多時間跟精神在理解邏輯判斷上。

--

--

(KJH) Kuan-Jung, Huang
(KJH) Kuan-Jung, Huang

Written by (KJH) Kuan-Jung, Huang

CTO at Metablox.co, Founder of AI Users Community in Taiwan

No responses yet