Java Security Annotation 研究
運用 Annotation 簡化程式碼
前言
在現代軟體開發中,軟體的安全性是一個不可或缺的要素,尤其是在建立 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();
}
這樣寫程式碼真的變得超級短的,閱讀程式碼的時候也不會花太多時間跟精神在理解邏輯判斷上。