springboot 配置elasticsearch

package com.jinw.cms.config;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.Header;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.message.BasicHeader;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.StringUtils;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.Arrays;

@ConfigurationProperties(prefix = "spring.elasticsearch") //配置的前缀
@Configuration
@Slf4j
public class ElasticsearchConfig {
    @Setter
    private String uris;
    @Setter
    private String username;
    @Setter
    private String password;

    /**
     * 解析配置的字符串,转为HttpHost对象数组
     *
     * @return
     */
    private HttpHost[] toHttpHost() {
        if (!StringUtils.hasLength(uris)) {
            throw new RuntimeException("invalid elasticsearch configuration");
        }
        String[] hostArray = uris.split(",");
        HttpHost[] httpHosts = new HttpHost[hostArray.length];
        HttpHost httpHost;
        for (int i = 0; i < hostArray.length; i++) {
            String[] strings = hostArray[i].split(":");
            httpHost = new HttpHost(strings[0], Integer.parseInt(strings[1]), "http");
            httpHosts[i] = httpHost;
        }
        return httpHosts;
    }

    @Bean("clientByPasswd")
    public ElasticsearchClient clientByPasswd() throws Exception {
        ElasticsearchTransport transport = getElasticsearchTransport(username, password, toHttpHost());
        return new ElasticsearchClient(transport);
    }

    private static SSLContext buildSSLContext() {
        ClassPathResource resource = new ClassPathResource("es01.crt");
        SSLContext sslContext = null;
        try {
            CertificateFactory factory = CertificateFactory.getInstance("X.509");
            Certificate trustedCa;
            try (InputStream is = resource.getInputStream()) {
                trustedCa = factory.generateCertificate(is);
            }
            KeyStore trustStore = KeyStore.getInstance("pkcs12");
            trustStore.load(null, null);
            trustStore.setCertificateEntry("ca", trustedCa);
            SSLContextBuilder sslContextBuilder = SSLContexts.custom()
                    .loadTrustMaterial(trustStore, null);
            sslContext = sslContextBuilder.build();
        } catch (CertificateException | IOException | KeyStoreException | NoSuchAlgorithmException |
                KeyManagementException e) {
            log.error("ES连接认证失败", e);
        }
        return sslContext;
    }

    private static ElasticsearchTransport getElasticsearchTransport(String username, String passwd, HttpHost... hosts) {
        // 账号密码的配置
        final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, passwd));
        // 自签证书的设置,并且还包含了账号密码
        HttpClientConfigCallback callback = httpAsyncClientBuilder -> httpAsyncClientBuilder
//                .setSSLContext(buildSSLContext())    //  证书式认证方式
                .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
                .setDefaultCredentialsProvider(credentialsProvider)
                //  Todo 这里是关键  ######################解决 X-Elastic-Product  问题开始
                .setDefaultHeaders(
                        Arrays.asList(
                                new BasicHeader(
                                        HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())))
                .addInterceptorLast(
                        (HttpResponseInterceptor)
                                (response, context) ->
                                        response.addHeader("X-Elastic-Product", "Elasticsearch"));
        // X-Elastic-Product   end
        // 用builder创建RestClient对象
        RestClient client = RestClient
                .builder(hosts)
                .setHttpClientConfigCallback(callback)
                .build();
        return new RestClientTransport(client, new JacksonJsonpMapper());
    }

    private static ElasticsearchTransport getElasticsearchTransport(String apiKey, HttpHost... hosts) {
        // 将ApiKey放入header中
        Header[] headers = new Header[]{new BasicHeader("Authorization", "ApiKey " + apiKey)};
        // es自签证书的设置
        HttpClientConfigCallback callback = httpAsyncClientBuilder -> httpAsyncClientBuilder
                .setSSLContext(buildSSLContext())
                .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE);
        // 用builder创建RestClient对象
        RestClient client = RestClient
                .builder(hosts)
                .setHttpClientConfigCallback(callback)
                .setDefaultHeaders(headers)
                .build();
        return new RestClientTransport(client, new JacksonJsonpMapper());
    }
}

展开阅读剩余部分

springboot 实现限流控制

package com.jinw.cms.config;

import com.jinw.cms.aspectj.RateLimiterAspect;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

@RequiredArgsConstructor
@Configuration
public class RateLimiterConfig {

    private final RedisTemplate<String, Object> redisTempate;

    @Bean
    @ConditionalOnProperty(name = "jw.rate-limiter.enable", havingValue = "true")
    public RateLimiterAspect rateLimitAspect() {
        return new RateLimiterAspect(redisTempate, limitScript());
    }

    /**
     * Lua限流脚本
     */
    public DefaultRedisScript<Boolean> limitScript() {
        DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(" local key = KEYS[1] --限流KEY\n" +
                "                local limit = tonumber(ARGV[1]) --限流大小\n" +
                "                local expireTime = tonumber(ARGV[2]) --过期时间 单位/s\n" +
                "\n" +
                "                local current = tonumber(redis.call('get', key) or \"0\")\n" +
                "                if current + 1 > limit then\n" +
                "                    return false --当前值超过限流大小阈值\n" +
                "                end\n" +
                "                current = tonumber(redis.call('incr', key)) --请求数+1\n" +
                "                if current == 1 then\n" +
                "                    redis.call('expire', key, expireTime) --设置过期时间\n" +
                "                end\n" +
                "                return true;");
        redisScript.setResultType(Boolean.class);
        return redisScript;
    }
}

展开阅读剩余部分

springboot 实现xss过滤

package com.jinw.cms.aspectj;

import com.jinw.cms.aspectj.annotation.XssIgnore;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.AsyncHandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;

public class XssInterceptor implements AsyncHandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            XssIgnore xssIgnore = handlerMethod.getMethodAnnotation(XssIgnore.class);
            if (Objects.nonNull(xssIgnore)) {
                XssContextHolder.ignore();
            }
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        XssContextHolder.remove();
    }

    /**
     * 如果返回一个current类型的变量,会启用一个新的线程。执行完preHandle方法之后立即会调用afterConcurrentHandlingStarted
     * 然后新线程再以次执行preHandle,postHandle,afterCompletion**
     */
    @Override
    public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        XssContextHolder.remove();
    }
}

展开阅读剩余部分