專案中後台功能已開發大半,卻遺漏了最基礎的用戶登錄與權限驗證。面對無規律的既有 URL,傳統過濾器/攔截器難以優雅應用。本文展示如何利用 Spring AOP 配合自定義註解,以最小改動實現靈活的 Token 權限控制。
<aop:aspectj-autoproxy />
需引入 spring-aop、aspectjweaver 依賴,並在 Spring 配置文件頭部聲明 AOP 命名空間。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserAccess {
// 可擴展:角色、權限碼等屬性
}
@Service
public class LoginService {
// "username -> token"
public static final Map<String, String> tokenMap = new ConcurrentHashMap<>();
// "token -> User"
public static final Map<String, User> loginUserMap = new ConcurrentHashMap<>();
public String login(String name, String password) {
// 1. 驗證用戶名密碼(略,對接 UserDao)
// 2. 生成 Token:username + password + timestamp → MD5
String token = MD5Util.MD5(name + password + System.currentTimeMillis());
// 3. 處理單點登錄:移除舊 Token
String oldToken = tokenMap.get(name);
if (oldToken != null) {
loginUserMap.remove(oldToken);
}
// 4. 存儲新映射
User user = new User(name, password); // 實際從 DB 獲取
tokenMap.put(name, token);
loginUserMap.put(token, user);
return token;
}
}
Token 設計要點:
@Controller
@RequestMapping("/login")
public class LoginController {
@Autowired
private LoginService loginService;
@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);
}
}
@Component
@Aspect
public class PermissionAspect {
@Before("@annotation(com.example.annotation.UserAccess)")
public void checkPermission(JoinPoint joinPoint) throws Exception {
Object[] args = joinPoint.getArgs();
HttpServletRequest request = (HttpServletRequest) args[0];
// 建議從 Header 獲取 Token,不侵業務參數
String token = request.getHeader("token");
if (token == null) {
token = request.getParameter("token"); // 兼容 Query 參數
}
User user = LoginService.loginUserMap.get(token);
if (user == null) {
throw new Exception("無效或過期的 Token,請重新登錄");
}
// 可選:將 User 放入 ThreadLocal 或 Request 屬性,供業務方法直接使用
request.setAttribute("currentUser", user);
}
}
@Controller
@RequestMapping("/order")
public class OrderController {
@UserAccess // ← 這裡
@RequestMapping("/list")
@ResponseBody
public List<Order> listOrders(HttpServletRequest request) {
User user = (User) request.getAttribute("currentUser");
return orderService.getOrdersByUser(user.getId());
}
}
| 維度 | 傳統過濾器/攔截器 | AOP + 註解方案 |
|---|---|---|
| URL 規則要求 | 需統一前綴/模式 | 無要求,方法級精確控制 |
| 既有代碼改動 | 大(需調整 URL 映射) | 極小(僅加註解) |
| 靈活性 | 粗粒度 | 細粒度,可擴展註解屬性 |
| Token 獲取方式 | 通常固定參數名 | Header/Parameter 靈活配置 |
@UserAccess(roles={"ADMIN","USER"}) 實現 RBAC