在 App 專案開發中,使用者登入與權限驗證是核心基礎功能。本文分享如何在既有 Spring 專案中,透過 Token 機制與 Spring AOP 實現輕量級的權限控管,且不需大幅修改既有程式碼。
在 Spring 設定檔中啟用 AspectJ 自動代理:
<aop:aspectj-autoproxy />
需引入 spring-aop 相關 JAR 套件,並在設定檔宣告 AOP 命名空間。
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserAccess {
}
此註解可標註在類別或方法上,作為 AOP 切入點依據。
import org.springframework.stereotype.Service;
import java.util.*;
@Service
public class LoginService {
// 儲存「使用者名稱:Token」對應
public static Map<String, String> tokenMap = new HashMap<>();
// 儲存「Token:User 物件」對應
public static Map<String, User> loginUserMap = new HashMap<>();
public String login(String name, String password) {
System.out.println(name + "-----登入嘗試");
String oldToken = tokenMap.get(name);
User user;
if (oldToken == null) {
user = new User();
user.setName(name);
user.setPassword(password);
System.out.println("新使用者登入");
} else {
user = loginUserMap.get(oldToken);
loginUserMap.remove(oldToken);
System.out.println("更新使用者登入 Token");
}
// Token 組成:username + password + 登入時間 → MD5 加密
String token = MD5Util.MD5(name + password + System.currentTimeMillis());
loginUserMap.put(token, user);
tokenMap.put(name, token);
System.out.println("目前線上使用者數:" + tokenMap.size());
return token;
}
}
Token 設計考量:
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("/login")
public class LoginController {
@Autowired
private LoginService loginService;
@UserAccess // 示例用,實際登入不需驗證
@RequestMapping(value="/loginin", method=RequestMethod.GET)
@ResponseBody
public String login(HttpServletRequest request) {
String name = request.getParameter("name");
String password = request.getParameter("password");
return loginService.login(name, password);
}
}
建議 Token 放在 Request Header 中,前後端分離架構下不影響既有參數處理邏輯。
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
@Component
@Aspect
public class PermissionAspect {
// 以 @UserAccess 註解為切入點
@Before("@annotation(com.example.annotation.UserAccess)")
public void checkPermission(JoinPoint joinPoint) throws Exception {
System.out.println("前置通知:權限驗證開始");
Object[] args = joinPoint.getArgs();
HttpServletRequest request = (HttpServletRequest) args[0];
String token = request.getParameter("token");
System.out.println("驗證 Token:" + token);
User user = LoginService.loginUserMap.get(token);
if (user == null) {
System.out.println("驗證失敗:Token 無效或已過期");
throw new Exception("無權限存取");
}
System.out.println("驗證通過:使用者 " + user.getName());
}
}
/login/loginin 傳送帳密,取得 Tokentoken=xxx@UserAccess 的方法執行前,AOP 自動攔截驗證完整原始碼參考:GitHub 專案連結