如何在SpringBoot中使用Spring-AOP實現(xiàn)接口鑒權
面向切面編程
面向切面編程,可以將與業(yè)務無關但是需要被各個業(yè)務模塊共同調用的邏輯抽取出來,以切面的方式切入到代碼中,從而降低系統(tǒng)中代碼的耦合度,減少重復的代碼。
Spring AOP是通過預編譯方式和運行期間動態(tài)代理實現(xiàn)程序面向切面編程
AOP的底層原理實現(xiàn)
AOP底層使用動態(tài)代理完成需求,為需要增加增強功能的類來生成代理類,有兩種生成代理類的方式,對于被代理類(即需要增強的類),如果:
- 實現(xiàn)了接口,使用JDK動態(tài)代理,生成的代理類會使用其接口沒有實現(xiàn)接口,
- 使用CGlib動態(tài)代理,生成的代理類會集成被代理類
AOP的相關術語
- 連接點:被代理(被增強)的類中的方法
- 切入點:實際上需要被增強的方法
- 通知:要增強的邏輯代碼
- 前置通知:在主體功能執(zhí)行之前執(zhí)行
- 后置通知:在主題功能執(zhí)行之后執(zhí)行
- 環(huán)繞通知:在主體功能執(zhí)行前后執(zhí)行
- 異常通知:在主題功能執(zhí)行出現(xiàn)異常時執(zhí)行
- 最終通知:主體功能無論執(zhí)行是否成功都會執(zhí)行
- 切面:切入點和切面的結合,即被增強的方法和增強的功能組成切面
相關注解以及切入點表達式
注解:
- @Aspect: 聲明某個類是切面,編寫通知、切入點
- @Before: 對應前置通知
- @AfterReturning: 對應后置通知
- @Around: 對應環(huán)繞通知
- @AfterThrowing: 對應異常通知
- @After: 對應最終通知
- @Pointcut: 聲明切入點,標注在一個方法上可以讓表達式更簡潔
使用切入點表達式聲明切入點
- execution([權限修飾符][返回類型][類完全路徑].[方法名稱][參數(shù)列表類型])
execution(* com.xxx.ABC.add()),對ABC類的方法進行增強
實現(xiàn)接口鑒權
1. 配置yml文件
配置接口鑒權賬密
account:
infos:
- account: xinchao
secret: admin2. 讀取賬密配置
@Data
public class SecretInfo {
private String account;
private String secret;
}3.編寫接口鑒權方法
@Configuration
@ConfigurationProperties("account")
public class SecretConfig {
private List<SecretInfo> infos;
private Map<String, SecretInfo> map;
private Map<String, TokenInfo> tokenMap = new HashMap<>();
public void setInfos(List<SecretInfo> infos) {
this.infos = infos;
map = infos.stream().collect(Collectors.toMap(SecretInfo::getAccount, Function.identity()));
}
public synchronized String getToken(String account, String secret) {
SecretInfo info = map.get(account);
if (info == null) {
throw new BusinessException("無效賬號");
}
if (!StringUtils.equals(info.getSecret(), secret)) {
throw new BusinessException("無效密碼");
}
TokenInfo tokenInfo = tokenMap.get(account);
if (tokenInfo != null && tokenInfo.getToken() != null) {
return tokenInfo.getToken();
}
tokenInfo = new TokenInfo();
String uuid = UUID.randomUUID().toString();
tokenInfo.setToken(uuid);
tokenInfo.setCreateDate(LocalDateTime.now());
tokenInfo.setExpireDate(LocalDateTime.now().plusHours(2));
tokenMap.put(account,tokenInfo);
return tokenInfo.getToken();
}
public boolean checkCaptcha(String captcha) {
return tokenMap.values().stream().anyMatch(e->StringUtils.equals(e.getToken(),captcha));
}
}@Data
public class TokenInfo {
private LocalDateTime createDate;
private LocalDateTime expireDate;
private String token;
public String getToken() {
if (LocalDateTime.now().isBefore(expireDate)) {
return token;
}
return null;
}
public boolean verification(String token) {
return Objects.equals(this.token, token);
}
}4. 編寫AOP
首先,編寫一個注解來標識不需要鑒權
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CaptchaIgnoreAop {
}@Slf4j
@Aspect
@Component
@Order(2)
public class CaptchaAop {
@Value("${spring.profiles.active:dev}")
private String env;
@Autowired
private SecretConfig config;
@Pointcut("execution(public * com.herenit.phsswitch.controller.impl..*.*(..))" +
"&&@annotation(org.springframework.web.bind.annotation.PostMapping)" +
"&&!@annotation(com.herenit.phsswitch.aop.CaptchaIgnoreAop)")
public void tokenAop() {
}
@Around("tokenAop()")
public Object doBefore(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
if (args.length == 0 || !(args[0] instanceof RequestWrapper)
|| "test,dev".contains(env)) {
log.info("當前環(huán)境無需校驗token");
return joinPoint.proceed();
}
String captcha = ((RequestWrapper) joinPoint.getArgs()[0]).getCaptcha();
if (!config.checkCaptcha(captcha)) {
throw new BusinessException("captcha無效");
}
return joinPoint.proceed();
}
}5.編寫接口測試
@PostMapping("/login")
@CaptchaIgnoreAop
public ResponseWrapper login(@RequestBody JSONObject userInfo) {
String token = config.getToken(userInfo.getString("loginName")
, userInfo.getString("password"));
JSONObject result = new JSONObject();
result.put("platformAccessToken", token);
return ResponseWrapper.success(result);
}通過這個接口,我們可以在內存中生成一個token,同時也會返回給前端。之后我們在調其他接口時傳入這個token進行鑒權即可。傳入的位置是captcha字段
public class RequestWrapper<T> implements Serializable {
private static final long serialVersionUID = 8988706670118918321L;
public RequestWrapper() {
super();
}
private T args;
private String captcha;
private String funcode;
public T getArgs() {
return args;
}
public void setArgs(T args) {
this.args = args;
}
public String getCaptcha() {
return captcha;
}
public void setCaptcha(String captcha) {
this.captcha = captcha;
}
public String getFuncode() {
return funcode;
}
public void setFuncode(String funcode) {
this.funcode = funcode;
}
}到此這篇關于如何在SpringBoot中使用Spring-AOP實現(xiàn)接口鑒權的文章就介紹到這了,更多相關 SpringBoot Spring-AOP 接口鑒權內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
java如何將map數(shù)據(jù)存入到實體類對象中
在Java編程中,經(jīng)常需要將Map集合中的數(shù)據(jù)轉換為實體類對象,這可以通過反射機制實現(xiàn),即通過遍歷Map對象,使用反射根據(jù)鍵名對應實體類的屬性名,動態(tài)調用setter方法將值設置到實體對象中,這樣的操作使得數(shù)據(jù)從Map結構轉移到了具體的JavaBean中,便于后續(xù)的操作和管理2024-09-09
教你如何用Eclipse創(chuàng)建一個Maven項目
這篇文章主要介紹了教你如何用Eclipse創(chuàng)建一個Maven項目,文中有非常詳細的代碼示例,對正在入門Java的小伙伴們是非常有幫助的喲,需要的朋友可以參考下2021-05-05
SpringBoot配置多個數(shù)據(jù)源超簡單步驟(連接多個數(shù)據(jù)庫)
公司項目有連接多個不同數(shù)據(jù)庫的需求,特研究了一下,根據(jù)網(wǎng)上的資料,這篇文章主要給大家介紹了關于SpringBoot配置多個數(shù)據(jù)源(連接多個數(shù)據(jù)庫)的相關資料,需要的朋友可以參考下2024-05-05

