Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
flow-server/src/main/java/com/vaadin/flow/server/connect/VaadinConnectController.java
### 代码内容：
package com.vaadin.flow.server.connect;

import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jackson.JacksonProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.vaadin.flow.function.DeploymentConfiguration;
import com.vaadin.flow.server.VaadinService;
import com.vaadin.flow.server.VaadinServletRequest;
import com.vaadin.flow.server.VaadinServletService;
import com.vaadin.flow.server.connect.auth.VaadinConnectAccessChecker;
import com.vaadin.flow.server.connect.exception.EndpointException;
import com.vaadin.flow.server.connect.exception.EndpointValidationException;
import com.vaadin.flow.server.connect.exception.EndpointValidationException.ValidationErrorData;
import com.vaadin.flow.server.startup.ServletDeployer.StubServletConfig;

/**
 * The controller that is responsible for processing Vaadin Connect requests.
 * Each class that is annotated with {@link Endpoint} gets its public methods
 * exposed so that those can be triggered by a correct POST request, including
 * the methods inherited from the other classes, excluding {@link Object} class
 * ones. Other methods (non-public) are not considered by the controller.
 *

 * For example, if a class with name {@code TestClass} that has the only public
 * method {@code testMethod} was annotated with the annotation, it can be called
 * via {@literal http://${base_url}/testclass/testmethod} POST call, where
 * {@literal ${base_url}} is the application base url, configured by the user.
 * Class name and method name case in the request URL does not matter, but if
 * the method has parameters, the request body should contain a valid JSON with
 * all parameters in the same order as they are declared in the method. The
 * parameter types should also correspond for the request to be successful.
 */
@RestController
@Import({ VaadinConnectControllerConfiguration.class,
        VaadinEndpointProperties.class })
public class VaadinConnectController {
    /**
     * A qualifier to override the request and response default json mapper.
     *
     * @see #VaadinConnectController(ObjectMapper, VaadinConnectAccessChecker,
     *      EndpointNameChecker, ExplicitNullableTypeChecker,
     *      ApplicationContext, ServletContext)
     */
    public static final String VAADIN_ENDPOINT_MAPPER_BEAN_QUALIFIER =
            "vaadinEndpointMapper";

    final Map<String, VaadinEndpointData> vaadinEndpoints = new HashMap<>();

    private final ObjectMapper vaadinEndpointMapper;
    private final VaadinConnectAccessChecker accessChecker;
    private final Validator validator = Validation
            .buildDefaultValidatorFactory().getValidator();
    private final ExplicitNullableTypeChecker explicitNullableTypeChecker;

    /**
     * A constructor used to initialize the controller.
     *
     * @param vaadinEndpointMapper
     *            optional bean to override the default {@link ObjectMapper}
     *            that is used for serializing and deserializing request and
     *            response bodies Use
     *            {@link VaadinConnectController#VAADIN_ENDPOINT_MAPPER_BEAN_QUALIFIER}
     *            qualifier to override the mapper.
     * @param accessChecker
     *            the ACL checker to verify the endpoint method access
     *            permissions
     * @param endpointNameChecker
     *            the endpoint name checker to verify custom Vaadin Connect
     *            endpoint names
     * @param explicitNullableTypeChecker
     *            the method parameter and return value type checker to verify
     *            that null values are explicit
     * @param context
     *            Spring context to extract beans annotated with {@link Endpoint}
     *            from
     * @param servletContext
     *            The servlet context for the controller.
     */
    public VaadinConnectController(
            @Autowired(required = false) @Qualifier(VAADIN_ENDPOINT_MAPPER_BEAN_QUALIFIER) ObjectMapper vaadinEndpointMapper,
            VaadinConnectAccessChecker accessChecker,
            EndpointNameChecker endpointNameChecker,
            ExplicitNullableTypeChecker explicitNullableTypeChecker,
            ApplicationContext context,
            ServletContext servletContext) {
        this.vaadinEndpointMapper = vaadinEndpointMapper != null
                ? vaadinEndpointMapper
                : getDefaultObjectMapper(context);
        this.accessChecker = accessChecker;
        this.explicitNullableTypeChecker = explicitNullableTypeChecker;

        context.getBeansWithAnnotation(Endpoint.class).forEach(
                (name, endpointBean) -> validateEndpointBean(endpointNameChecker,
                        name, endpointBean));

        DeploymentConfiguration cfg = createDeploymentConfiguration(servletContext);
        if (cfg != null) {
            accessChecker.enableCsrf(cfg.isXsrfProtectionEnabled());
        }
    }

    private DeploymentConfiguration createDeploymentConfiguration(
            ServletContext ctx) {
        if (ctx.getServletRegistrations().isEmpty()) {
            return null;
        }
        ServletRegistration reg = ctx.getServletRegistrations().entrySet()
                .iterator().next().getValue();
        return StubServletConfig.createDeploymentConfiguration(ctx, reg,
                VaadinConnectController.class);
    }

    private static Logger getLogger() {
        return LoggerFactory.getLogger(VaadinConnectController.class);
    }

    void validateEndpointBean(EndpointNameChecker endpointNameChecker,
            String name, Object endpointBean) {
        // Check the bean type instead of the implementation type in
        // case of e.g. proxies
        Class<?> beanType = ClassUtils.getUserClass(endpointBean.getClass());

        String endpointName = Optional
                .ofNullable(beanType.getAnnotation(Endpoint.class))
                .map(Endpoint::value).filter(value -> !value.isEmpty())
                .orElse(beanType.getSimpleName());
        if (endpointName.isEmpty()) {
            throw new IllegalStateException(String.format(
                    "A bean with name '%s' and type '%s' is annotated with '%s' "
                            + "annotation but is an anonymous class hence has no name. ",
                    name, beanType, Endpoint.class)
                    + String.format(
                            "Either modify the bean declaration so that it is not an "
                                    + "anonymous class or specify an endpoint " +
                                    "name in the '%s' annotation",
                            Endpoint.class));
        }
        String validationError = endpointNameChecker.check(endpointName);
        if (validationError != null) {
            throw new IllegalStateException(
                    String.format("Endpoint name '%s' is invalid, reason: '%s'",
                            endpointName, validationError));
        }

        vaadinEndpoints.put(endpointName.toLowerCase(Locale.ENGLISH),
                new VaadinEndpointData(endpointBean, beanType.getMethods()));
    }

    private ObjectMapper getDefaultObjectMapper(ApplicationContext context) {
        try {
            ObjectMapper objectMapper = context.getBean(ObjectMapper.class);
            JacksonProperties jacksonProperties = context
                    .getBean(JacksonProperties.class);
            if (jacksonProperties.getVisibility().isEmpty()) {
                objectMapper.setVisibility(PropertyAccessor.ALL,
                        JsonAutoDetect.Visibility.ANY);
            }
            return objectMapper;
        } catch (Exception e) {
            throw new IllegalStateException(String.format(
                    "Auto configured jackson object mapper is not found."
                            + "Please define your own object mapper with '@Qualifier(%s)' or "
                            + "make sure that the auto configured jackson object mapper is available.",
                    VAADIN_ENDPOINT_MAPPER_BEAN_QUALIFIER), e);
        }
    }

    /**
     * Captures and processes the Vaadin Connect requests.
     *

     * Matches the endpoint name and a method name with the corresponding Java
     * class and a public method in the class. Extracts parameters from a
     * request body if the Java method requires any and applies in the same
     * order. After the method call, serializes the Java method execution result
     * and sends it back.
     *

     * If an issue occurs during the request processing, an error response is
     * returned instead of the serialized Java method return value.
     *
     * @param endpointName
     *            the name of an endpoint to address the calls to, not case
     *            sensitive
     * @param methodName
     *            the method name to execute on an endpoint, not case sensitive
     * @param body
     *            optional request body, that should be specified if the method
     *            called has parameters
     * @param request
     *            the current request which triggers the endpoint call
     * @return execution result as a JSON string or an error message string
     */
    @PostMapping(path = "/{endpoint}/{method}", produces =
            MediaType.APPLICATION_JSON_UTF8_VALUE)
    public ResponseEntity<String> serveEndpoint(
            @PathVariable("endpoint") String endpointName,
            @PathVariable("method") String methodName,
            @RequestBody(required = false) ObjectNode body,
            HttpServletRequest request) {
        getLogger().debug("Endpoint: {}, method: {}, request body: {}",
                endpointName, methodName, body);

        VaadinEndpointData vaadinEndpointData = vaadinEndpoints
                .get(endpointName.toLowerCase(Locale.ENGLISH));
        if (vaadinEndpointData == null) {
            getLogger().debug("Endpoint '{}' not found", endpointName);
            return ResponseEntity.notFound().build();
        }

        Method methodToInvoke = vaadinEndpointData
                .getMethod(methodName.toLowerCase(Locale.ENGLISH)).orElse(null);
        if (methodToInvoke == null) {
            getLogger().debug("Method '{}' not found in endpoint '{}'",
                    methodName, endpointName);
            return ResponseEntity.notFound().build();
        }

        try {

            // Put a VaadinRequest in the instances object so as the request is
            // available in the end-point method
            VaadinServletService service = (VaadinServletService)VaadinService.getCurrent();
            if (service != null) {
                service.setCurrentInstances(new VaadinServletRequest(request, service), null);
            }

            return invokeVaadinEndpointMethod(endpointName, methodName,
                    methodToInvoke, body, vaadinEndpointData, request);
        } catch (JsonProcessingException e) {
            String errorMessage = String.format(
                    "Failed to serialize endpoint '%s' method '%s' response. "
                            + "Double check method's return type or specify a custom mapper bean with qualifier '%s'",
                    endpointName, methodName,
                    VAADIN_ENDPOINT_MAPPER_BEAN_QUALIFIER);
            getLogger().error(errorMessage, e);
            try {
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                        .body(createResponseErrorObject(errorMessage));
            } catch (JsonProcessingException unexpected) {
                throw new IllegalStateException(String.format(
                        "Unexpected: Failed to serialize a plain Java string '%s' into a JSON. "
                                + "Double check the provided mapper's configuration.",
                        errorMessage), unexpected);
            }
        }
    }

    private ResponseEntity<String> invokeVaadinEndpointMethod(String endpointName,
            String methodName, Method methodToInvoke, ObjectNode body,
            VaadinEndpointData vaadinEndpointData, HttpServletRequest request)
            throws JsonProcessingException {
        String checkError = accessChecker.check(methodToInvoke, request);
        if (checkError != null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                    .body(createResponseErrorObject(String.format(
                            "Endpoint '%s' method '%s' request cannot be accessed, reason: '%s'",
                            endpointName, methodName, checkError)));
        }

        Map<String, JsonNode> requestParameters = getRequestParameters(body);
        Parameter[] javaParameters = methodToInvoke.getParameters();
        if (javaParameters.length != requestParameters.size()) {
            return ResponseEntity.badRequest()
                    .body(createResponseErrorObject(String.format(
                            "Incorrect number of parameters for endpoint '%s' method '%s', "
                                    + "expected: %s, got: %s",
                            endpointName, methodName, javaParameters.length,
                            requestParameters.size())));
        }

        Object[] vaadinEndpointParameters;
        try {
            vaadinEndpointParameters = getVaadinEndpointParameters(
                    requestParameters, javaParameters, methodName, endpointName);
        } catch (EndpointValidationException e) {
            getLogger().debug(
                    "Endpoint '{}' method '{}' received invalid response",
                    endpointName, methodName, e);
            return ResponseEntity.badRequest().body(vaadinEndpointMapper
                    .writeValueAsString(e.getSerializationData()));
        }

        Set<ConstraintViolation<Object>> methodParameterConstraintViolations = validator
                .forExecutables()
                .validateParameters(vaadinEndpointData.getEndpointObject(),
                        methodToInvoke, vaadinEndpointParameters);
        if (!methodParameterConstraintViolations.isEmpty()) {
            return ResponseEntity.badRequest().body(vaadinEndpointMapper
                    .writeValueAsString(new EndpointValidationException(
                            String.format(
                                    "Validation error in endpoint '%s' method '%s'",
                                    endpointName, methodName),
                            createMethodValidationErrors(
                                    methodParameterConstraintViolations))
                                            .getSerializationData()));
        }

        Object returnValue;
        try {
            returnValue = methodToInvoke.invoke(
                    vaadinEndpointData.getEndpointObject(),
                    vaadinEndpointParameters);
        } catch (IllegalArgumentException e) {
            String errorMessage = String.format(
                    "Received incorrect arguments for endpoint '%s' method '%s'. "
                            + "Expected parameter types (and their order) are: '[%s]'",
                    endpointName, methodName,
                    listMethodParameterTypes(javaParameters));
            getLogger().debug(errorMessage, e);
            return ResponseEntity.badRequest()
                    .body(createResponseErrorObject(errorMessage));
        } catch (IllegalAccessException e) {
            String errorMessage = String.format(
                    "Endpoint '%s' method '%s' access failure", endpointName,
                    methodName);
            getLogger().error(errorMessage, e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(createResponseErrorObject(errorMessage));
        } catch (InvocationTargetException e) {
            return handleMethodExecutionError(endpointName, methodName, e);
        }

        String implicitNullError = this.explicitNullableTypeChecker
                .checkValueForAnnotatedElement(returnValue, methodToInvoke);
        if (implicitNullError != null) {
            EndpointException returnValueException = new EndpointException(
                    String.format(
                            "Unexpected return value in endpoint '%s' method '%s'. %s",
                            endpointName, methodName, implicitNullError));

            getLogger().error(returnValueException.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(vaadinEndpointMapper.writeValueAsString(
                            returnValueException.getSerializationData()));
        }

        Set<ConstraintViolation<Object>> returnValueConstraintViolations = validator
                .forExecutables()
                .validateReturnValue(vaadinEndpointData.getEndpointObject(),
                        methodToInvoke, returnValue);
        if (!returnValueConstraintViolations.isEmpty()) {
            getLogger().error(
                    "Endpoint '{}' method '{}' had returned a value that has validation errors: '{}', this might cause bugs on the client side. Fix the method implementation.",
                    endpointName, methodName, returnValueConstraintViolations);
        }
        return ResponseEntity
                .ok(vaadinEndpointMapper.writeValueAsString(returnValue));
    }

    private ResponseEntity<String> handleMethodExecutionError(
            String endpointName, String methodName, InvocationTargetException e)
            throws JsonProcessingException {
        if (EndpointException.class
                .isAssignableFrom(e.getCause().getClass())) {
            EndpointException endpointException = ((EndpointException) e
                    .getCause());
            getLogger().debug("Endpoint '{}' method '{}' aborted the execution",
                    endpointName, methodName, endpointException);
            return ResponseEntity.badRequest()
                    .body(vaadinEndpointMapper.writeValueAsString(
                            endpointException.getSerializationData()));
        } else {
            String errorMessage = String.format(
                    "Endpoint '%s' method '%s' execution failure", endpointName,
                    methodName);
            getLogger().error(errorMessage, e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(createResponseErrorObject(errorMessage));
        }
    }

    private String createResponseErrorObject(String errorMessage)
            throws JsonProcessingException {
        return vaadinEndpointMapper.writeValueAsString(Collections.singletonMap(
                EndpointException.ERROR_MESSAGE_FIELD, errorMessage));
    }

    private String listMethodParameterTypes(Parameter[] javaParameters) {
        return Stream.of(javaParameters).map(Parameter::getType)
                .map(Class::getName).collect(Collectors.joining(", "));
    }

    private Object[] getVaadinEndpointParameters(
            Map<String, JsonNode> requestParameters, Parameter[] javaParameters,
            String methodName, String endpointName) {
        Object[] endpointParameters = new Object[javaParameters.length];
        String[] parameterNames = new String[requestParameters.size()];
        requestParameters.keySet().toArray(parameterNames);
        Map<String, String> errorParams = new HashMap<>();
        Set<ConstraintViolation<Object>> constraintViolations = new LinkedHashSet<>();

        for (int i = 0; i < javaParameters.length; i++) {
            Type expectedType = javaParameters[i].getParameterizedType();
            try {
                Object parameter = vaadinEndpointMapper
                        .readerFor(vaadinEndpointMapper.getTypeFactory()
                                .constructType(expectedType))
                        .readValue(requestParameters.get(parameterNames[i]));

                endpointParameters[i] = parameter;

                if (parameter != null) {
                    constraintViolations.addAll(validator.validate(parameter));
                }
            } catch (IOException e) {
                String typeName = expectedType.getTypeName();
                getLogger().error(
                        "Unable to deserialize an endpoint '{}' method '{}' "
                                + "parameter '{}' with type '{}'",
                        endpointName, methodName, parameterNames[i], typeName,
                        e);
                errorParams.put(parameterNames[i], typeName);
            }
        }

        if (errorParams.isEmpty() && constraintViolations.isEmpty()) {
            return endpointParameters;
        }
        throw getInvalidEndpointParametersException(methodName, endpointName,
                errorParams, constraintViolations);
    }

    private EndpointValidationException getInvalidEndpointParametersException(
            String methodName, String endpointName,
            Map<String, String> deserializationErrors,
            Set<ConstraintViolation<Object>> constraintViolations) {
        List<ValidationErrorData> validationErrorData = new ArrayList<>(
                deserializationErrors.size() + constraintViolations.size());

        for (Map.Entry<String, String> deserializationError : deserializationErrors
                .entrySet()) {
            String message = String.format(
                    "Unable to deserialize an endpoint method parameter into type '%s'",
                    deserializationError.getValue());
            validationErrorData.add(new ValidationErrorData(message,
                    deserializationError.getKey()));
        }

        validationErrorData
                .addAll(createBeanValidationErrors(constraintViolations));

        String message = String.format(
                "Validation error in endpoint '%s' method '%s'", endpointName,
                methodName);
        return new EndpointValidationException(message,
                validationErrorData);
    }

    private List<ValidationErrorData> createBeanValidationErrors(
            Collection<ConstraintViolation<Object>> beanConstraintViolations) {
        return beanConstraintViolations.stream().map(
                constraintViolation -> new ValidationErrorData(String.format(
                        "Object of type '%s' has invalid property '%s' with value '%s', validation error: '%s'",
                        constraintViolation.getRootBeanClass(),
                        constraintViolation.getPropertyPath().toString(),
                        constraintViolation.getInvalidValue(),
                        constraintViolation.getMessage()),
                        constraintViolation.getPropertyPath().toString()))
                .collect(Collectors.toList());
    }

    private List<ValidationErrorData> createMethodValidationErrors(
            Collection<ConstraintViolation<Object>> methodConstraintViolations) {
        return methodConstraintViolations.stream().map(constraintViolation -> {
            String parameterPath = constraintViolation.getPropertyPath()
                    .toString();
            return new ValidationErrorData(String.format(
                    "Method '%s' of the object '%s' received invalid parameter '%s' with value '%s', validation error: '%s'",
                    parameterPath.split("\\.")[0],
                    constraintViolation.getRootBeanClass(), parameterPath,
                    constraintViolation.getInvalidValue(),
                    constraintViolation.getMessage()), parameterPath);
        }).collect(Collectors.toList());
    }

    private Map<String, JsonNode> getRequestParameters(ObjectNode body) {
        Map<String, JsonNode> parametersData = new LinkedHashMap<>();
        if (body != null) {
            body.fields().forEachRemaining(entry -> parametersData
                    .put(entry.getKey(), entry.getValue()));
        }
        return parametersData;
    }

    static class VaadinEndpointData {
        final Map<String, Method> methods = new HashMap<>();
        private final Object vaadinEndpointObject;

        private VaadinEndpointData(Object vaadinEndpointObject,
                Method... endpointMethods) {
            this.vaadinEndpointObject = vaadinEndpointObject;
            Stream.of(endpointMethods)
                    .filter(method -> method.getDeclaringClass() != Object.class
                            && !method.isBridge())
                    .forEach(method -> methods.put(
                            method.getName().toLowerCase(Locale.ENGLISH),
                            method));
        }

        private Optional<Method> getMethod(String methodName) {
            return Optional.ofNullable(methods.get(methodName));
        }

        private Object getEndpointObject() {
            return vaadinEndpointObject;
        }
    }
}

## 内容2
### 文件路径：
flow-server/src/main/java/com/vaadin/flow/server/connect/VaadinConnectControllerConfiguration.java
### 代码内容：
package com.vaadin.flow.server.connect;

import java.lang.reflect.Method;

import com.vaadin.flow.server.frontend.FrontendUtils;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import com.vaadin.flow.server.connect.auth.VaadinConnectAccessChecker;

/**
 * A configuration class for customizing the {@link VaadinConnectController}
 * class.
 */
@Configuration
public class VaadinConnectControllerConfiguration {
    private final VaadinEndpointProperties vaadinEndpointProperties;

    /**
     * Initializes the connect configuration.
     *
     * @param vaadinEndpointProperties
     *            Vaadin Connect properties
     */
    public VaadinConnectControllerConfiguration(
            VaadinEndpointProperties vaadinEndpointProperties) {
        this.vaadinEndpointProperties = vaadinEndpointProperties;
    }

    /**
     * Registers {@link VaadinConnectController} to use
     * {@link VaadinEndpointProperties#getVaadinEndpointPrefix()} as a prefix
     * for all Vaadin Connect endpoints.
     *
     * @return updated configuration for {@link VaadinConnectController}
     */
    @Bean
    public WebMvcRegistrations webMvcRegistrationsHandlerMapping() {
        return new WebMvcRegistrations() {

            @Override
            public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
                return new RequestMappingHandlerMapping() {

                    @Override
                    protected void registerHandlerMethod(Object handler,
                            Method method, RequestMappingInfo mapping) {
                        // If Spring context initialization fails here with a
                        // stack overflow in a project that also has the
                        // `vaadin-spring` dependency, make sure that the Spring
                        // version in `flow-server` and in `vaadin-spring` is
                        // the same.

                        if (VaadinConnectController.class
                                .equals(method.getDeclaringClass())) {
                            mapping = prependConnectPrefixUrl(mapping);
                        }

                        super.registerHandlerMethod(handler, method, mapping);
                    }
                };
            }
        };
    }

    /**
     * Prepends the Connect prefix URL from the Vaadin Connect properties to
     * the {@code pattern} of a {@link RequestMappingInfo} object, and returns
     * the updated mapping as a new object (not modifying the given
     * {@param mapping} parameter).
     *
     * @return a new mapping with the Connect prefix URL prepended to the
     *         mapping pattern
     */
    private RequestMappingInfo prependConnectPrefixUrl(
            RequestMappingInfo mapping) {
        String customEnpointPrefixName = FrontendUtils.getCustomEndpointPrefix();
        PatternsRequestCondition connectEndpointPattern =
                new PatternsRequestCondition(
                        customEnpointPrefixName != null ? customEnpointPrefixName :
                vaadinEndpointProperties.getVaadinEndpointPrefix())
                        .combine(mapping.getPatternsCondition());

        return new RequestMappingInfo(mapping.getName(), connectEndpointPattern,
                mapping.getMethodsCondition(), mapping.getParamsCondition(),
                mapping.getHeadersCondition(), mapping.getConsumesCondition(),
                mapping.getProducesCondition(), mapping.getCustomCondition());
    }

    /**
     * Registers an endpoint name checker responsible for validating the
     * endpoint names.
     *
     * @return the endpoint name checker
     */
    @Bean
    public EndpointNameChecker endpointNameChecker() {
        return new EndpointNameChecker();
    }

    /**
     * Registers a default {@link VaadinConnectAccessChecker} bean instance.
     *
     * @return the default Vaadin Connect access checker bean
     */
    @Bean
    public VaadinConnectAccessChecker accessChecker() {
        return new VaadinConnectAccessChecker();
    }

    /**
     * Registers a {@link ExplicitNullableTypeChecker} bean instance.
     *
     * @return the explicit nullable type checker
     */
    @Bean
    public ExplicitNullableTypeChecker typeChecker() {
        return new ExplicitNullableTypeChecker();
    }
}

Output:
【漏洞类型】依赖注入安全问题
【严重性】Medium
【漏洞摘要】原始代码中，`VaadinConnectController`的构造函数通过`@Autowired(required = false)`注解注入了`ObjectMapper`实例。如果Spring上下文中没有定义`ObjectMapper` bean，将使用默认的`ObjectMapper`实例。由于默认的`ObjectMapper`实例的`JsonAutoDetect.Visibility`设置为`ANY`，这意味着所有字段，包括私有字段，都将被序列化和反序列化。这可能导致意外的数据泄露，特别是当敏感信息存储在私有字段中时。
【关键代码模式】`@Autowired(required = false)`，`ObjectMapper`，`JsonAutoDetect.Visibility.ANY`
【修复方法说明】修复方法通过移除`required = false`，并确保`ObjectMapper` bean在Spring上下文中可用。此外，修复代码在`VaadinConnectControllerConfiguration`中注册了一个新的`ObjectMapper` bean，并设置了合适的`JsonAutoDetect.Visibility`，以避免序列化私有字段。


Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
fusionauth-samlv2.iml
### 代码内容：
<?xml version="1.0" encoding="UTF-8"?>
<module relativePaths="true" type="JAVA_MODULE" version="4">
  <component name="NewModuleRootManager">
    <output url="file://$MODULE_DIR$/build/classes/main" />
    <output-test url="file://$MODULE_DIR$/build/classes/test" />
    <exclude-output />
    <content url="file://$MODULE_DIR$">
      <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
      <sourceFolder url="file://$MODULE_DIR$/src/main/resources" isTestSource="false" />
      <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
      <sourceFolder url="file://$MODULE_DIR$/src/test/resources" isTestSource="true" />
      <excludeFolder url="file://$MODULE_DIR$/.gradle" />
      <excludeFolder url="file://$MODULE_DIR$/build" />
    </content>
    <orderEntry type="inheritedJdk" />
    <orderEntry type="sourceFolder" forTests="false" />
    <orderEntry type="module-library">
      <library>
        <CLASSES>
          <root url="jar://$USER_HOME$/.savant/cache/org/slf4j/slf4j-api/1.7.21/slf4j-api-1.7.21.jar!/" />
        </CLASSES>
        <JAVADOC />
        <SOURCES>
          <root url="jar://$USER_HOME$/.savant/cache/org/slf4j/slf4j-api/1.7.21/slf4j-api-1.7.21-src.jar!/" />
        </SOURCES>
      </library>
    </orderEntry>
    <orderEntry type="module-library">
      <library>
        <CLASSES>
          <root url="jar://$USER_HOME$/.savant/cache/jakarta/xml/bind/jakarta.xml.bind-api/2.3.2/jakarta.xml.bind-api-2.3.2.jar!/" />
        </CLASSES>
        <JAVADOC />
        <SOURCES>
          <root url="jar://$USER_HOME$/.savant/cache/jakarta/xml/bind/jakarta.xml.bind-api/2.3.2/jakarta.xml.bind-api-2.3.2-src.jar!/" />
        </SOURCES>
      </library>
    </orderEntry>
    <orderEntry type="module-library" scope="TEST">
      <library>
        <CLASSES>
          <root url="jar://$USER_HOME$/.savant/cache/org/testng/testng/6.14.3/testng-6.14.3.jar!/" />
        </CLASSES>
        <JAVADOC />
        <SOURCES>
          <root url="jar://$USER_HOME$/.savant/cache/org/testng/testng/6.14.3/testng-6.14.3-src.jar!/" />
        </SOURCES>
      </library>
    </orderEntry>
    <orderEntry type="module-library" scope="TEST">
      <library>
        <CLASSES>
          <root url="jar://$USER_HOME$/.savant/cache/com/beust/jcommander/1.72.0/jcommander-1.72.0.jar!/" />
        </CLASSES>
        <JAVADOC />
        <SOURCES>
          <root url="jar://$USER_HOME$/.savant/cache/com/beust/jcommander/1.72.0/jcommander-1.72.0-src.jar!/" />
        </SOURCES>
      </library>
    </orderEntry>
    <orderEntry type="module-library" scope="TEST">
      <library>
        <CLASSES>
          <root url="jar://$USER_HOME$/.savant/cache/org/apache-extras/beanshell/bsh/2.0.0-beta.6/bsh-2.0.0-beta.6.jar!/" />
        </CLASSES>
        <JAVADOC />
        <SOURCES>
          <root url="jar://$USER_HOME$/.savant/cache/org/apache-extras/beanshell/bsh/2.0.0-beta.6/bsh-2.0.0-beta.6-src.jar!/" />
        </SOURCES>
      </library>
    </orderEntry>
    <orderEntry type="module-library" scope="RUNTIME">
      <library>
        <CLASSES>
          <root url="jar://$USER_HOME$/.savant/cache/org/glassfish/jaxb/jaxb-runtime/2.3.2/jaxb-runtime-2.3.2.jar!/" />
        </CLASSES>
        <JAVADOC />
        <SOURCES>
          <root url="jar://$USER_HOME$/.savant/cache/org/glassfish/jaxb/jaxb-runtime/2.3.2/jaxb-runtime-2.3.2-src.jar!/" />
        </SOURCES>
      </library>
    </orderEntry>
    <orderEntry type="module-library" scope="RUNTIME">
      <library>
        <CLASSES>
          <root url="jar://$USER_HOME$/.savant/cache/com/sun/xml/fastinfoset/FastInfoset/1.2.16/FastInfoset-1.2.16.jar!/" />
        </CLASSES>
        <JAVADOC />
        <SOURCES>
          <root url="jar://$USER_HOME$/.savant/cache/com/sun/xml/fastinfoset/FastInfoset/1.2.16/FastInfoset-1.2.16-src.jar!/" />
        </SOURCES>
      </library>
    </orderEntry>
    <orderEntry type="module-library" scope="RUNTIME">
      <library>
        <CLASSES>
          <root url="jar://$USER_HOME$/.savant/cache/com/sun/istack/istack-commons-runtime/3.0.8/istack-commons-runtime-3.0.8.jar!/" />
        </CLASSES>
        <JAVADOC />
        <SOURCES>
          <root url="jar://$USER_HOME$/.savant/cache/com/sun/istack/istack-commons-runtime/3.0.8/istack-commons-runtime-3.0.8-src.jar!/" />
        </SOURCES>
      </library>
    </orderEntry>
    <orderEntry type="module-library" scope="RUNTIME">
      <library>
        <CLASSES>
          <root url="jar://$USER_HOME$/.savant/cache/jakarta/activation/jakarta.activation-api/1.2.1/jakarta.activation-api-1.2.1.jar!/" />
        </CLASSES>
        <JAVADOC />
        <SOURCES>
          <root url="jar://$USER_HOME$/.savant/cache/jakarta/activation/jakarta.activation-api/1.2.1/jakarta.activation-api-1.2.1-src.jar!/" />
        </SOURCES>
      </library>
    </orderEntry>
    <orderEntry type="module-library" scope="RUNTIME">
      <library>
        <CLASSES>
          <root url="jar://$USER_HOME$/.savant/cache/org/glassfish/jaxb/txw2/2.3.2/txw2-2.3.2.jar!/" />
        </CLASSES>
        <JAVADOC />
        <SOURCES>
          <root url="jar://$USER_HOME$/.savant/cache/org/glassfish/jaxb/txw2/2.3.2/txw2-2.3.2-src.jar!/" />
        </SOURCES>
      </library>
    </orderEntry>
    <orderEntry type="module-library" scope="RUNTIME">
      <library>
        <CLASSES>
          <root url="jar://$USER_HOME$/.savant/cache/org/jvnet/staxex/stax-ex/1.8.1/stax-ex-1.8.1.jar!/" />
        </CLASSES>
        <JAVADOC />
        <SOURCES>
          <root url="jar://$USER_HOME$/.savant/cache/org/jvnet/staxex/stax-ex/1.8.1/stax-ex-1.8.1-src.jar!/" />
        </SOURCES>
      </library>
    </orderEntry>
  </component>
</module>

## 内容2
### 文件路径：
src/main/java/io/fusionauth/samlv2/service/DefaultSAMLv2Service.java
### 代码内容：
package io.fusionauth.samlv2.service;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.crypto.KeySelector;
import javax.xml.crypto.MarshalException;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.DigestMethod;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignedInfo;
import javax.xml.crypto.dsig.Transform;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureException;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;

import com.sun.org.apache.xerces.internal.jaxp.datatype.XMLGregorianCalendarImpl;
import io.fusionauth.samlv2.domain.Algorithm;
import io.fusionauth.samlv2.domain.AuthenticationRequest;
import io.fusionauth.samlv2.domain.AuthenticationResponse;
import io.fusionauth.samlv2.domain.Binding;
import io.fusionauth.samlv2.domain.Conditions;
import io.fusionauth.samlv2.domain.ConfirmationMethod;
import io.fusionauth.samlv2.domain.MetaData;
import io.fusionauth.samlv2.domain.MetaData.IDPMetaData;
import io.fusionauth.samlv2.domain.MetaData.SPMetaData;
import io.fusionauth.samlv2.domain.NameID;
import io.fusionauth.samlv2.domain.NameIDFormat;
import io.fusionauth.samlv2.domain.ResponseStatus;
import io.fusionauth.samlv2.domain.SAMLException;
import io.fusionauth.samlv2.domain.SignatureLocation;
import io.fusionauth.samlv2.domain.SignatureNotFoundException;
import io.fusionauth.samlv2.domain.Subject;
import io.fusionauth.samlv2.domain.SubjectConfirmation;
import io.fusionauth.samlv2.domain.jaxb.oasis.assertion.AssertionType;
import io.fusionauth.samlv2.domain.jaxb.oasis.assertion.AttributeStatementType;
import io.fusionauth.samlv2.domain.jaxb.oasis.assertion.AttributeType;
import io.fusionauth.samlv2.domain.jaxb.oasis.assertion.AudienceRestrictionType;
import io.fusionauth.samlv2.domain.jaxb.oasis.assertion.AuthnContextType;
import io.fusionauth.samlv2.domain.jaxb.oasis.assertion.AuthnStatementType;
import io.fusionauth.samlv2.domain.jaxb.oasis.assertion.ConditionAbstractType;
import io.fusionauth.samlv2.domain.jaxb.oasis.assertion.ConditionsType;
import io.fusionauth.samlv2.domain.jaxb.oasis.assertion.EncryptedElementType;
import io.fusionauth.samlv2.domain.jaxb.oasis.assertion.NameIDType;
import io.fusionauth.samlv2.domain.jaxb.oasis.assertion.StatementAbstractType;
import io.fusionauth.samlv2.domain.jaxb.oasis.assertion.SubjectConfirmationDataType;
import io.fusionauth.samlv2.domain.jaxb.oasis.assertion.SubjectConfirmationType;
import io.fusionauth.samlv2.domain.jaxb.oasis.assertion.SubjectType;
import io.fusionauth.samlv2.domain.jaxb.oasis.metadata.EndpointType;
import io.fusionauth.samlv2.domain.jaxb.oasis.metadata.EntityDescriptorType;
import io.fusionauth.samlv2.domain.jaxb.oasis.metadata.IDPSSODescriptorType;
import io.fusionauth.samlv2.domain.jaxb.oasis.metadata.IndexedEndpointType;
import io.fusionauth.samlv2.domain.jaxb.oasis.metadata.KeyDescriptorType;
import io.fusionauth.samlv2.domain.jaxb.oasis.metadata.KeyTypes;
import io.fusionauth.samlv2.domain.jaxb.oasis.metadata.RoleDescriptorType;
import io.fusionauth.samlv2.domain.jaxb.oasis.metadata.SPSSODescriptorType;
import io.fusionauth.samlv2.domain.jaxb.oasis.metadata.SSODescriptorType;
import io.fusionauth.samlv2.domain.jaxb.oasis.protocol.AuthnRequestType;
import io.fusionauth.samlv2.domain.jaxb.oasis.protocol.NameIDPolicyType;
import io.fusionauth.samlv2.domain.jaxb.oasis.protocol.ObjectFactory;
import io.fusionauth.samlv2.domain.jaxb.oasis.protocol.ResponseType;
import io.fusionauth.samlv2.domain.jaxb.oasis.protocol.StatusCodeType;
import io.fusionauth.samlv2.domain.jaxb.oasis.protocol.StatusType;
import io.fusionauth.samlv2.domain.jaxb.w3c.xmldsig.KeyInfoType;
import io.fusionauth.samlv2.domain.jaxb.w3c.xmldsig.X509DataType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * Default implementation of the SAML service.
 *
 * @author Brian Pontarelli
 */
public class DefaultSAMLv2Service implements SAMLv2Service {
  private static final io.fusionauth.samlv2.domain.jaxb.oasis.assertion.ObjectFactory ASSERTION_OBJECT_FACTORY = new io.fusionauth.samlv2.domain.jaxb.oasis.assertion.ObjectFactory();

  private static final io.fusionauth.samlv2.domain.jaxb.w3c.xmldsig.ObjectFactory DSIG_OBJECT_FACTORY = new io.fusionauth.samlv2.domain.jaxb.w3c.xmldsig.ObjectFactory();

  private static final io.fusionauth.samlv2.domain.jaxb.oasis.metadata.ObjectFactory METADATA_OBJECT_FACTORY = new io.fusionauth.samlv2.domain.jaxb.oasis.metadata.ObjectFactory();

  private static final ObjectFactory PROTOCOL_OBJECT_FACTORY = new ObjectFactory();

  private static final Logger logger = LoggerFactory.getLogger(DefaultSAMLv2Service.class);

  static {
    String ignoreLineBreaks = System.getProperty("com.sun.org.apache.xml.internal.security.ignoreLineBreaks");
    if (!Boolean.parseBoolean(ignoreLineBreaks)) {
      throw new IllegalStateException("When the fusionauth-samlv2 jar is included in the classpath, you must set the following system property:\n" +
          "-Dcom.sun.org.apache.xml.internal.security.ignoreLineBreaks=true");
    }
  }

  @Override
  public String buildAuthnResponse(AuthenticationResponse response, boolean sign, PrivateKey privateKey,
                                   X509Certificate certificate, Algorithm algorithm, String xmlSignatureC14nMethod,
                                   SignatureLocation signatureOption) throws SAMLException {
    ResponseType jaxbResponse = new ResponseType();

    // Status (element)
    StatusType status = new StatusType();
    status.setStatusCode(new StatusCodeType());
    status.getStatusCode().setValue(response.status.code.toSAMLFormat());
    status.setStatusMessage(response.status.message);
    jaxbResponse.setStatus(status);

    // Id (attribute), issuer (element) and version (attribute)
    jaxbResponse.setID(response.id);
    jaxbResponse.setIssuer(new NameIDType());
    jaxbResponse.getIssuer().setValue(response.issuer);
    jaxbResponse.setVersion(response.version);

    // Response to (attribute)
    jaxbResponse.setInResponseTo(response.inResponseTo);

    // Instant (attribute)
    jaxbResponse.setIssueInstant(toXMLGregorianCalendar(response.issueInstant));

    // Destination (Attribute)
    jaxbResponse.setDestination(response.destination);

    // The main assertion (element)
    AssertionType assertionType = new AssertionType();
    if (response.assertion != null && response.status.code == ResponseStatus.Success) {
      ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);
      String id = "_" + UUID.randomUUID().toString();
      assertionType.setID(id);
      assertionType.setIssuer(jaxbResponse.getIssuer());
      assertionType.setIssueInstant(toXMLGregorianCalendar(now));
      assertionType.setVersion(response.version);

      // Subject (element)
      if (response.assertion.subject != null) {
        SubjectType subjectType = new SubjectType();

        // NameId (element)
        if (response.assertion.subject.nameID != null) {
          NameIDType nameIdType = new NameIDType();
          nameIdType.setValue(response.assertion.subject.nameID.id);
          nameIdType.setFormat(response.assertion.subject.nameID.format.toSAMLFormat());
          subjectType.getContent().add(ASSERTION_OBJECT_FACTORY.createNameID(nameIdType));
        }

        // Subject confirmation (element)
        if (response.assertion.subject.subjectConfirmation != null) {
          SubjectConfirmationDataType dataType = new SubjectConfirmationDataType();
          dataType.setInResponseTo(response.assertion.subject.subjectConfirmation.inResponseTo);
          dataType.setNotBefore(toXMLGregorianCalendar(response.assertion.subject.subjectConfirmation.notBefore));
          dataType.setNotOnOrAfter(toXMLGregorianCalendar(response.assertion.subject.subjectConfirmation.notOnOrAfter));
          dataType.setRecipient(response.assertion.subject.subjectConfirmation.recipient);
          SubjectConfirmationType subjectConfirmationType = new SubjectConfirmationType();
          subjectConfirmationType.setSubjectConfirmationData(dataType);
          if (response.assertion.subject.subjectConfirmation.method != null) {
            subjectConfirmationType.setMethod(response.assertion.subject.subjectConfirmation.method.toSAMLFormat());
          }
          subjectType.getContent().add(ASSERTION_OBJECT_FACTORY.createSubjectConfirmation(subjectConfirmationType));
        }

        // Add the subject
        assertionType.setSubject(subjectType);
      }

      // Conditions (element)
      if (response.assertion.conditions != null) {
        ConditionsType conditionsType = new ConditionsType();
        conditionsType.setNotBefore(toXMLGregorianCalendar(response.assertion.conditions.notBefore));
        conditionsType.setNotOnOrAfter(toXMLGregorianCalendar(response.assertion.conditions.notOnOrAfter));
        assertionType.setConditions(conditionsType);

        // Audiences (element)
        if (response.assertion.conditions.audiences.size() > 0) {
          AudienceRestrictionType audienceRestrictionType = new AudienceRestrictionType();
          audienceRestrictionType.getAudience().addAll(response.assertion.conditions.audiences);
          conditionsType.getConditionOrAudienceRestrictionOrOneTimeUse().add(audienceRestrictionType);
        }
      }

      // Attributes (elements)
      AttributeStatementType attributeStatementType = new AttributeStatementType();
      response.assertion.attributes.forEach((k, v) -> {
        AttributeType attributeType = new AttributeType();
        attributeType.setName(k);
        attributeType.getAttributeValue().addAll(v);
        attributeStatementType.getAttributeOrEncryptedAttribute().add(attributeType);
      });
      assertionType.getStatementOrAuthnStatementOrAuthzDecisionStatement().add(attributeStatementType);

      // AuthnStatement (element)
      AuthnStatementType authnStatement = new AuthnStatementType();
      authnStatement.setAuthnInstant(toXMLGregorianCalendar(now));
      authnStatement.setAuthnContext(new AuthnContextType());
      authnStatement.getAuthnContext().getContent().add(ASSERTION_OBJECT_FACTORY.createAuthnContextClassRef("urn:oasis:names:tc:SAML:2.0:ac:classes:Password"));
      authnStatement.setSessionIndex(response.sessionIndex);
      authnStatement.setSessionNotOnOrAfter(toXMLGregorianCalendar(response.sessionExpiry));
      assertionType.getStatementOrAuthnStatementOrAuthzDecisionStatement().add(authnStatement);

      // Add the assertion (element - order doesn't matter)
      jaxbResponse.getAssertionOrEncryptedAssertion().add(assertionType);
    }

    Document document = marshallToDocument(PROTOCOL_OBJECT_FACTORY.createResponse(jaxbResponse), ResponseType.class);
    try {
      // If successful, sign the assertion if requested, otherwise, sign the root
      Element toSign;
      Node insertBefore = null;
      // The 'Signature' must come directly after the 'Issuer' element.
      if (response.status.code == ResponseStatus.Success && signatureOption == SignatureLocation.Assertion) {
        toSign = (Element) document.getElementsByTagName("Assertion").item(0);
        // Issuer is the only required element. See schema for AssertionType in section 2.3.3 of SAML Core.
        // - The next sibling of the 'Issuer' may be null, this will cause the Signature to be inserted as the last element
        //   of the assertion which is what we want.
        Node issuer = toSign.getElementsByTagName("Issuer").item(0);
        insertBefore = issuer.getNextSibling();
      } else {
        toSign = document.getDocumentElement();
        // The only required element in the StatusResponseType is Status. See Section 3.2.2 in SAML Core.
        // The children will be a sequence that must exist in the order of 'Issuer', 'Signature', 'Extensions', and then 'Status'
        // - If the first element is 'Issuer', then the next sibling will be used for 'insertBefore'.
        // - If the first element is NOT 'Issuer', it MUST be 'Extensions' or 'Status', and thus is the 'insertBefore' node.
        NodeList children = toSign.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
          Node n = children.item(i);
          if (n instanceof Element) {
            insertBefore = n.getLocalName().equals("Issuer") ? n.getNextSibling() : n;
            break;
          }
        }
      }

      String xml = signXML(privateKey, certificate, algorithm, xmlSignatureC14nMethod, document, toSign, insertBefore);
      return Base64.getEncoder().encodeToString(xml.getBytes(StandardCharsets.UTF_8));
    } catch (Exception e) {
      throw new SAMLException("Unable to sign XML SAML response", e);
    }
  }

  @Override
  public String buildInvalidTestingPostAuthnRequest(AuthenticationRequest request, boolean sign, PrivateKey privateKey,
                                                    X509Certificate certificate, Algorithm algorithm,
                                                    String xmlSignatureC14nMethod) throws SAMLException {
    AuthnRequestType authnRequest = toAuthnRequest(request, "bad");
    return buildPostAuthnRequest(authnRequest, sign, privateKey, certificate, algorithm, xmlSignatureC14nMethod);
  }

  @Override
  public String buildInvalidTestingRedirectAuthnRequest(AuthenticationRequest request, String relayState, boolean sign,
                                                        PrivateKey key, Algorithm algorithm) throws SAMLException {
    AuthnRequestType authnRequest = toAuthnRequest(request, "bad");
    return buildRedirectAuthnRequest(authnRequest, relayState, sign, key, algorithm);
  }

  @Override
  public String buildMetadataResponse(MetaData metaData) throws SAMLException {
    EntityDescriptorType root = new EntityDescriptorType();
    root.setID("_" + metaData.id);
    root.setEntityID(metaData.entityId);

    if (metaData.idp != null) {
      IDPSSODescriptorType idp = new IDPSSODescriptorType();
      idp.getProtocolSupportEnumeration().add("urn:oasis:names:tc:SAML:2.0:protocol");

      metaData.idp.redirectBindingSignInEndpoints.forEach(endpoint -> {
        EndpointType signIn = new EndpointType();
        signIn.setBinding(Binding.HTTP_Redirect.toSAMLFormat());
        signIn.setLocation(endpoint);
        idp.getSingleSignOnService().add(signIn);
      });

      metaData.idp.postBindingSignInEndpoints.forEach(endpoint -> {
        EndpointType signIn = new EndpointType();
        signIn.setBinding(Binding.HTTP_POST.toSAMLFormat());
        signIn.setLocation(endpoint);
        idp.getSingleSignOnService().add(signIn);
      });

      metaData.idp.redirectBindingLogoutEndpoints.forEach(endpoint -> {
        EndpointType logOut = new EndpointType();
        logOut.setBinding(Binding.HTTP_Redirect.toSAMLFormat());
        logOut.setLocation(endpoint);
        idp.getSingleLogoutService().add(logOut);
      });

      metaData.idp.postBindingLogoutEndpoints.forEach(endpoint -> {
        EndpointType logOut = new EndpointType();
        logOut.setBinding(Binding.HTTP_POST.toSAMLFormat());
        logOut.setLocation(endpoint);
        idp.getSingleLogoutService().add(logOut);
      });

      // Add certificates
      addKeyDescriptors(idp, metaData.idp.certificates);

      root.getRoleDescriptorOrIDPSSODescriptorOrSPSSODescriptor().add(idp);
    }

    if (metaData.sp != null) {
      SPSSODescriptorType sp = new SPSSODescriptorType();
      sp.getProtocolSupportEnumeration().add("urn:oasis:names:tc:SAML:2.0:protocol");
      sp.setAuthnRequestsSigned(metaData.sp.authnRequestsSigned);
      sp.setWantAssertionsSigned(metaData.sp.wantAssertionsSigned);

      if (metaData.sp.acsEndpoint != null) {
        IndexedEndpointType acs = new IndexedEndpointType();
        acs.setBinding(Binding.HTTP_POST.toSAMLFormat());
        acs.setLocation(metaData.sp.acsEndpoint);
        sp.getAssertionConsumerService().add(acs);
      }

      if (metaData.sp.nameIDFormat != null) {
        sp.getNameIDFormat().add(metaData.sp.nameIDFormat.toSAMLFormat());
      }

      // Add certificates
      addKeyDescriptors(sp, metaData.sp.certificates);

      root.getRoleDescriptorOrIDPSSODescriptorOrSPSSODescriptor().add(sp);
    }

    // Convert to String
    byte[] bytes = marshallToBytes(METADATA_OBJECT_FACTORY.createEntityDescriptor(root), EntityDescriptorType.class);
    return new String(bytes, StandardCharsets.UTF_8);
  }

  @Override
  public String buildPostAuthnRequest(AuthenticationRequest request, boolean sign, PrivateKey privateKey,
                                      X509Certificate certificate, Algorithm algorithm, String xmlSignatureC14nMethod)
      throws SAMLException {
    AuthnRequestType authnRequest = toAuthnRequest(request, "2.0");
    return buildPostAuthnRequest(authnRequest, sign, privateKey, certificate, algorithm, xmlSignatureC14nMethod);
  }

  @Override
  public String buildRedirectAuthnRequest(AuthenticationRequest request, String relayState, boolean sign,
                                          PrivateKey key,
                                          Algorithm algorithm)
      throws SAMLException {
    AuthnRequestType authnRequest = toAuthnRequest(request, "2.0");
    return buildRedirectAuthnRequest(authnRequest, relayState, sign, key, algorithm);
  }

  @Override
  public MetaData parseMetaData(String metaDataXML) throws SAMLException {
    Document document = parseFromBytes(metaDataXML.getBytes(StandardCharsets.UTF_8));
    EntityDescriptorType root = unmarshallFromDocument(document, EntityDescriptorType.class);
    MetaData metaData = new MetaData();
    metaData.id = root.getID();
    metaData.entityId = root.getEntityID();

    List<RoleDescriptorType> roles = root.getRoleDescriptorOrIDPSSODescriptorOrSPSSODescriptor();
    Optional<RoleDescriptorType> idpDescriptor = roles.stream()
                                                      .filter(r -> r instanceof IDPSSODescriptorType)
                                                      .findFirst();
    if (idpDescriptor.isPresent()) {
      IDPSSODescriptorType idp = (IDPSSODescriptorType) idpDescriptor.get();

      // Extract the URLs
      metaData.idp = new IDPMetaData();

      // Extract SignIn URLs
      for (EndpointType endpoint : idp.getSingleSignOnService()) {
        if (Binding.HTTP_Redirect.toSAMLFormat().equals(endpoint.getBinding())) {
          metaData.idp.redirectBindingSignInEndpoints.add(endpoint.getLocation());
        } else if (Binding.HTTP_POST.toSAMLFormat().equals(endpoint.getBinding())) {
          metaData.idp.postBindingSignInEndpoints.add(endpoint.getLocation());
        }
      }

      // Extract Logout URLs
      for (EndpointType endpoint : idp.getSingleLogoutService()) {
        if (Binding.HTTP_Redirect.toSAMLFormat().equals(endpoint.getBinding())) {
          metaData.idp.redirectBindingLogoutEndpoints.add(endpoint.getLocation());
        } else if (Binding.HTTP_POST.toSAMLFormat().equals(endpoint.getBinding())) {
          metaData.idp.postBindingLogoutEndpoints.add(endpoint.getLocation());
        }
      }

      // Extract the signing certificates
      try {
        metaData.idp.certificates = idp.getKeyDescriptor()
                                       .stream()
                                       .filter(kd -> kd.getUse() == KeyTypes.SIGNING)
                                       .map(this::toCertificate)
                                       .filter(Objects::nonNull)
                                       .collect(Collectors.toList());
      } catch (IllegalArgumentException e) {
        // toPublicKey might throw this and we want to translate it back to a known exception
        throw new SAMLException(e.getCause());
      }
    }

    Optional<RoleDescriptorType> spDescriptor = roles.stream()
                                                     .filter(r -> r instanceof SPSSODescriptorType)
                                                     .findFirst();

    if (spDescriptor.isPresent()) {
      SPSSODescriptorType sp = (SPSSODescriptorType) spDescriptor.get();
      metaData.sp = new SPMetaData();
      metaData.sp.acsEndpoint = sp.getAssertionConsumerService().size() > 0 ? sp.getAssertionConsumerService().get(0).getLocation() : null;
      try {
        metaData.sp.nameIDFormat = sp.getNameIDFormat().size() > 0 ? NameIDFormat.fromSAMLFormat(sp.getNameIDFormat().get(0)) : null;
      } catch (Exception e) {
        // fromSAMLFormat may throw an exception if the Name ID Format is not defined by our NameIDFormat enum.
        throw new SAMLException(e.getCause());
      }
    }

    return metaData;
  }

  @Override
  public AuthenticationRequest parseRequestPostBinding(String encodedRequest,
                                                       Function<AuthenticationRequest, PostBindingSignatureHelper> signatureHelperFunction)
      throws SAMLException {
    byte[] xml = Base64.getMimeDecoder().decode(encodedRequest);
    AuthnRequestParseResult result = parseRequest(xml);
    PostBindingSignatureHelper signatureHelper = signatureHelperFunction.apply(result.request);
    if (signatureHelper.verifySignature()) {
      verifySignature(result.document, signatureHelper.keySelector());
    }

    return result.request;
  }

  @Override
  public AuthenticationRequest parseRequestRedirectBinding(String encodedRequest, String relayState,
                                                           Function<AuthenticationRequest, RedirectBindingSignatureHelper> signatureHelperFunction)
      throws SAMLException {
    AuthnRequestParseResult result = parseRequest(decodeAndInflate(encodedRequest));
    RedirectBindingSignatureHelper signatureHelper = signatureHelperFunction.apply(result.request);
    if (signatureHelper.verifySignature()) {
      if (signatureHelper.signature() == null || signatureHelper.publicKey() == null || signatureHelper.algorithm() == null) {
        throw new SignatureNotFoundException("You must specify a signature, key and algorithm if you want to verify the SAML request signature");
      }

      try {
        String parameters = "SAMLRequest=" + URLEncoder.encode(encodedRequest, "UTF-8");
        if (relayState != null) {
          parameters += "&RelayState=" + URLEncoder.encode(relayState, "UTF-8");
        }
        parameters += "&SigAlg=" + URLEncoder.encode(signatureHelper.algorithm().uri, "UTF-8");

        Signature sig = Signature.getInstance(signatureHelper.algorithm().name);
        sig.initVerify(signatureHelper.publicKey());
        sig.update(parameters.getBytes(StandardCharsets.UTF_8));
        if (!sig.verify(Base64.getMimeDecoder().decode(signatureHelper.signature()))) {
          throw new SAMLException("Invalid SAML v2.0 operation. The signature is invalid.");
        }
      } catch (GeneralSecurityException | UnsupportedEncodingException e) {
        throw new SAMLException("Unable to verify signature", e);
      }
    }

    return result.request;
  }

  @Override
  public AuthenticationResponse parseResponse(String encodedResponse, boolean verifySignature, KeySelector keySelector)
      throws SAMLException {

    AuthenticationResponse response = new AuthenticationResponse();
    byte[] decodedResponse = Base64.getMimeDecoder().decode(encodedResponse);
    response.rawResponse = new String(decodedResponse, StandardCharsets.UTF_8);

    Document document = parseFromBytes(decodedResponse);
    if (verifySignature) {
      verifySignature(document, keySelector);
    }

    ResponseType jaxbResponse = unmarshallFromDocument(document, ResponseType.class);
    response.status.code = ResponseStatus.fromSAMLFormat(jaxbResponse.getStatus().getStatusCode().getValue());
    response.id = jaxbResponse.getID();
    response.inResponseTo = jaxbResponse.getInResponseTo();
    response.issuer = jaxbResponse.getIssuer() != null ? jaxbResponse.getIssuer().getValue() : null;
    response.issueInstant = toZonedDateTime(jaxbResponse.getIssueInstant());
    response.destination = jaxbResponse.getDestination();
    response.version = jaxbResponse.getVersion();

    List<Object> assertions = jaxbResponse.getAssertionOrEncryptedAssertion();
    for (Object assertion : assertions) {
      if (assertion instanceof EncryptedElementType) {
        logger.warn("SAML response contained encrypted attribute. It was ignored.");
        continue;
      }

      AssertionType assertionType = (AssertionType) assertion;

      // Handle the subject
      SubjectType subject = assertionType.getSubject();
      if (subject != null) {
        response.assertion.subject = new Subject();

        List<JAXBElement<?>> elements = subject.getContent();
        for (JAXBElement<?> element : elements) {
          Class<?> type = element.getDeclaredType();
          if (type == NameIDType.class) {
            if (response.assertion.subject.nameID != null) {
              logger.warn("SAML response contained multiple NameID elements. Only the first one was used.");
              continue;
            }

            // Extract the name
            response.assertion.subject.nameID = parseNameId((NameIDType) element.getValue());
          } else if (type == SubjectConfirmationType.class) {
            // Extract the confirmation
            response.assertion.subject.subjectConfirmation = parseConfirmation((SubjectConfirmationType) element.getValue());
          } else if (type == EncryptedElementType.class) {
            throw new SAMLException("This library currently doesn't handle encrypted assertions");
          }
        }
      }

      // Handle conditions to pull out audience restriction
      ConditionsType conditionsType = assertionType.getConditions();
      if (conditionsType != null) {
        response.assertion.conditions = new Conditions();
        response.assertion.conditions.notBefore = convertToZonedDateTime(conditionsType.getNotBefore());
        response.assertion.conditions.notOnOrAfter = convertToZonedDateTime(conditionsType.getNotOnOrAfter());

        List<ConditionAbstractType> conditionAbstractTypes = conditionsType.getConditionOrAudienceRestrictionOrOneTimeUse();
        // Only handling the AudienceRestriction.
        // - Optional additional conditions include OneTimeUse and ProxyRestriction.  See section 2.5.1 in the SAML v2 core spec,
        //   the way these additional conditions are described, I see no use for them.
        //   - OneTimeUse specifics are in section 2.5.1.5
        //   - ProxyRestriction specifics are in section 2.6.1.6
        //   http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
        for (ConditionAbstractType conditionAbstractType : conditionAbstractTypes) {
          if (conditionAbstractType instanceof AudienceRestrictionType) {
            AudienceRestrictionType restrictionType = (AudienceRestrictionType) conditionAbstractType;
            response.assertion.conditions.audiences.addAll(restrictionType.getAudience());
          }
        }
      }

      // Handle the attributes
      List<StatementAbstractType> statements = assertionType.getStatementOrAuthnStatementOrAuthzDecisionStatement();
      for (StatementAbstractType statement : statements) {
        if (statement instanceof AttributeStatementType) {
          AttributeStatementType attributeStatementType = (AttributeStatementType) statement;
          List<Object> attributeObjects = attributeStatementType.getAttributeOrEncryptedAttribute();
          for (Object attributeObject : attributeObjects) {
            if (attributeObject instanceof AttributeType) {
              AttributeType attributeType = (AttributeType) attributeObject;
              String name = attributeType.getName();
              List<Object> attributeValues = attributeType.getAttributeValue();
              List<String> values = attributeValues.stream().map(this::attributeToString).collect(Collectors.toList());
              response.assertion.attributes.computeIfAbsent(name, k -> new ArrayList<>()).addAll(values);
            } else {
              throw new SAMLException("This library currently doesn't support encrypted attributes");
            }
          }
        }
      }
    }

    return response;
  }

  private void addKeyDescriptors(SSODescriptorType descriptor, List<Certificate> certificates) {
    certificates.forEach(cert -> {
      KeyDescriptorType key = new KeyDescriptorType();
      key.setUse(KeyTypes.SIGNING);
      KeyInfoType info = new KeyInfoType();
      key.setKeyInfo(info);
      X509DataType data = new X509DataType();
      info.getContent().add(DSIG_OBJECT_FACTORY.createX509Data(data));

      try {
        JAXBElement<byte[]> certElement = DSIG_OBJECT_FACTORY.createX509DataTypeX509Certificate(cert.getEncoded());
        data.getX509IssuerSerialOrX509SKIOrX509SubjectName().add(certElement);
        descriptor.getKeyDescriptor().add(key);
      } catch (Exception e) {
        // Rethrow
        throw new IllegalArgumentException(e);
      }
    });
  }

  private String attributeToString(Object attribute) {
    if (attribute == null) {
      return null;
    }

    if (attribute instanceof Number) {
      return attribute.toString();
    } else if (attribute instanceof String) {
      return (String) attribute;
    } else if (attribute instanceof Element) {
      return ((Element) attribute).getTextContent();
    } else {
      logger.warn("This library currently doesn't handle attributes of type [" + attribute.getClass() + "]");
    }

    return null;
  }

  private String buildPostAuthnRequest(AuthnRequestType authnRequest, boolean sign, PrivateKey privateKey,
                                       X509Certificate certificate,
                                       Algorithm algorithm, String xmlSignatureC14nMethod) throws SAMLException {
    Document document = marshallToDocument(PROTOCOL_OBJECT_FACTORY.createAuthnRequest(authnRequest), AuthnRequestType.class);
    try {
      Element toSign = document.getDocumentElement();
      String xml;
      if (sign) {
        xml = signXML(privateKey, certificate, algorithm, xmlSignatureC14nMethod, document, toSign, null);
      } else {
        xml = marshallToString(document);
      }

      return Base64.getEncoder().encodeToString(xml.getBytes(StandardCharsets.UTF_8));
    } catch (Exception e) {
      throw new SAMLException("Unable to sign XML SAML response", e);
    }
  }

  private String buildRedirectAuthnRequest(AuthnRequestType authnRequest, String relayState, boolean sign,
                                           PrivateKey key, Algorithm algorithm) throws SAMLException {
    try {
      byte[] xml = marshallToBytes(PROTOCOL_OBJECT_FACTORY.createAuthnRequest(authnRequest), AuthnRequestType.class);
      String encodedResult = deflateAndEncode(xml);
      String parameters = "SAMLRequest=" + URLEncoder.encode(encodedResult, "UTF-8");
      if (relayState != null) {
        parameters += "&RelayState=" + URLEncoder.encode(relayState, "UTF-8");
      }

      if (sign && key != null && algorithm != null) {
        Signature signature;
        parameters += "&SigAlg=" + URLEncoder.encode(algorithm.uri, "UTF-8");
        signature = Signature.getInstance(algorithm.name);
        signature.initSign(key);
        signature.update(parameters.getBytes(StandardCharsets.UTF_8));

        String signatureParameter = Base64.getEncoder().encodeToString(signature.sign());
        parameters += "&Signature=" + URLEncoder.encode(signatureParameter, "UTF-8");
      }

      return parameters;
    } catch (Exception e) {
      // Not possible but freak out
      throw new SAMLException(e);
    }
  }

  private ZonedDateTime convertToZonedDateTime(XMLGregorianCalendar cal) {
    return cal != null ? cal.toGregorianCalendar().toZonedDateTime() : null;
  }

  private byte[] decodeAndInflate(String encodedRequest) throws SAMLException {
    byte[] bytes = Base64.getMimeDecoder().decode(encodedRequest);
    Inflater inflater = new Inflater(true);
    inflater.setInput(bytes);
    inflater.finished();

    try {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      byte[] result = new byte[bytes.length];
      while (!inflater.finished()) {
        int length = inflater.inflate(result);
        if (length > 0) {
          baos.write(result, 0, length);
        }
      }

      return baos.toByteArray();
    } catch (DataFormatException e) {
      throw new SAMLException("Invalid AuthnRequest. Inflating the bytes failed.", e);
    }
  }

  private String deflateAndEncode(byte[] result) {
    Deflater deflater = new Deflater(Deflater.DEFLATED, true);
    deflater.setInput(result);
    deflater.finish();
    byte[] deflatedResult = new byte[result.length];
    int length = deflater.deflate(deflatedResult);
    deflater.end();
    byte[] src = Arrays.copyOf(deflatedResult, length);
    return Base64.getEncoder().encodeToString(src);
  }

  private void fixIDs(Element element) {
    NamedNodeMap attributes = element.getAttributes();
    for (int i = 0; i < attributes.getLength(); i++) {
      Attr attribute = (Attr) attributes.item(i);
      if (attribute.getLocalName().equalsIgnoreCase("id")) {
        element.setIdAttributeNode(attribute, true);
      }
    }

    NodeList children = element.getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      Node child = children.item(i);
      if (child.getNodeType() == Node.ELEMENT_NODE) {
        fixIDs((Element) child);
      }
    }
  }

  private <T> byte[] marshallToBytes(JAXBElement<T> object, Class<T> type) throws SAMLException {
    try {
      JAXBContext context = JAXBContext.newInstance(type);
      Marshaller marshaller = context.createMarshaller();
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      marshaller.marshal(object, baos);
      return baos.toByteArray();
    } catch (JAXBException e) {
      throw new SAMLException("Unable to marshallRequest JAXB SAML object to bytes.", e);
    }
  }

  @SuppressWarnings("SameParameterValue")
  private <T> Document marshallToDocument(JAXBElement<T> object, Class<T> type) throws SAMLException {
    try {
      JAXBContext context = JAXBContext.newInstance(type);
      Marshaller marshaller = context.createMarshaller();
      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
      dbf.setNamespaceAware(true);
      DocumentBuilder db = dbf.newDocumentBuilder();
      Document document = db.newDocument();
      marshaller.marshal(object, document);
      return document;
    } catch (JAXBException | ParserConfigurationException e) {
      throw new SAMLException("Unable to marshallRequest JAXB SAML object to DOM.", e);
    }
  }

  private String marshallToString(Document document) throws TransformerException {
    StringWriter sw = new StringWriter();
    TransformerFactory tf = TransformerFactory.newInstance();
    Transformer transformer = tf.newTransformer();
    transformer.transform(new DOMSource(document), new StreamResult(sw));
    return sw.toString();
  }

  private SubjectConfirmation parseConfirmation(SubjectConfirmationType subjectConfirmationType) {
    SubjectConfirmation subjectConfirmation = new SubjectConfirmation();
    SubjectConfirmationDataType data = subjectConfirmationType.getSubjectConfirmationData();
    if (data != null) {
      subjectConfirmation.address = data.getAddress();
      subjectConfirmation.inResponseTo = data.getInResponseTo();
      subjectConfirmation.notBefore = toZonedDateTime(data.getNotBefore());
      subjectConfirmation.notOnOrAfter = toZonedDateTime(data.getNotOnOrAfter());
      subjectConfirmation.recipient = data.getRecipient();
    }

    subjectConfirmation.method = ConfirmationMethod.fromSAMLFormat(subjectConfirmationType.getMethod());

    return subjectConfirmation;
  }

  private Document parseFromBytes(byte[] bytes) throws SAMLException {
    DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
    documentBuilderFactory.setNamespaceAware(true);
    try {
      DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();
      return builder.parse(new ByteArrayInputStream(bytes));
    } catch (ParserConfigurationException | SAXException | IOException e) {
      throw new SAMLException("Unable to parse SAML v2.0 authentication response", e);
    }
  }

  private NameID parseNameId(NameIDType element) {
    NameID nameId = new NameID();
    nameId.format = NameIDFormat.fromSAMLFormat(element.getFormat());
    nameId.id = element.getValue();
    return nameId;
  }

  private AuthnRequestParseResult parseRequest(byte[] xmlBytes) throws SAMLException {
    String xml = new String(xmlBytes, StandardCharsets.UTF_8);
    if (logger.isDebugEnabled()) {
      logger.debug("SAMLRequest XML is\n{}", xml);
    }

    AuthnRequestParseResult result = new AuthnRequestParseResult();
    result.document = parseFromBytes(xmlBytes);
    result.authnRequest = unmarshallFromDocument(result.document, AuthnRequestType.class);
    result.request = new AuthenticationRequest();
    result.request.xml = xml;
    result.request.id = result.authnRequest.getID();
    result.request.issuer = result.authnRequest.getIssuer().getValue();
    result.request.issueInstant = result.authnRequest.getIssueInstant().toGregorianCalendar().toZonedDateTime();
    NameIDPolicyType nameIdPolicyType = result.authnRequest.getNameIDPolicy();
    if (nameIdPolicyType == null) {
      result.request.nameIdFormat = NameIDFormat.EmailAddress;
    } else {
      result.request.nameIdFormat = NameIDFormat.fromSAMLFormat(nameIdPolicyType.getFormat());
    }
    result.request.version = result.authnRequest.getVersion();
    return result;
  }

  private String signXML(PrivateKey privateKey, X509Certificate certificate, Algorithm algorithm,
                         String xmlSignatureC14nMethod, Document document, Element toSign, Node insertBefore)
      throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, MarshalException, XMLSignatureException, TransformerException {
    // Set the id attribute node. Yucky! Yuck!
    toSign.setIdAttributeNode(toSign.getAttributeNode("ID"), true);

    // If there is an insert before, set it so that the signature is in the place that some IdPs require
    // - If insertBefore is 'null' the signature will be inserted as the last element.
    DOMSignContext dsc = new DOMSignContext(privateKey, toSign);
    dsc.setNextSibling(insertBefore);

    // Sign away
    XMLSignatureFactory factory = XMLSignatureFactory.getInstance("DOM");
    CanonicalizationMethod c14n = factory.newCanonicalizationMethod(xmlSignatureC14nMethod, (C14NMethodParameterSpec) null);
    Reference ref = factory.newReference("#" + toSign.getAttribute("ID"),
        factory.newDigestMethod(DigestMethod.SHA256, null),
        Arrays.asList(factory.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null), c14n),
        null,
        null);
    SignedInfo si = factory.newSignedInfo(c14n,
        factory.newSignatureMethod(algorithm.uri, null),
        Collections.singletonList(ref));
    KeyInfoFactory kif = factory.getKeyInfoFactory();
    X509Data data = kif.newX509Data(Collections.singletonList(certificate));
    KeyInfo ki = kif.newKeyInfo(Collections.singletonList(data));
    XMLSignature signature = factory.newXMLSignature(si, ki);

    signature.sign(dsc);
    return marshallToString(document);
  }

  private AuthnRequestType toAuthnRequest(AuthenticationRequest request, String version) {
    // SAML Web SSO profile requirements (section 4.1.4.1)
    AuthnRequestType authnRequest = new AuthnRequestType();
    authnRequest.setAssertionConsumerServiceURL(request.acsURL);
    authnRequest.setDestination(request.destination);
    authnRequest.setIssuer(new NameIDType());
    authnRequest.getIssuer().setValue(request.issuer);
    authnRequest.setNameIDPolicy(new NameIDPolicyType());
    authnRequest.getNameIDPolicy().setFormat(NameIDFormat.EmailAddress.toSAMLFormat());
    authnRequest.getNameIDPolicy().setAllowCreate(false);
    authnRequest.setID(request.id);
    authnRequest.setVersion(version);
    authnRequest.setIssueInstant(new XMLGregorianCalendarImpl(GregorianCalendar.from(ZonedDateTime.now())));
    return authnRequest;
  }

  private Certificate toCertificate(KeyDescriptorType keyDescriptorType) {
    try {
      List<Object> keyData = keyDescriptorType.getKeyInfo().getContent();
      for (Object keyDatum : keyData) {
        if (keyDatum instanceof JAXBElement<?>) {
          JAXBElement<?> element = (JAXBElement<?>) keyDatum;
          if (element.getDeclaredType() == X509DataType.class) {
            X509DataType cert = (X509DataType) element.getValue();
            List<Object> certData = cert.getX509IssuerSerialOrX509SKIOrX509SubjectName();
            for (Object certDatum : certData) {
              element = (JAXBElement<?>) certDatum;
              if (element.getName().getLocalPart().equals("X509Certificate")) {
                byte[] certBytes = (byte[]) element.getValue();
                CertificateFactory cf = CertificateFactory.getInstance("X.509");
                return cf.generateCertificate(new ByteArrayInputStream(certBytes));
              }
            }
          }
        }
      }

      return null;
    } catch (CertificateException e) {
      throw new IllegalArgumentException(e);
    }
  }

  private XMLGregorianCalendar toXMLGregorianCalendar(ZonedDateTime instant) {
    if (instant == null) {
      return null;
    }

    return new XMLGregorianCalendarImpl(GregorianCalendar.from(instant));
  }

  private ZonedDateTime toZonedDateTime(XMLGregorianCalendar instant) {
    if (instant == null) {
      return null;
    }

    return instant.toGregorianCalendar().toZonedDateTime();
  }

  private <T> T unmarshallFromDocument(Document document, Class<T> type) throws SAMLException {
    try {
      JAXBContext context = JAXBContext.newInstance(type);
      Unmarshaller unmarshaller = context.createUnmarshaller();
      JAXBElement<T> element = unmarshaller.unmarshal(document, type);
      return element.getValue();
    } catch (JAXBException e) {
      throw new SAMLException("Unable to unmarshall SAML response", e);
    }
  }

  private void verifySignature(Document document, KeySelector keySelector) throws SAMLException {
    // Fix the IDs in the entire document per the suggestions at http://stackoverflow.com/questions/17331187/xml-dig-sig-error-after-upgrade-to-java7u25
    fixIDs(document.getDocumentElement());

    NodeList nl = document.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
    if (nl.getLength() == 0) {
      throw new SignatureNotFoundException("Invalid SAML v2.0 operation. The signature is missing from the XML but is required.");
    }

    for (int i = 0; i < nl.getLength(); i++) {
      DOMValidateContext validateContext = new DOMValidateContext(keySelector, nl.item(i));
      XMLSignatureFactory factory = XMLSignatureFactory.getInstance("DOM");
      try {
        XMLSignature signature = factory.unmarshalXMLSignature(validateContext);
        boolean valid = signature.validate(validateContext);
        if (!valid) {
          throw new SAMLException("Invalid SAML v2.0 operation. The signature is invalid.");
        }
      } catch (MarshalException e) {
        throw new SAMLException("Unable to verify XML signature in the SAML v2.0 XML. We couldn't unmarshall the XML Signature element.", e);
      } catch (XMLSignatureException e) {
        throw new SAMLException("Unable to verify XML signature in the SAML v2.0 XML. The signature was unmarshalled but we couldn't validate it. Possible reasons include a key was not provided that was eligible to verify the signature, or an un-expected exception occurred.", e);
      }
    }
  }

  private static class AuthnRequestParseResult {
    public AuthnRequestType authnRequest;

    public Document document;

    public AuthenticationRequest request;
  }
}

Output:
【漏洞类型】XML External Entity (XXE) 漏洞
【严重性】High（高）
【漏洞摘要】这是一个 XML External Entity (XXE) 漏洞。XML 外部实体是一种安全漏洞，攻击者可以通过注入恶意的 XML 实体来执行恶意操作，例如读取本地文件、发起外部网络请求等。在 `parseFromBytes` 方法中，`DocumentBuilderFactory` 未配置为防止 XXE 攻击。例如，`dbf.setExpandEntityReferences(false)` 和 `dbf.setFeature(FEATURE_SECURE_PROCESSING, true)` 这些设置是缺失的，导致潜在的 XXE 漏洞。
【关键代码模式】`parseFromBytes` 方法中的 `DocumentBuilderFactory` 配置。
【修复方法说明】通过在 `SAMLTools` 类中添加 `newDocumentBuilder` 方法，并确保在创建 `DocumentBuilderFactory` 时禁用外部实体引用和启用安全处理功能，解决了 XXE 漏洞。