本文主要寫了一個關於登錄校驗的demo,使用範圍,java 項目進行接口請求的校驗,採用了springboot框架+註解+攔截器的方式來實現,也是當前行業中比較常用的一種模式,現在主要對代碼流程進行解析和說明。
創建一個註解
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Auth {
}
所謂註解就是爲類或方法加上一個標識,沒有任何作用。擴展一下:
jdk1.5之後Java增加了對元數據(MetaData)的支持,也就是Annotation(註解),他是代碼裏的特殊標記,
這些標記可以在編譯,類加載,運行時被讀取,並執行相應操作。通過使用註解可以在不改變原有邏輯的情況下,
在源文件中添加補充信息,代碼分析工具,開發工具,部署工具,可以更具這些信息進行驗證和部署。
Annotation就像Java修飾符一樣,可以用於修飾包、類、構造器、方法、變量、參數、局部變量,這些信息
存儲在Annotation的"name=value“中。
Annotation對程序的運行無影響,如果希望Annotation在運行時其到作用,就需要通過配套工具對
annotation中的信息進行訪問處理,這一工具統稱爲APT。
這裏我們創建一個最簡單的註解,精確到方法上,也可以作用在類上。
創建攔截器
@Component
public class LoginInterceptor extends HandlerInterceptorAdapter {
@Autowired
protected AdminSecurityManager adminSecurityManager;
public LoginInterceptor(AdminSecurityManager adminSecurityManager) {
this.adminSecurityManager = adminSecurityManager;
}
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
adminSecurityManager.setSession(request.getSession());
if (!handler.getClass().isAssignableFrom(HandlerMethod.class)) {
return true;
}
UserExt user = JSONObject.toJavaObject(adminSecurityManager.getPrincipal(), UserExt.class);
final HandlerMethod handlerMethod = (HandlerMethod) handler;
final Method method = handlerMethod.getMethod();
final Class<?> clazz = method.getDeclaringClass();
if (clazz.isAnnotationPresent(Auth.class) || method.isAnnotationPresent(Auth.class)) {
if (user == null) {
JSONObject res = new JSONObject();
res.put("code", 401);
res.put("msg", "You did not login.");
res.put("data", "");
PrintWriter out = response.getWriter();
out.append(res.toString());
return false;
}
if (CommonConstants.VALUE_YES.equals(user.getDeletedFlag())
|| !CommonConstants.STATUS_ACTIVE.equals(user.getStatus())
|| !adminSecurityManager.hasPermission(request, adminSecurityManager.getPrincipal())) {
JSONObject res = new JSONObject();
res.put("code", 403);
res.put("msg", " to access");
res.put("data", "");
PrintWriter out = response.getWriter();
out.append(res.toString());
return false;
}
// 設置登陸用戶
request.setAttribute("currentUser", user);
}
return true;
}
}
@Component
public class AdminSecurityManager extends SecurityManager<JSONObject> {
private String key = "bog";
@Override
public String getKey() {
return key;
}
@Override
public boolean hasPermission(HttpServletRequest request,JSONObject admin) {
if (admin == null){
return false;
}
try {
String requestURI = WebUtils.getCleanedURI(request.getRequestURI());
// Check if the requestURI is in permission list
String allPermissionUrls = (String) request.getSession().getAttribute(CommonConstants.ALL_PERMISSIONS);
if (allPermissionUrls.contains(requestURI + ";")) {
JSONArray permissions = admin.getJSONArray("permissionUrlList");
return permissions.stream().anyMatch(n -> Pattern.compile((String) n).matcher(requestURI).matches());
} else {
return true;
}
}catch (Exception e){
e.printStackTrace();
return false;
}
}
@Override
public Number principalId() {
return null;
}
public Admin getAdmin() {
JSONObject principal = super.getPrincipal();
return JSONObject.toJavaObject(principal, Admin.class);
}
}
public abstract class SecurityManager<T> {
protected ThreadLocal<HttpSession> session = new ThreadLocal<HttpSession>();
public abstract String getKey();
public void loginSession(T principal) {
session.get().setAttribute(CommonConstants.REDIS_KEY + getKey(), principal);
}
public void setSession(HttpSession session) {
this.session.set(session);
}
public void logout() {
this.session.get().removeAttribute(CommonConstants.REDIS_KEY + getKey());
}
public abstract boolean hasPermission(HttpServletRequest request, T principal);
public int hasVerify(HttpServletRequest request) {
return 0;
}
public abstract Number principalId();
@SuppressWarnings("unchecked")
public boolean hasPermission(HttpServletRequest request) {
T principal = getPrincipal();
if (principal == null) {
return false;
}
return hasPermission(request, principal);
}
@SuppressWarnings("unchecked")
public T getPrincipal() {
return (T) session.get().getAttribute(CommonConstants.REDIS_KEY + getKey());
}
public boolean setPrincipal(T principal) {
session.get().setAttribute(CommonConstants.REDIS_KEY + getKey(), principal);
return true;
}
public boolean getVerifyState(String url, int verifyType) {
Integer verifyState = (Integer) session.get().getAttribute("verify_" + verifyType + ":" + url);
if (verifyState != null) {
session.get().removeAttribute(CommonConstants.REDIS_KEY + getKey());
return false;
}
return true;
}
public void setVerifyState(String url, int verifyType) {
session.get().setAttribute("verify_" + verifyType + ":" + url, verifyType);
}
}
這裏的攔截器主要是集成了spring的HandlerAdapter 重寫了preHandler方法
preHandler方法的實現:
- 首先重新設置當前操作的session信息
- 判斷是否通過controler的方法進入(@RequestMapping)如果否直接放行。
- 從session中獲取用戶信息將json字符串轉成對應的對象
- 如果有@author註解,那麼進行用戶身份校驗,如果第三步取得的信息爲空則返回用戶登錄失敗信息。
- 如果沒有@author註解,直接放行。
- 最後如果登錄成功,可以將用戶信息放置於request中,我們可以在controller中的方法入參HttpServletRequest對象中獲取用戶信息。進而直接在邏輯中直接通過request.getAttrubute("")獲取
AdminSecurityManager
這個類主要是用來操作用戶信息,這裏是通過ThreadLocal來進行存儲用戶信息。通過redis去管理信息。
註冊攔截器
@Configuration
@EnableConfigurationProperties({ApiTsgProperties.class})
public class WebConfig implements WebMvcConfigurer {
@Autowired
protected AdminSecurityManager adminSecurityManager;
@Bean
public PermissionInterceptor permissionInterceptor() {
return new PermissionInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(new LoginInterceptor(adminSecurityManager));
registry.addInterceptor(permissionInterceptor());
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}
}
springboot中所有自定義攔截器都需要將其註冊到webMvc適配器中。否則不會發生作用。
api添加註解
測試
測試發現,不添加Author註解,我們可以直接調用方法,如果增加了@author註解,就需要登錄後調用。
擴展
用戶登錄成功時候會將用戶信息存放在threadlocal當中。