Spring Manual Scan Package Path and Get Bean Instances Outside Containers

Recent projects have a requirement that the Controller under the specified package be open for calls to other applications, but that its license be validated.
Solution: Define a Filter that scans all Controller s under the specified package within the init initialization method to generate an open set of URL s; check the request parameters within the doFilter method (salt MD5 generation)

The scheme uses two tool classes, the first being the wrapper class of HttpServletRequest, to solve the problem of RequestBody being read twice.Normally the stream of HttpServletRequest can only be read once

/**
 * Custom Http request wrapper class to resolve the problem that RequestBody can only read once
 *
 * @create 2017-12-11 17:18
 */
public class HttpRequestTwiceReadingWrapper extends HttpServletRequestWrapper {

    private byte[] requestBody = null;

    public HttpRequestTwiceReadingWrapper(HttpServletRequest request) {

        super(request);

        //Cache request body
        try {
            requestBody = StreamUtils.copyToByteArray(request.getInputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Rewrite getInputStream()
     */
    @Override
    public ServletInputStream getInputStream() throws IOException {
        if (requestBody == null) {
            requestBody = new byte[0];
        }
        InputStream bis = new ByteArrayInputStream(requestBody);

        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return true;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }

            @Override
            public int read() throws IOException {
                return bis.read();
            }
        };
    }

    /**
     * Rewrite getReader()
     */
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

}

The second tool class is the class outside Spring that gets the beans inside the container.

/**
 * Spring Container Tool Class for Outside Containers Objects to Get Bean s Inside Containers
 *
 * @create 2017-12-12 14:19
 */
 @Component
public class SpringBeanInstanceAccessor implements BeanFactoryAware {

    //@Autowired does not support static property injection and can only be implemented in the form of a specified interface
    private static BeanFactory factory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        factory = beanFactory;
    }

    /**
     * Gets a Bean with the specified name
     *
     * @param beanName
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> Object getBean(String beanName, Class<T> clazz) {
        return factory.getBean(beanName, clazz);
    }

    /**
     * Gets a Bean of the specified type
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> Object getBean(Class<T> clazz) {
        return factory.getBean(clazz);
    }
}

The last one is Filter, which scans the Controller under the specified package to generate a collection of URL s, obtains the parameters within RequestBody, combines the keys specified by Spring's Mapper query parameters, combines the keys+","+Referer+","+RequestBody, generates the token by adding salt to MD5, and then validates the token within HttpHeader.

/**
 * Request License Verification Filter
 * [token]And [timestamp] are passed in by HttpHeader
 *
 * @create 2017-12-08 11:26
 */
public class CrosRequestPermitCheckingFilter implements Filter {

    private static final Logger LOGGER = LoggerFactory.getLogger(CrosRequestPermitCheckingFilter.class);

    private static final String RESOURCE_PATTERN = "**/*.class";

    private final List<TypeFilter> includeFilters = new LinkedList<TypeFilter>();
    private final List<TypeFilter> excludeFilters = new LinkedList<TypeFilter>();

    private static final String APPLICATION_NAMESPACE = "";
    private static final Integer PERMIT_VALIDITY_IN_MINUTE = 5;
    private static final List<String> EXPOSED_CONTROLLER_PACKAGES = Arrays.asList("com.bob.mvc.controller");

    private Set<String> exposedRequestUriSet = new LinkedHashSet<String>();

    /**
     * Initialize, scan the open Controller package, and generate an open collection of URL s
     *
     * @param filterConfig
     * @throws ServletException
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        includeFilters.add(new AnnotationTypeFilter(RequestMapping.class, false));
        List<String> controllers = new ArrayList<String>();
        try {
            for (String pkg : EXPOSED_CONTROLLER_PACKAGES) {
                controllers.addAll(this.findCandidateControllers(pkg));
            }
            if (controllers.isEmpty()) {
                if (LOGGER.isWarnEnabled()) {
                    LOGGER.warn("Scan specified package{}No compliant openings were found Controller class", EXPOSED_CONTROLLER_PACKAGES.toString());
                }
                return;
            }
            generateExposedURL(this.transformToClass(controllers), APPLICATION_NAMESPACE);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Scan Specification Controller package,Discovery Open URL:{}", exposedRequestUriSet.toString());
            }
        } catch (Exception e) {
            LOGGER.error("Scan Open Controller An exception occurred", e);
            return;
        }

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = new HttpRequestTwiceReadingWrapper((HttpServletRequest)servletRequest);
        String path = request.getRequestURI();
        if (path.endsWith("/")) {
            path = path.substring(0, path.length() - 1);
        }
        if (!exposedRequestUriSet.contains(path)) {
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }
        try {
            processCrosRequestPermitCkecking(request);
        } catch (IllegalArgumentException | IllegalStateException e) {
            writeResult(servletResponse, e.getMessage());
        }
        filterChain.doFilter(request, servletResponse);
    }

    /**
     * Processing cross-domain requests for license validation
     *
     * @param request
     * @throws Exception
     */
    private void processCrosRequestPermitCkecking(HttpServletRequest request) throws IOException {
        String timestamp = request.getHeader("timestamp");
        Assert.hasText(timestamp, "Cross-domain request not specified[timestamp]");
        Assert.state(isNumber(timestamp), "[timestamp]Not a valid timestamp");
        Assert.state(Long.valueOf(timestamp) >= System.currentTimeMillis() - 1000 * 60 * PERMIT_VALIDITY_IN_MINUTE, "Cross Domain Request License Expired");
        String referer = request.getHeader("Referer");
        String requestBody = getRequestBodyInString(request);
        String appcode = getTargetProperty(requestBody, "appcode");
        Assert.hasText(appcode, "Cross-domain Request[appcode]Non-existent");
        String campusId = getTargetProperty(requestBody, "campusId");
        Assert.isTrue(isNumber(campusId), "Cross-domain Request[campusId]Incorrect");
        String key = selectKey(appcode, campusId);
        Assert.notNull(key, "Cross-domain Request[appcode]and[campusId]Corresponding key Non-existent");
        Assert.state(verify(key + "," + referer + "," + requestBody, request.getHeader("token")), "Cross-domain Request[token]Mismatch with parameters");
    }

    /**
     * Write error results
     *
     * @param servletResponse
     * @param result
     * @throws IOException
     */
    private void writeResult(ServletResponse servletResponse, String result) throws IOException {
        servletResponse.getOutputStream().write(result.getBytes("UTF-8"));
    }

    /**
     * Returns''if there is no data in RequestBody
     *
     * @param request
     * @return
     * @throws IOException
     */
    private String getRequestBodyInString(HttpServletRequest request) throws IOException {
        InputStream is = request.getInputStream();
        byte[] bytes = new byte[2048];
        int length = is.read(bytes);
        return length < 0 ? "" : new String(Arrays.copyOf(bytes, length), "UTF-8");
    }

    /**
     * Gets the value of the specified property from the json string
     *
     * @param json
     * @param property
     * @return
     */
    private String getTargetProperty(String json, String property) {
        if (StringUtils.isEmpty(json)) {
            return null;
        }
        String[] fragments = json.split(",");
        String value = null;
        for (String fragment : fragments) {
            if (fragment.contains(property)) {
                value = fragment.substring(fragment.indexOf(":") + 1).trim();
                if (value.contains("}")) {
                    value = value.substring(0, value.indexOf("}")).trim();
                }
                if (value.contains("\"")) {
                    value = value.substring(1, value.length() - 1).trim();
                }
                break;
            }
        }
        return value;
    }

    /**
     * Check if the salt is the same as the text
     *
     * @param password
     * @param md5
     * @return
     */
    private boolean verify(String password, String md5) {
        if (StringUtils.isEmpty(password) || StringUtils.isEmpty(md5)) {
            return false;
        }
        char[] cs1 = new char[32];
        char[] cs2 = new char[16];
        for (int i = 0; i < 48; i += 3) {
            cs1[i / 3 * 2] = md5.charAt(i);
            cs1[i / 3 * 2 + 1] = md5.charAt(i + 2);
            cs2[i / 3] = md5.charAt(i + 1);
        }
        return new String(cs1).equals(md5Hex(password + new String(cs2)));
    }

    /**
     * Gets an MD5 summary as a hexadecimal string
     */
    private String md5Hex(String src) {
        try {
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            byte[] bs = md5.digest(src.getBytes());
            return new String(new Hex().encode(bs));
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Is the string a number
     *
     * @param value
     * @return
     */
    private boolean isNumber(String value) {
        char[] chars = ((String)value).toCharArray();
        for (char c : chars) {
            if (!Character.isDigit(c)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Get the required Controller name
     * @ComponentScanThis is to use these codes to scan the package and then filter through the TypeFilter what you want
     * @ComponentScanAn AnnotationTypeFilter(Component.class, false) type filter was added to the scan
     *
     * @param basePackage
     * @return
     * @throws IOException
     */
    private List<String> findCandidateControllers(String basePackage) throws IOException {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Start scanning packages[" + basePackage + "]All classes under");
        }
        List<String> controllers = new ArrayList<String>();
        String packageSearchPath = CLASSPATH_ALL_URL_PREFIX + replaceDotByDelimiter(basePackage) + '/' + RESOURCE_PATTERN;
        ResourceLoader resourceLoader = new DefaultResourceLoader();
        MetadataReaderFactory readerFactory = new SimpleMetadataReaderFactory(resourceLoader);
        Resource[] resources = ResourcePatternUtils.getResourcePatternResolver(resourceLoader).getResources(packageSearchPath);
        for (Resource resource : resources) {
            MetadataReader reader = readerFactory.getMetadataReader(resource);
            if (isCandidateController(reader, readerFactory)) {
                controllers.add(reader.getClassMetadata().getClassName());
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Scan to open as required Controller class:[" + controllers.get(controllers.size() - 1) + "]");
                }
            }
        }
        return controllers;
    }

    /**
     * Get the class that identifies @RequestMapping through TypeFilter
     *
     * @param reader
     * @param readerFactory
     * @return
     * @throws IOException
     */
    protected boolean isCandidateController(MetadataReader reader, MetadataReaderFactory readerFactory) throws IOException {
        for (TypeFilter tf : this.excludeFilters) {
            if (tf.match(reader, readerFactory)) {
                return false;
            }
        }
        for (TypeFilter tf : this.includeFilters) {
            if (tf.match(reader, readerFactory)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Convert Class Name to Class Object
     *
     * @param classNames
     * @return
     * @throws ClassNotFoundException
     */
    private List<Class<?>> transformToClass(List<String> classNames) throws ClassNotFoundException {
        List<Class<?>> classes = new ArrayList<Class<?>>(classNames.size());
        for (String className : classNames) {
            classes.add(ClassUtils.forName(className, this.getClass().getClassLoader()));
        }
        return classes;
    }

    /**
     * Replace'. 'with'/' in the package path
     *
     * @param path
     * @return
     */
    private String replaceDotByDelimiter(String path) {
        return StringUtils.replace(path, ".", "/");
    }

    /**
     * Introspective Controllers, generating an open collection of URL s
     *
     * @param controllers
     * @param prefix
     */
    private void generateExposedURL(List<Class<?>> controllers, String prefix) {
        for (Class<?> controller : controllers) {
            String[] classMappings = controller.getAnnotation(RequestMapping.class).value();
            ReflectionUtils.doWithMethods(controller,
                (method) -> {
                    String[] methodMappings = method.getAnnotation(RequestMapping.class).value();
                    exposedRequestUriSet.add(prefix + transformMappings(classMappings) + transformMappings(methodMappings));
                },
                (method) -> method.isAnnotationPresent(RequestMapping.class)
            );
        }
    }

    /**
     * Get the Mapper in the Spring container from the tool class and query the knife key of the database
     *
     * @param appcode
     * @param campusId
     * @return
     */
    public String selectKey(String appcode, String campusId) {
        CampusMapper campusMapper = (CampusMapper )SpringBeanInstanceAccessor.getBean(CampusMapper .class);
        return bankUserMapper.selectKey(Integer.valueOf(campusId));
    }

    /**
     * If {@linkplain RequestMapping#value()} on a method or class is not specified, use''instead
     * value()Only a single value is supported
     *
     * @param mappings
     * @return
     */
    private String transformMappings(String[] mappings) {
        return ObjectUtils.isEmpty(mappings) ? "" : mappings[0];
    }

    @Override
    public void destroy() {

    }

}

Tags: JSON Spring Fragment Database

Posted on Thu, 14 May 2020 12:39:31 -0400 by jcanker