yeoseon/tip-archive

[Java] Reflection

yeoseon opened this issue · 1 comments

예전에 회사에서 SonarQube를 통한 소스코드 품질 개선을 할 때 나왔던 개념 같다.

Reference

Java Reflection

이미 로딩이 완료된 클래스에서 또 다른 클래스를 동적으로 로딩하여 생성자, 멤버필드 그리고 멤버 메서드 등을 사용할 수 있도록 한다.

컴파일 시간이 아닌 실행시간(Runtime)에 동적으로 특정 클래스의 정보를 객체화를 통해 분석 및 추출해낼 수 있는 프로그래밍 기법

사용 방법

예제 1

Class.forName("클래스명")에는 해당 클래스가 없을 경우 처리할 ClassNotFoundException 처리가 되어있다.

@CallerSensitive
public static Class<?> forName(String className)
            throws ClassNotFoundException {
    Class<?> caller = Reflection.getCallerClass();
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

따라서 다음과 같이 try-catch 문으로 처리를 해주면 된다.

import java.lang.reflect.Method;

/**
 * 리플렉션 예제 - Vector Class
 *
 * @author Kimtaeng
 * Created on 2018. 1. 5
 */
public class MadPlay {

    public void reflectionTest() {
        try {
            Class vectorClass = Class.forName("java.util.Vector");

            Method[] methods = vectorClass.getDeclaredMethods();

            for (Method method : methods) {
                System.out.println(method.toString());
            }

        } catch (ClassNotFoundException e) {
            // Exception Handling
        }
    }

    public static void main(String[] args) {
        new MadPlay().reflectionTest();
    }
}

예제 2

매개변수와 반환 타입도 확인할 수 있다.

import java.lang.reflect.Method;

/**
 * 리플렉션 예제 - Parameter Types
 *
 * @author Kimtaeng
 * Created on 2018. 1. 5
 */
public class MadPlay {

    public void reflectionTest() {

        try {
            Class vectorClass = Class.forName("java.util.Vector");

            Method[] methods = vectorClass.getDeclaredMethods();

            /* 임의의 메서드 지정, 이름으로 확인 */
            Method method = methods[25];
            System.out.println("Class Name : " + method.getDeclaringClass());
            System.out.println("Method Name : " + method.getName());
            System.out.println("Return Type : " + method.getReturnType());

            /* Parameter Types */
            Class[] paramTypes = method.getParameterTypes();
            for(Class paramType : paramTypes) {
                System.out.println("Param Type : " + paramType);
            }

            /* Exception Types */
            Class[] exceptionTypes = method.getExceptionTypes();
            for(Class exceptionType : exceptionTypes) {
                System.out.println("Exception Type : " + exceptionType);
            }

        } catch (ClassNotFoundException e) {
            // Exception Handling
        }
    }

    public static void main(String[] args) {
        new MadPlay().reflectionTest();
    }
}

예제 3

메소드 이름으로도 호출할 수 있다.

import java.lang.reflect.Method;

/**
 * 리플렉션 예제 - Call method by name
 *
 * @author Kimtaeng
 * Created on 2018. 1. 5
 */
class MadLife {
    public void sayHello(String name) {
        System.out.println("Hello, " + name);
    }
}

public class MadPlay {

    public void reflectionTest() {

        try {
            Class myClass = Class.forName("MadLife");
            Method method = myClass.getMethod("sayHello", new Class[]{String.class});
            method.invoke(myClass.newInstance(), new Object[]{new String("Kimtaeng")});

        } catch (Exception e) {
            // Exception Handling
        }
    }

    public static void main(String[] args) {
        new MadPlay().reflectionTest();
    }
}

예제 4

멤버 필드의 값도 수정할 수 있다.

import java.lang.reflect.Field;

/**
 * 리플렉션 예제 - Modify member variable in class
 *
 * @author Kimtaeng
 * Created on 2018. 1. 5
 */
class MadLife {
    public int number;

    public void setNumber(int number) {
        this.number = number;
    }
}

public class MadPlay {

    public void reflectionTest() {

        try {
            Class myClass = Class.forName("MadLife");

            Field field = myClass.getField("number");
            MadLife obj = (MadLife) myClass.newInstance();
            obj.setNumber(5);

            System.out.println("Before Number : " + field.get(obj));
            field.set(obj, 10);
            System.out.println("After Number : " + field.get(obj));
        } catch (Exception e) {
            // Exception Handling
        }
    }

    public static void main(String[] args) {
        new MadPlay().reflectionTest();
    }
}

예제 5

나는 회사에서 이런 유형을 봤었다.

private SampleIn _setSampleIn(TargetIn in) {
	SampleIn sampleIn = new SampleIn();

	sampleIn.setValue1(in.getValue1());
	sampleIn.setValue2(in.getValue2());
	sampleIn.setValue3(in.getValue3());
	sampleIn.setValue4(in.getValue4());
	sampleIn.setValue5(in.getValue5());

	return sampleIn;
}

해당 코드를 다음과 같이 바꿨다.

private SampleIn _setSampleIn(TargetIn in){
	SampleIn sampleIn = new SampleIn();
	_copyField(in, sampleIn);

	return SampleIn;
}

private void _copyField(Object from, Object to) {
	Field[] toFields = to.getClass().getDeclaredFields();

	for (Field toField : toFields) {
	   try {
		   Field fromField = from.getClass().getDeclaredField(toField.getName());
		   fromField.setAccessible(true);
		   Object fromObj = fromField.get(from);
		   toField.setAccessible(true);
		   toField.set(to, fromObj);
	   } catch (NoSuchFieldException e) {	
	   } catch (SecurityException e) {
	   } catch (IllegalArgumentException e) {
	   } catch (IllegalAccessException e) {
	   }
	}
}

보안 문제

SonarQube의 몇몇 규칙에서는 Reflection 코드를 지양하라고 하고 있다.

Dynamically executing code is security-sensitive

동적으로 실행되는 코드는 보안에 취약합니다. 이 규칙의 목적은 dynamic code execution이 발생할 수 있는 코드에 대해 다시 한 번 검토하는 것 입니다.

[주요 원인]

[권장하는 Secure Coding]

  • 알 수 없는 코드의 실행과 관련하여 가장 좋은 해결책은 신뢰할 수 없는 소스가 제공 한 코드를 실행하지 않는 것입니다. 정말로 해야한다면 샌드 박스 환경에서 코드를 실행하십시오. jails, 방화벽 및 운영 체제와 프로그래밍 언어가 제공하는 모든 것을 사용하십시오. (예 : Java의 보안 관리자, iframe 및 웹 브라우저의 javascript에 대한 동일한 출처 정책)
  • 위험한 코드의 블랙리스트를 만들려고 시도하지 마십시오. 그런 식으로 모든 공격을 감추는 것은 불가능합니다.
  • reflection의 사용에 관해서는, 그것이 많은 취약점으로 이어질 수 있기 때문에 그것은 엄격하게 통제되어야합니다. 신뢰할 수없는 소스가 어떤 코드를 실행할 것인지 결정하지 못하게하십시오. 어쨌든해야한다면, 허용 된 코드 목록을 만들고이 목록 중에서 선택하십시오.

[예외]

  • 하드 코딩 된 type name, method name 또는 field name을 가진 reflection methods 를 호출해도 문제가 발생하지 않습니다.

Changing or bypassing accessibility is security-sensitive

외부에 공개되지 않는 private 멤버도 접근과 조작이 가능하므로 주의해야한다.
private 멤버는 Field.setAccessible() 메서드를 true 지정하면 접근이 가능하다.

접근의 Changing(변경) 또는 bypassing(무시, 우회)은 보안에 취약합니다. 이 규칙은 Reflection을 사용하지 않는 것을 권장하고있습니다. 사용해야 한다면 그래도 되는 예외임을 증명하는 문서를 작성할 필요가 있어보입니다.

private 메소드는 private한 목적으로 작성되었으며, 다른 모든 visibility level(public, protected, default)에서도 마찬가지입니다.
Class나 Method의 accessibility을 변경하거나 우회하는 것은 "캡슐화 원칙(the encapsulation principle)"에 위배 되어 보안상의 문제가 발생할 수 있습니다.
이 규칙은 reflection을 사용하여 class, method 또는 field의 (접근 제한자에 의한)visibility를 변경하고 field의 값을 직접 업데이트하는 데 사용되는 경우 문제가 발생합니다.

[주요 원인]

  • method나 field의 visibility level 수준을 재 정의 할 필요가 있다.
  • 이 method는 신뢰할 수없는 코드에 의해 호출됩니다.
  • 이 코드를 이용해 보안에 민감한 method나 field에 접근할 수 있습니다.
  • 신뢰할 수 없는 코드는 Java Reflection API에 접근 할 수 있습니다.

[권장하는 Secure Coding]

  • 가능하면 field나 method의 accessibility를 수정하거나 무시하지 마십시오.
  • 신뢰할 수 없는 코드는 Java Reflection API에 직접적으로 액세스 해서는 안됩니다. 이 method가 그래도 되는 예외인지 확인하십시오.
  • 신뢰할 수없는 코드를 send boxing 하고 Reflection API에 대한 액세스를 금지하려면 ClassLoaders 및 SecurityManager를 사용하십시오.

Reference