溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

怎么在SpringBoot中使用Spring AOP實現(xiàn)接口鑒權(quán)

發(fā)布時間:2022-09-29 10:56:24 來源:億速云 閱讀:166 作者:iii 欄目:開發(fā)技術(shù)

這篇“怎么在SpringBoot中使用Spring AOP實現(xiàn)接口鑒權(quán)”文章的知識點大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“怎么在SpringBoot中使用Spring AOP實現(xiàn)接口鑒權(quán)”文章吧。

面向切面編程

面向切面編程,可以將與業(yè)務(wù)無關(guān)但是需要被各個業(yè)務(wù)模塊共同調(diào)用的邏輯抽取出來,以切面的方式切入到代碼中,從而降低系統(tǒng)中代碼的耦合度,減少重復(fù)的代碼。

Spring AOP是通過預(yù)編譯方式和運行期間動態(tài)代理實現(xiàn)程序面向切面編程

AOP的底層原理實現(xiàn)

AOP底層使用動態(tài)代理完成需求,為需要增加增強功能的類來生成代理類,有兩種生成代理類的方式,對于被代理類(即需要增強的類),如果:

  • 實現(xiàn)了接口,使用JDK動態(tài)代理,生成的代理類會使用其接口沒有實現(xiàn)接口,

  • 使用CGlib動態(tài)代理,生成的代理類會集成被代理類

AOP的相關(guān)術(shù)語

  • 連接點:被代理(被增強)的類中的方法

  • 切入點:實際上需要被增強的方法

  • 通知:要增強的邏輯代碼

    • 前置通知:在主體功能執(zhí)行之前執(zhí)行

    • 后置通知:在主題功能執(zhí)行之后執(zhí)行

    • 環(huán)繞通知:在主體功能執(zhí)行前后執(zhí)行

    • 異常通知:在主題功能執(zhí)行出現(xiàn)異常時執(zhí)行

    • 最終通知:主體功能無論執(zhí)行是否成功都會執(zhí)行

  • 切面:切入點和切面的結(jié)合,即被增強的方法和增強的功能組成切面

相關(guān)注解以及切入點表達式

注解:

  • @Aspect: 聲明某個類是切面,編寫通知、切入點

  • @Before: 對應(yīng)前置通知

  • @AfterReturning: 對應(yīng)后置通知

  • @Around: 對應(yīng)環(huán)繞通知

  • @AfterThrowing: 對應(yīng)異常通知

  • @After: 對應(yīng)最終通知

  • @Pointcut: 聲明切入點,標注在一個方法上可以讓表達式更簡潔

使用切入點表達式聲明切入點

  • execution([權(quán)限修飾符][返回類型][類完全路徑].[方法名稱][參數(shù)列表類型])

execution(* com.xxx.ABC.add()),對ABC類的方法進行增強

實現(xiàn)接口鑒權(quán)

1. 配置yml文件

配置接口鑒權(quán)賬密

account:
  infos:
    - account: xinchao
      secret: admin

2. 讀取賬密配置

@Data
public class SecretInfo {
    private String account;
    private String secret;
}

3.編寫接口鑒權(quán)方法

@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

首先,編寫一個注解來標識不需要鑒權(quán)

@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);
}

通過這個接口,我們可以在內(nèi)存中生成一個token,同時也會返回給前端。之后我們在調(diào)其他接口時傳入這個token進行鑒權(quán)即可。傳入的位置是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;
    }
}

以上就是關(guān)于“怎么在SpringBoot中使用Spring AOP實現(xiàn)接口鑒權(quán)”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對大家有幫助,若想了解更多相關(guān)的知識內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI