1.AOP簡介
AOP,面向切面編程,往往被定義為促使軟件系統實現關注點的分離的技術。系統是由許多不同的組件所組成的,每一個組件負責一塊特定的功能。除了實現自身核心功能之外,這些組件還經常承擔着額外的職責。例如日志、事務管理和安全這樣的核心服務經常融入到自身具有核心業務邏輯的組件中去。這些系統服務經常被稱為橫切關注點,因為它們會跨越系統的多個組件
下面介紹一下AOP相關的術語:
通知: 通知定義了切面是什么以及何時使用的概念。Spring 切面可以應用5種類型的通知:
前置通知(Before):在目標方法被調用之前調用通知功能。
后置通知(After):在目標方法完成之后調用通知,此時不會關心方法的輸出是什么。
返回通知(After-returning):在目標方法成功執行之后調用通知。
異常通知(After-throwing):在目標方法拋出異常后調用通知。
環繞通知(Around):通知包裹了被通知的方法,在被通知的方法調用之前和調用之后執行自定義的行為。
連接點:是在應用執行過程中能夠插入切面的一個點。
切點: 切點定義了切面在何處要織入的一個或者多個連接點。
切面:是通知和切點的結合。通知和切點共同定義了切面的全部內容。
引入:引入允許我們向現有類添加新方法或屬性。
織入:是把切面應用到目標對象,並創建新的代理對象的過程。切面在指定的連接點被織入到目標對象中。在目標對象的生命周期中有多個點可以進行織入:
編譯期: 在目標類編譯時,切面被織入。這種方式需要特殊的編譯器。AspectJ的織入編譯器就是以這種方式織入切面的。
類加載期:切面在目標加載到JVM時被織入。這種方式需要特殊的類加載器(class loader)它可以在目標類被引入應用之前增強該目標類的字節碼。
運行期: 切面在應用運行到某個時刻時被織入。一般情況下,在織入切面時,AOP容器會為目標對象動態地創建一個代理對象。SpringAOP就是以這種方式織入切面的。
2.編寫Spring AOP的aspect類
package com.sxk.aop;
import com.sxk.entity.Token;
import com.sxk.service.AuthorityService;
import com.sxk.service.TokenService;
import org.apache.commons.fileupload.RequestContext;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import java.sql.Timestamp;
import java.util.Date;
import java.util.List;
/**
* Created by stonegeek on 2017/3/4.
*/
@Aspect
public class AroundTest {
@Autowired
TokenService tokenService;
@Autowired
AuthorityService authorityService;
@Pointcut("execution(public * com.sxk.controller.testcontroller.*(..))")
public void testaround(){}
@Around("testaround()")
public Object test(ProceedingJoinPoint jp) throws Throwable{
System.out.println("開始驗證Token權限。。。。");
HttpServletRequest request= ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
String tokenName=this.getToken(request);
String url=this.getURL(request);
String method=this.getMethod(request);
System.out.println("Token為:"+tokenName);
System.out.println("URL為:"+url);
System.out.println("Method為:"+method);
Token token=tokenService.findByTokenName(tokenName);
if(token==null)
return "{'result':'Token not exsits'}";
Timestamp creatTime=token.getCreateTime();
int len=token.getEffectiveTime();
Timestamp timeNow=new Timestamp(new Date().getTime());
List<String> allApi=null;
if((creatTime.getTime()+len*1000*60)>=timeNow.getTime()){
allApi=authorityService.getAPI(tokenName);
System.out.println(allApi);
if(allApi!=null&&allApi.contains(url)){
System.out.println("Token驗證通過!!!");
return jp.proceed();
}else {
System.out.println("驗證失敗!!!");
return "{'result':'No authority for this API!!'}";
}
}else {
System.out.println("The Token is Timeout");
return "{'result':'The Token is Timeout!!'}";
}
}
public String getToken(HttpServletRequest request){
return request.getHeader("token");
}
public String getURL(HttpServletRequest request){
return request.getRequestURI();
}
public String getMethod(HttpServletRequest request){
return request.getMethod();
}
}
代碼詳解:
在管理系統調用后台接口之前,會進行攔截,攔截方法是通過((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest()拿到request,在這個request的headers中會有一個key-value(前后台約定的),key為token,value為tokenName,此tokenName為UUID,然后通過這個tokenName從數據庫中查詢是否有此token,沒有的話將返回token not exists字樣,有的話 則進行判斷此token是否過期,token表中存有token創建時間,還有有效時間,token過期的話,返回the token is timeout!,token有效的話再通過tokenName查詢權限表此token的所有允許的API,再對此request和API進行比對,如果包含則允許請求資源,否則返回no authority for this api。
3.登錄業務實現類
package com.sxk.service.impl;
import com.sxk.dao.TokenMapper;
import com.sxk.dao.UserMapper;
import com.sxk.service.Login_Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.sql.Timestamp;
import java.util.Date;
import java.util.UUID;
import com.sxk.entity.User;
import com.sxk.entity.Token;
/**
* Created by stonegeek on 2017/3/5.
*/
@Service("login_ServiceImpl")
public class Login_ServiceImpl implements Login_Service {
int effectTime=200;
@Autowired
private UserMapper userMapper;
@Autowired
private TokenMapper tokenMapper;
@Override
public String login(String userName, String password) {
String json=null;
System.out.println("【認證中心】得到用戶名和密碼分別為:"+userName+","+password);
User user=userMapper.login(userName,password);
System.out.println(user);
Token token;
if (user!=null) {
System.out.println("【認證中心】用戶名、密碼驗證通過!");
UUID tokenName = UUID.randomUUID();
token = new Token();
token.setLoginName(userName);
token.setTokenName(tokenName.toString());
Timestamp timeNow = new Timestamp(new Date().getTime());
token.setCreateTime(timeNow);
token.setEffectiveTime(effectTime);
System.out.println("別再出錯了");
if (tokenMapper.findByLoginName(userName) == null) {
tokenMapper.insert(token);
} else {
tokenMapper.updateByPrimaryKeySelective(token);
}
json = "{\"token\":\"" + tokenName.toString() + "\"}";
}else{
json="{\"token\":\"false\"}";
}
System.out.println(json);
return json;
}
}
代碼詳解:
首先,登錄時給了username和password,然后根據這兩個字段查詢數據庫是否有此用戶,或者用戶的密碼是否正確,通過之后,會根據將此username作為loginname存到token表中,於此同時還有一個UUID生成作為tokenname,creatime、effectTime一同存到token表中,當然此操作是有條件的,先根據tokenname判斷token表中是否有此token,沒有的話在進行insert,有的話則修改token的有效時間和創建時間。
4.AOP配置文件
<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean id="aroundTest" class="com.sxk.aop.AroundTest" />
5.token實體類
package com.sxk.entity;
import java.io.Serializable;
import java.sql.Timestamp;
/**
* Created by lenovo on 2017/3/2.
*/
public class Token implements Serializable {
private Integer tokenId;
private String tokenName;
private Timestamp createTime;
private Integer effectiveTime;
private String loginName;
public Integer getTokenId() {
return tokenId;
}
public void setTokenId(Integer tokenId) {
this.tokenId = tokenId;
}
public String getTokenName() {
return tokenName;
}
public void setTokenName(String tokenName) {
this.tokenName = tokenName;
}
public Timestamp getCreateTime() {
return createTime;
}
public void setCreateTime(Timestamp createTime) {
this.createTime = createTime;
}
public Integer getEffectiveTime() {
return effectiveTime;
}
public void setEffectiveTime(Integer effectiveTime) {
this.effectiveTime = effectiveTime;
}
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
}
6.數據庫模型
7.關於UUID.randomUUID()簡單介紹
UUID.randomUUID().toString()是javaJDK提供的一個自動生成主鍵的方法。UUID(Universally Unique Identifier)全局唯一標識符,是指在一台機器上生成的數字,它保證對在同一時空中的所有機器都是唯一的,是由一個十六位的數字組成,表現出來的形式。由以下幾部分的組合:當前日期和時間(UUID的第一個部分與時間有關,如果你在生成一個UUID之后,過幾秒又生成一個UUID,則第一個部分不同,其余相同),時鍾序列,全局唯一的IEEE機器識別號(如果有網卡,從網卡獲得,沒有網卡以其他方式獲得),UUID的唯一缺陷在於生成的結果串會比較長。
8.總結
此權限控制省略了對其他業務類的操作,比如說用戶的CRUD模塊、權限的CRUD模塊、API的CRUD模塊、角色的CRUD模塊以及各個模塊之間的關聯管理,不過核心部分已經介紹差不多了。
實際開發當中,要根據具體的需求來修改代碼的結構實現等等!