Remove support for deprecated Java SecurityManager (-> JDK 17 build compatibility)

Includes hard JDK 9+ API dependency in CGLIB ReflectUtils (Lookup.defineClass) and removal of OutputStream spy proxy usage (avoiding invalid Mockito proxy on JDK 17)

Closes gh-26901
This commit is contained in:
Juergen Hoeller
2021-09-15 15:30:25 +02:00
parent 0640da74bc
commit cf2429b0f0
47 changed files with 147 additions and 2078 deletions

View File

@@ -26,9 +26,6 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
@@ -56,11 +53,6 @@ public class ReflectUtils {
private static final ClassLoader defaultLoader = ReflectUtils.class.getClassLoader();
// SPRING PATCH BEGIN
private static final Method privateLookupInMethod;
private static final Method lookupDefineClassMethod;
private static final Method classLoaderDefineClassMethod;
private static final ProtectionDomain PROTECTION_DOMAIN;
@@ -69,63 +61,28 @@ public class ReflectUtils {
private static final List<Method> OBJECT_METHODS = new ArrayList<Method>();
// SPRING PATCH BEGIN
static {
Method privateLookupIn;
Method lookupDefineClass;
Method classLoaderDefineClass;
ProtectionDomain protectionDomain;
Throwable throwable = null;
try {
privateLookupIn = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws Exception {
try {
return MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
}
catch (NoSuchMethodException ex) {
return null;
}
}
});
lookupDefineClass = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws Exception {
try {
return MethodHandles.Lookup.class.getMethod("defineClass", byte[].class);
}
catch (NoSuchMethodException ex) {
return null;
}
}
});
classLoaderDefineClass = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws Exception {
return ClassLoader.class.getDeclaredMethod("defineClass",
classLoaderDefineClass = ClassLoader.class.getDeclaredMethod("defineClass",
String.class, byte[].class, Integer.TYPE, Integer.TYPE, ProtectionDomain.class);
}
});
protectionDomain = getProtectionDomain(ReflectUtils.class);
AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws Exception {
Method[] methods = Object.class.getDeclaredMethods();
for (Method method : methods) {
if ("finalize".equals(method.getName())
|| (method.getModifiers() & (Modifier.FINAL | Modifier.STATIC)) > 0) {
continue;
}
OBJECT_METHODS.add(method);
}
return null;
for (Method method : Object.class.getDeclaredMethods()) {
if ("finalize".equals(method.getName())
|| (method.getModifiers() & (Modifier.FINAL | Modifier.STATIC)) > 0) {
continue;
}
});
OBJECT_METHODS.add(method);
}
}
catch (Throwable t) {
privateLookupIn = null;
lookupDefineClass = null;
classLoaderDefineClass = null;
protectionDomain = null;
throwable = t;
}
privateLookupInMethod = privateLookupIn;
lookupDefineClassMethod = lookupDefineClass;
classLoaderDefineClassMethod = classLoaderDefineClass;
PROTECTION_DOMAIN = protectionDomain;
THROWABLE = throwable;
@@ -160,11 +117,7 @@ public class ReflectUtils {
if (source == null) {
return null;
}
return (ProtectionDomain) AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return source.getProtectionDomain();
}
});
return source.getProtectionDomain();
}
public static Type[] getExceptionTypes(Member member) {
@@ -336,15 +289,7 @@ public class ReflectUtils {
public static Constructor getConstructor(Class type, Class[] parameterTypes) {
try {
Constructor constructor = type.getDeclaredConstructor(parameterTypes);
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
constructor.setAccessible(true);
return null;
});
}
else {
constructor.setAccessible(true);
}
constructor.setAccessible(true);
return constructor;
}
catch (NoSuchMethodException e) {
@@ -501,18 +446,12 @@ public class ReflectUtils {
Class c = null;
// Preferred option: JDK 9+ Lookup.defineClass API if ClassLoader matches
if (contextClass != null && contextClass.getClassLoader() == loader &&
privateLookupInMethod != null && lookupDefineClassMethod != null) {
if (contextClass != null && contextClass.getClassLoader() == loader) {
try {
MethodHandles.Lookup lookup = (MethodHandles.Lookup)
privateLookupInMethod.invoke(null, contextClass, MethodHandles.lookup());
c = (Class) lookupDefineClassMethod.invoke(lookup, b);
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(contextClass, MethodHandles.lookup());
c = lookup.defineClass(b);
}
catch (InvocationTargetException ex) {
Throwable target = ex.getTargetException();
if (target.getClass() != LinkageError.class && target.getClass() != IllegalArgumentException.class) {
throw new CodeGenerationException(target);
}
catch (LinkageError | IllegalArgumentException ex) {
// in case of plain LinkageError (class already defined)
// or IllegalArgumentException (class in different package):
// fall through to traditional ClassLoader.defineClass below
@@ -567,15 +506,10 @@ public class ReflectUtils {
}
// Fallback option: JDK 9+ Lookup.defineClass API even if ClassLoader does not match
if (c == null && contextClass != null && contextClass.getClassLoader() != loader &&
privateLookupInMethod != null && lookupDefineClassMethod != null) {
if (c == null && contextClass != null && contextClass.getClassLoader() != loader) {
try {
MethodHandles.Lookup lookup = (MethodHandles.Lookup)
privateLookupInMethod.invoke(null, contextClass, MethodHandles.lookup());
c = (Class) lookupDefineClassMethod.invoke(lookup, b);
}
catch (InvocationTargetException ex) {
throw new CodeGenerationException(ex.getTargetException());
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(contextClass, MethodHandles.lookup());
c = lookup.defineClass(b);
}
catch (Throwable ex) {
throw new CodeGenerationException(ex);

View File

@@ -16,7 +16,6 @@
package org.springframework.core.env;
import java.security.AccessControlException;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
@@ -60,8 +59,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
* <p>The default is "false", falling back to system environment variable checks if a
* Spring environment property (e.g. a placeholder in a configuration String) isn't
* resolvable otherwise. Consider switching this flag to "true" if you experience
* log warnings from {@code getenv} calls coming from Spring, e.g. on WebSphere
* with strict SecurityManager settings and AccessControlExceptions warnings.
* log warnings from {@code getenv} calls coming from Spring.
* @see #suppressGetenvAccess()
*/
public static final String IGNORE_GETENV_PROPERTY_NAME = "spring.getenv.ignore";
@@ -438,27 +436,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public Map<String, Object> getSystemProperties() {
try {
return (Map) System.getProperties();
}
catch (AccessControlException ex) {
return (Map) new ReadOnlySystemAttributesMap() {
@Override
@Nullable
protected String getSystemAttribute(String attributeName) {
try {
return System.getProperty(attributeName);
}
catch (AccessControlException ex) {
if (logger.isInfoEnabled()) {
logger.info("Caught AccessControlException when accessing system property '" +
attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage());
}
return null;
}
}
};
}
return (Map) System.getProperties();
}
@Override
@@ -467,27 +445,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
if (suppressGetenvAccess()) {
return Collections.emptyMap();
}
try {
return (Map) System.getenv();
}
catch (AccessControlException ex) {
return (Map) new ReadOnlySystemAttributesMap() {
@Override
@Nullable
protected String getSystemAttribute(String attributeName) {
try {
return System.getenv(attributeName);
}
catch (AccessControlException ex) {
if (logger.isInfoEnabled()) {
logger.info("Caught AccessControlException when accessing system environment variable '" +
attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage());
}
return null;
}
}
};
}
return (Map) System.getenv();
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -119,32 +119,20 @@ public interface ConfigurableEnvironment extends Environment, ConfigurableProper
MutablePropertySources getPropertySources();
/**
* Return the value of {@link System#getProperties()} if allowed by the current
* {@link SecurityManager}, otherwise return a map implementation that will attempt
* to access individual keys using calls to {@link System#getProperty(String)}.
* Return the value of {@link System#getProperties()}.
* <p>Note that most {@code Environment} implementations will include this system
* properties map as a default {@link PropertySource} to be searched. Therefore, it is
* recommended that this method not be used directly unless bypassing other property
* sources is expressly intended.
* <p>Calls to {@link Map#get(Object)} on the Map returned will never throw
* {@link IllegalAccessException}; in cases where the SecurityManager forbids access
* to a property, {@code null} will be returned and an INFO-level log message will be
* issued noting the exception.
*/
Map<String, Object> getSystemProperties();
/**
* Return the value of {@link System#getenv()} if allowed by the current
* {@link SecurityManager}, otherwise return a map implementation that will attempt
* to access individual keys using calls to {@link System#getenv(String)}.
* Return the value of {@link System#getenv()}.
* <p>Note that most {@link Environment} implementations will include this system
* environment map as a default {@link PropertySource} to be searched. Therefore, it
* is recommended that this method not be used directly unless bypassing other
* property sources is expressly intended.
* <p>Calls to {@link Map#get(Object)} on the Map returned will never throw
* {@link IllegalAccessException}; in cases where the SecurityManager forbids access
* to a property, {@code null} will be returned and an INFO-level log message will be
* issued noting the exception.
*/
Map<String, Object> getSystemEnvironment();

View File

@@ -1,123 +0,0 @@
/*
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.env;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import org.springframework.lang.Nullable;
/**
* Read-only {@code Map<String, String>} implementation that is backed by system
* properties or environment variables.
*
* <p>Used by {@link AbstractEnvironment} when a {@link SecurityManager} prohibits
* access to {@link System#getProperties()} or {@link System#getenv()}. It is for this
* reason that the implementations of {@link #keySet()}, {@link #entrySet()}, and
* {@link #values()} always return empty even though {@link #get(Object)} may in fact
* return non-null if the current security manager allows access to individual keys.
*
* @author Arjen Poutsma
* @author Chris Beams
* @since 3.0
*/
abstract class ReadOnlySystemAttributesMap implements Map<String, String> {
@Override
public boolean containsKey(Object key) {
return (get(key) != null);
}
/**
* Returns the value to which the specified key is mapped, or {@code null} if this map
* contains no mapping for the key.
* @param key the name of the system attribute to retrieve
* @throws IllegalArgumentException if given key is non-String
*/
@Override
@Nullable
public String get(Object key) {
if (!(key instanceof String)) {
throw new IllegalArgumentException(
"Type of key [" + key.getClass().getName() + "] must be java.lang.String");
}
return getSystemAttribute((String) key);
}
@Override
public boolean isEmpty() {
return false;
}
/**
* Template method that returns the underlying system attribute.
* <p>Implementations typically call {@link System#getProperty(String)} or {@link System#getenv(String)} here.
*/
@Nullable
protected abstract String getSystemAttribute(String attributeName);
// Unsupported
@Override
public int size() {
throw new UnsupportedOperationException();
}
@Override
public String put(String key, String value) {
throw new UnsupportedOperationException();
}
@Override
public boolean containsValue(Object value) {
throw new UnsupportedOperationException();
}
@Override
public String remove(Object key) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@Override
public Set<String> keySet() {
return Collections.emptySet();
}
@Override
public void putAll(Map<? extends String, ? extends String> map) {
throw new UnsupportedOperationException();
}
@Override
public Collection<String> values() {
return Collections.emptySet();
}
@Override
public Set<Entry<String, String>> entrySet() {
return Collections.emptySet();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -122,34 +122,26 @@ public class SystemEnvironmentPropertySource extends MapPropertySource {
@Nullable
private String checkPropertyName(String name) {
// Check name as-is
if (containsKey(name)) {
if (this.source.containsKey(name)) {
return name;
}
// Check name with just dots replaced
String noDotName = name.replace('.', '_');
if (!name.equals(noDotName) && containsKey(noDotName)) {
if (!name.equals(noDotName) && this.source.containsKey(noDotName)) {
return noDotName;
}
// Check name with just hyphens replaced
String noHyphenName = name.replace('-', '_');
if (!name.equals(noHyphenName) && containsKey(noHyphenName)) {
if (!name.equals(noHyphenName) && this.source.containsKey(noHyphenName)) {
return noHyphenName;
}
// Check name with dots and hyphens replaced
String noDotNoHyphenName = noDotName.replace('-', '_');
if (!noDotName.equals(noDotNoHyphenName) && containsKey(noDotNoHyphenName)) {
if (!noDotName.equals(noDotNoHyphenName) && this.source.containsKey(noDotNoHyphenName)) {
return noDotNoHyphenName;
}
// Give up
return null;
}
private boolean containsKey(String name) {
return (isSecurityManagerPresent() ? this.source.keySet().contains(name) : this.source.containsKey(name));
}
protected boolean isSecurityManagerPresent() {
return (System.getSecurityManager() != null);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
package org.springframework.core.type.classreading;
import java.lang.reflect.Field;
import java.security.AccessControlException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -97,7 +96,7 @@ abstract class AbstractRecursiveAnnotationVisitor extends AnnotationVisitor {
catch (ClassNotFoundException | NoClassDefFoundError ex) {
logger.debug("Failed to classload enum type while reading annotation metadata", ex);
}
catch (IllegalAccessException | AccessControlException ex) {
catch (IllegalAccessException ex) {
logger.debug("Could not access enum value while reading annotation metadata", ex);
}
return valueToUse;

View File

@@ -107,9 +107,7 @@ final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttrib
String annotationName = annotationType.getName();
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotationName) && visited.add(annotation)) {
try {
// Only do attribute scanning for public annotations; we'd run into
// IllegalAccessExceptions otherwise, and we don't want to mess with
// accessibility in a SecurityManager environment.
// Only do attribute scanning for public annotations.
if (Modifier.isPublic(annotationType.getModifiers())) {
this.attributesMap.add(annotationName,
AnnotationUtils.getAnnotationAttributes(annotation, false, true));

View File

@@ -190,8 +190,7 @@ public abstract class ReflectionUtils {
/**
* Make the given constructor accessible, explicitly setting it accessible
* if necessary. The {@code setAccessible(true)} method is only called
* when actually necessary, to avoid unnecessary conflicts with a JVM
* SecurityManager (if active).
* when actually necessary, to avoid unnecessary conflicts.
* @param ctor the constructor to make accessible
* @see java.lang.reflect.Constructor#setAccessible
*/
@@ -441,10 +440,9 @@ public abstract class ReflectionUtils {
/**
* Variant of {@link Class#getDeclaredMethods()} that uses a local cache in
* order to avoid the JVM's SecurityManager check and new Method instances.
* In addition, it also includes Java 8 default methods from locally
* implemented interfaces, since those are effectively to be treated just
* like declared methods.
* order to avoid new Method instances. In addition, it also includes Java 8
* default methods from locally implemented interfaces, since those are
* effectively to be treated just like declared methods.
* @param clazz the class to introspect
* @return the cached array of methods
* @throws IllegalStateException if introspection fails
@@ -561,8 +559,7 @@ public abstract class ReflectionUtils {
/**
* Make the given method accessible, explicitly setting it accessible if
* necessary. The {@code setAccessible(true)} method is only called
* when actually necessary, to avoid unnecessary conflicts with a JVM
* SecurityManager (if active).
* when actually necessary, to avoid unnecessary conflicts.
* @param method the method to make accessible
* @see java.lang.reflect.Method#setAccessible
*/
@@ -720,7 +717,7 @@ public abstract class ReflectionUtils {
/**
* This variant retrieves {@link Class#getDeclaredFields()} from a local cache
* in order to avoid the JVM's SecurityManager check and defensive array copying.
* in order to avoid defensive array copying.
* @param clazz the class to introspect
* @return the cached array of fields
* @throws IllegalStateException if introspection fails
@@ -774,8 +771,7 @@ public abstract class ReflectionUtils {
/**
* Make the given field accessible, explicitly setting it accessible if
* necessary. The {@code setAccessible(true)} method is only called
* when actually necessary, to avoid unnecessary conflicts with a JVM
* SecurityManager (if active).
* when actually necessary, to avoid unnecessary conflicts.
* @param field the field to make accessible
* @see java.lang.reflect.Field#setAccessible
*/