溫馨提示×

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

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

java怎么實(shí)現(xiàn)給接口增加一個(gè)參數(shù)

發(fā)布時(shí)間:2022-03-24 13:45:23 來(lái)源:億速云 閱讀:905 作者:iii 欄目:大數(shù)據(jù)

這篇“java怎么實(shí)現(xiàn)給接口增加一個(gè)參數(shù)”文章的知識(shí)點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價(jià)值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來(lái)看看這篇“java怎么實(shí)現(xiàn)給接口增加一個(gè)參數(shù)”文章吧。

一、背景

一般在微服務(wù)架構(gòu)中我們都會(huì)使用spring security oauth3來(lái)進(jìn)行權(quán)限控制,我們將資源服務(wù)全部放在內(nèi)網(wǎng)環(huán)境中,將API網(wǎng)關(guān)暴露在公網(wǎng)上,公網(wǎng)如果想要訪問(wèn)我們的資源必須經(jīng)過(guò)API網(wǎng)關(guān)進(jìn)行鑒權(quán),鑒權(quán)通過(guò)后再訪問(wèn)我們的資源服務(wù)。我們根據(jù)如下圖片來(lái)分析一下問(wèn)題。

java怎么實(shí)現(xiàn)給接口增加一個(gè)參數(shù)

現(xiàn)在我們有三個(gè)服務(wù):分別是用戶服務(wù)、訂單服務(wù)和產(chǎn)品服務(wù)。用戶如果購(gòu)買(mǎi)產(chǎn)品,則需要調(diào)用產(chǎn)品服務(wù)生成訂單,那么我們?cè)谶@個(gè)調(diào)用過(guò)程中有必要鑒權(quán)嗎?答案是否定的,因?yàn)檫@些資源服務(wù)放在內(nèi)網(wǎng)環(huán)境中,完全不用考慮安全問(wèn)題。 

二、思路

如果要想實(shí)現(xiàn)這個(gè)功能,我們則需要來(lái)區(qū)分這兩種請(qǐng)求,來(lái)自網(wǎng)關(guān)的請(qǐng)求進(jìn)行鑒權(quán),而服務(wù)間的請(qǐng)求則直接調(diào)用。

是否可以給接口增加一個(gè)參數(shù)來(lái)標(biāo)記它是服務(wù)間調(diào)用的請(qǐng)求?

這樣雖然可以實(shí)現(xiàn)兩種請(qǐng)求的區(qū)分,但是實(shí)際中不會(huì)這么做。一般情況下服務(wù)間調(diào)用和網(wǎng)關(guān)請(qǐng)求的數(shù)據(jù)接口是同一個(gè)接口,如果寫(xiě)成兩個(gè)接口來(lái)分別給兩種請(qǐng)求調(diào)用,這樣無(wú)疑增加了大量重復(fù)代碼。也就是說(shuō)我們一般不會(huì)通過(guò)改變請(qǐng)求參數(shù)的個(gè)數(shù)來(lái)實(shí)現(xiàn)這兩種服務(wù)的區(qū)分。

雖然不能增加請(qǐng)求的參數(shù)個(gè)數(shù)來(lái)區(qū)分,但是我們可以給請(qǐng)求的header中添加一個(gè)參數(shù)用來(lái)區(qū)分。這樣完全可以避免上面提到的問(wèn)題。 

三、實(shí)現(xiàn) 

3.1 自定義注解

我們自定義一個(gè)Inner的注解,然后利用aop對(duì)這個(gè)注解進(jìn)行處理

1@Target(ElementType.METHOD)
2@Retention(RetentionPolicy.RUNTIME)
3@Documented
4public @interface Inner {
5    /**
6     * 是否AOP統(tǒng)一處理
7     */
8    boolean value() default true;
9}
 
 1@Aspect
2@Component
3public class InnerAspect implements Ordered {
4
5    private final Logger log = LoggerFactory.getLogger(InnerAspect.class);
6
7    @Around("@annotation(inner)")
8    public Object around(ProceedingJoinPoint point, Inner inner) throws Throwable {
9        String header = ServletUtils.getRequest().getHeader(SecurityConstants.FROM);
10        if (inner.value() && !StringUtils.equals(SecurityConstants.FROM_IN, header)){
11            log.warn("訪問(wèn)接口 {} 沒(méi)有權(quán)限", point.getSignature().getName());
12            throw new AccessDeniedException("Access is denied");
13        }
14        return point.proceed();
15    }
16
17    @Override
18    public int getOrder() {
19        return Ordered.HIGHEST_PRECEDENCE + 1;
20    }
21}
 

上面這段代碼就是獲取所有加了@Inner注解的方法或類(lèi),判斷請(qǐng)求頭中是否有我們規(guī)定的參數(shù),如果沒(méi)有,則不允許訪問(wèn)接口。 

3.2 暴露url

將所有注解了@Inner的方法和類(lèi)暴露出來(lái),允許不鑒權(quán)可以方法,這里需要注意的點(diǎn)是如果方法使用pathVariable 傳參的,則需要將這個(gè)參數(shù)轉(zhuǎn)換為*。如果不轉(zhuǎn)換,當(dāng)成接口的訪問(wèn)路徑,則找不到此接口。

 1@Configuration
2public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware{
3
4    private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");
5    private ApplicationContext applicationContext;
6    private List<String> urls = new ArrayList<>();
7    public static final String ASTERISK = "*";
8
9    @Override
10    public void afterPropertiesSet() {
11        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
12        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
13        map.keySet().forEach(info -> {
14            HandlerMethod handlerMethod = map.get(info);
15            // 獲取方法上邊的注解 替代path variable 為 *
16            Inner method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Inner.class);
17            Optional.ofNullable(method).ifPresent(inner -> info.getPatternsCondition().getPatterns()
18                    .forEach(url -> urls.add(ReUtil.replaceAll(url, PATTERN, ASTERISK))));
19            // 獲取類(lèi)上邊的注解, 替代path variable 為 *
20            Inner controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Inner.class);
21            Optional.ofNullable(controller).ifPresent(inner -> info.getPatternsCondition().getPatterns()
22                    .forEach(url -> urls.add(ReUtil.replaceAll(url, PATTERN, ASTERISK))));
23        });
24    }
25
26    @Override
27    public void setApplicationContext(ApplicationContext context) {
28        this.applicationContext = context;
29    }
30
31    public List<String> getUrls() {
32        return urls;
33    }
34
35    public void setUrls(List<String> urls) {
36        this.urls = urls;
37    }
38}
 

在資源服務(wù)器中,將請(qǐng)求暴露出來(lái)

 1public void configure(HttpSecurity httpSecurity) throws Exception {
2    //允許使用iframe 嵌套,避免swagger-ui 不被加載的問(wèn)題
3    httpSecurity.headers().frameOptions().disable();
4    ExpressionUrlAuthorizationConfigurer<HttpSecurity>
5        .ExpressionInterceptUrlRegistry registry = httpSecurity
6        .authorizeRequests();
7    // 將上面獲取到的請(qǐng)求,暴露出來(lái)
8    permitAllUrl.getUrls()
9        .forEach(url -> registry.antMatchers(url).permitAll());
10    registry.anyRequest().authenticated()
11        .and().csrf().disable();
12}
   

3.3 如何去請(qǐng)求

定義一個(gè)接口:

1@PostMapping("test")
2@Inner
3public String test(@RequestParam String id){
4    return id;
5}
 

定義feign遠(yuǎn)程調(diào)用接口

1@PostMapping("test")
2MediaFodderBean test(@RequestParam("id") String id,@RequestHeader(SecurityConstants.FROM) String from);

服務(wù)間進(jìn)行調(diào)用,傳請(qǐng)求頭

1 String id = testService.test(id, SecurityConstants.FROM_IN);  

四、思考 

4.1 安全性

上面雖然實(shí)現(xiàn)了服務(wù)間調(diào)用,但是我們將@Inner的請(qǐng)求暴露出去了,也就是說(shuō)不用鑒權(quán)既可以訪問(wèn)到,那么我們是不是可以模擬一個(gè)請(qǐng)求頭,然后在其他地方通過(guò)網(wǎng)關(guān)來(lái)調(diào)用呢?

答案是可以,那么,這時(shí)候我們就需要對(duì)網(wǎng)關(guān)中分發(fā)的請(qǐng)求進(jìn)行處理,在網(wǎng)關(guān)中寫(xiě)一個(gè)全局?jǐn)r截器,將請(qǐng)求頭的form參數(shù)清洗。

 1@Component
2public class RequestGlobalFilter implements GlobalFilter, Ordered {
3
4    @Override
5    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
6        // 清洗請(qǐng)求頭中from 參數(shù)
7        ServerHttpRequest request = exchange.getRequest().mutate()
8            .headers(httpHeaders -> httpHeaders.remove(SecurityConstants.FROM))
9            .build();
10        addOriginalRequestUrl(exchange, request.getURI());
11        String rawPath = request.getURI().getRawPath();
12        ServerHttpRequest newRequest = request.mutate()
13            .path(rawPath)
14            .build();
15        exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newRequest.getURI());
16        return chain.filter(exchange.mutate()
17            .request(newRequest.mutate()
18                .build()).build());
19    }
20
21    @Override
22    public int getOrder() {
23        return -1000;
24    }
25}
   

4.2 擴(kuò)展性

我們自定義@Inner注解的時(shí)候,放了一個(gè)boolean類(lèi)型的value(),默認(rèn)為true。如果我們想讓這個(gè)請(qǐng)求可以通過(guò)網(wǎng)關(guān)訪問(wèn)的話,將value賦值為false即可。

1@PostMapping("test")
2@Inner(value=false)
3public String test(@RequestParam String id){
4    return id;
5}

以上就是關(guān)于“java怎么實(shí)現(xiàn)給接口增加一個(gè)參數(shù)”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對(duì)大家有幫助,若想了解更多相關(guān)的知識(shí)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

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

AI