2013-07-11 4 views
10

Zostałem zadany to pytanie w wywiadzie. Osoba przeprowadzająca wywiad chciała wiedzieć, jak uniezależnić obiekt. a potem zapytał, co jeśli serializuję ten obiekt - czy złamie niezmienność? Jeśli tak, jak mogę temu zapobiec? Czy ktoś może mi pomóc to zrozumieć?W jaki sposób przekształcanie/deseryzacja może przerwać niezmienność?

+0

Nie jestem w 100% pewien, nigdy wcześniej nie dodawałem zbyt wiele czasu do serializacji, ale może to mieć związek ze słowem kluczowym "przejściowy". Jeśli masz, powiedz instancję MouseListener, która jest przejściowa w niezmiennej klasie, serializując, a następnie odseparowując, możesz bardzo dobrze zmutować klasę, modyfikując instancję MouseListener. –

+2

@Daft Punk - Przejściowe słowo kluczowe jest używane do określenia, że ​​atrybut nie musi być serializowany – DRastislav

+1

Twierdzę, że serializacja i deserializacja tworzy nowy obiekt, dlatego nie wpływa na niezmienność. – parsifal

Odpowiedz

9

Obiekt niezmienny to taki, którego nie można zmienić po utworzeniu. Możesz utworzyć taki obiekt, używając modyfikatorów dostępu private i słowa kluczowego final.

Jeśli niezmienny obiekt został poddany serializacji, jego surowe bajty można zmodyfikować, aby po deserializacji obiekt przestał być taki sam.

Nie da się tego całkowicie zapobiec. Szyfrowanie, sumy kontrolne i CRC pomogą temu zapobiec.

+3

'private' tak naprawdę nie jest wymagane. Publiczny końcowy ciąg foo = "foo" 'nie spowodowałby zmiany klasy. To tylko zła enkapsulacja. – zapl

+0

w tym konkretnym przypadku nie. ale to tylko dlatego, że sam obiekt String jest niezmienny.Czuję, że naprawdę niezmienny obiekt nie zezwala na dostęp do jego wewnętrznych danych, jeśli te obiekty nie są również niezmienne. –

+0

zależy od twojej definicji. Przypuszczam, że zmienna w tym miejscu oznacza, że ​​obiekt nie może być ponownie przypisany lub jego wewnętrzny stan nie może być zmienić? –

4

Po przekształceniu do postaci szeregowej wykresu obiektów, który ma wiele odniesień do tego samego obiektu, serializator odnotowuje ten fakt, aby deserializowany wykres obiektów miał taką samą strukturę.

Na przykład

int[] none = new int[0]; 
int[][] twoArrays = new int[] { none, none }; 
System.out.print(twoArrays[0] == twoArrays[1]); 

wypisze true, a jeśli odcinkach i rozszeregować twoArrays wtedy byłoby uzyskać ten sam rezultat zamiast każdego elementu tablicy będącej inny przedmiot jak w

int[][] twoDistinctArrays = new int[] { new int[0], new int[0] }; 

Możesz wykorzystać tę obsługę do dzielenia się referencjami do bajtów rzemieślniczych po zserializowanym wpisie, aby udostępnić odwołanie do obiektu lub tablicy prywatnej pomocy, a następnie zmutować go.

Tak więc klasa, której nie można odpalić, może zachować niezmienniki - prywatny obiekt nie ucieknie - nie można utrzymać klasy serializowalnej.

+1

Powszechną obroną przed tym jest implementacja defensywnie zakodowanej metody 'readObject()', która tworzy nowe obiekty tych zserializowanych (aby zmienić użyte referencje). Na przykład, jeśli obiekt miał pole "Integer" o nazwie "id", twoja metoda 'readObject()' miałaby 'id = new Integer (id)'. Zobacz dwie minuty http://video.javazone.no/talk/49302113 na czas 21:21, aby dowiedzieć się więcej. –

+1

Można również zapobiegać aliasowaniu za pomocą metody 'ObjectInputStream.readUnshared()'. To nie wystarcza dla bezpieczeństwa; jak Bloch zwrócił uwagę w * Efektywnej Javie *, ktoś może wiele w zserializowanym pliku zmienić wersję bez wersji i zepsuć niezmienniki. Co gorsza, ktoś może zastąpić podklasę dla klasy. Serializujesz 'java.util.Date', a ktoś zamienia go na podklasę' my.bad.Date', która robi nieprzyjemne rzeczy podczas uzyskiwania dostępu. –

+1

@Slanec, Dzięki za wskazówkę do tej rozmowy. Tak, możesz rozwiązać problemy bezpieczeństwa z serializacją Java, reimplementując duże fragmenty, ale kiedy muszę konwertować bajty na obiekty bezpiecznie, po prostu zrezygnuję z wbudowanej serializacji Javy i zamiast tego używam generatora analizatora tak, że mogę statycznie związać metody które można wywołać w wyniku parsowania. –

1

Uczyń go niezmiennym, zachowując wszystkie informacje o stanie w formularzu, w którym nie można go zmienić po utworzeniu obiektu.

Java nie zezwala na doskonałą niezmienność w niektórych przypadkach.

Serializowalny to coś, co można zrobić, ale nie jest idealny, ponieważ musi istnieć sposób na odtworzenie dokładnej kopii obiektu podczas deserializacji, a może nie wystarczyć użycie tych samych konstruktorów do deserializacji i utworzenia obiekt w pierwszej kolejności. To pozostawia dziurę.

Niektóre rzeczy do zrobienia:

  • Nic oprócz własności prywatnej lub końcowych.
  • Konstruktor ustawia dowolne z właściwości krytycznych dla działania.

Niektóre inne rzeczy, aby myśleć o:

  • zmienne statyczne są prawdopodobnie zły pomysł, chociaż statyczny końcowy stały nie jest problemem. Nie ma możliwości ustawienia ich z zewnątrz, gdy klasa jest załadowana, ale uniemożliwia późniejsze jej ustawienie.
  • jeśli jedną z właściwości przekazanych do konstruktora jest obiekt, wywołujący może zachować odniesienie do tego obiektu, a jeśli nie jest również niezmienny, zmienić jakiś stan wewnętrzny tego obiektu.To skutecznie zmienia stan wewnętrzny obiektu, który przechował kopię tego, teraz zmodyfikowanego, obiektu.
  • Ktoś mógłby, teoretycznie, wziąć zserializowaną formę i ją zmienić (lub po prostu zbudować zserializowaną formę od zera), a następnie użyć jej do deserializacji, tworząc w ten sposób zmodyfikowaną wersję obiektu. (Myślę, że w większości przypadków nie warto się tym martwić).
  • można napisać niestandardowy szeregowy/deserializacyjny kod, który podpisuje zserializowaną formę (lub szyfruje ją) w taki sposób, że można wykryć modyfikacje. Lub możesz użyć jakiejś formy transmisji zserializowanego formularza, który gwarantuje, że nie zostanie zmieniony. (To zakłada, że ​​masz pewną kontrolę nad serializowanym formularzem, gdy nie jest w tranzycie.)
  • Istnieją manipulatory kodów bajtowych, które mogą zrobić wszystko, co chcą, aby obiekt. Na przykład dodaj metodę ustawiającą do obiektu, który w przeciwnym razie jest niezmienny.

Prosta odpowiedź jest taka, że ​​w większości przypadków wystarczy przestrzegać dwóch zasad u góry tej odpowiedzi, które będą wystarczające dla zaspokojenia potrzeb w zakresie niezmienności.

4

Powinieneś przeczytać Efektywną Javę napisaną przez Joshua Blocha. Cały rozdział poświęcony jest zagadnieniom bezpieczeństwa związanym z serializacją i poradom, jak prawidłowo zaprojektować klasę.

W kilku słowach: powinieneś poznać metody readObject i readResolve.

Bardziej szczegółowa odpowiedź: Tak, serializacja może przerwać niezmienność.

Załóżmy masz klasy okresu (to jest przykład z książki Joshuy):

private final class Period implements Serializable { 
    private final Date start; 
    private final Date end; 

public Period(Date start, Date end){ 
    this.start = new Date(start.getTime()); 
    this.end = new Date(end.getTime()); 
    if(this.start.compareTo(this.end() > 0) 
     throw new IllegalArgumentException("sth"); 
} 
//getters and others methods ommited 
} 

Wygląda świetnie. To niezmienna (nie można zmienić początek i koniec po inicjalizacji), elegancki, mały, threadsafe itp

Ale ...

Trzeba pamiętać, że serializacji jest kolejnym sposobem tworzenia obiektów (i to jest nie używając konstruktorów). Obiekty budowane są ze strumienia bajtów.

Rozważ scenariusz, gdy ktoś (atakujący) zmieni tablicę bajtów serializacji. Jeśli zrobi coś takiego, może złamać twój stan o końcu < końca. Ponadto istnieje możliwość, że osoba atakująca wprowadzi strumień (przekazany do metody deserializacji) odniesienie do jego obiektu Date (który jest zmienny, a niezmienność klasy okresu zostanie całkowicie zniszczona).

Najlepsza obrona nie korzysta z serializacji, jeśli nie musisz. Jeśli musisz serializować swoją klasę, użyj wzoru Serialization Proxy.

Edytuj (na żądanie kurzbota): Jeśli chcesz korzystać z Serializacji Proxy, musisz dodać wewnętrzną klasę statyczną w Okresie. Obiekty tej klasy będą używane do serializacji zamiast do obiektów klasy Okres.

W klasie Okres napisać dwie nowe metody:

private Object writeReplace(){ 
    return new SerializationProxy(this); 
} 

private void readObject(ObjectInputStream stream) throws InvalidObjectException { 
    throw new InvalidObjectException("Need proxy"); 
} 

Pierwszy sposób zastąpić domyślny obiekt odcinkach okresu w obiekcie SerializationProxy. Druga gwarancja, że ​​atakujący nie użyje standardowej metody readObject.

Należy napisać writeObject metodę SerializationProxy więc można użyć:

private Object readResolve() { 
    return new Period(start, end); 
} 

w tym przypadku korzystania z interfejsu API tylko publiczny i mieć pewność, że klasa Okres pozostaje niezmiennie.

+0

Czy możesz wyjaśnić, w jaki sposób wzór Serialization Proxy może rozwiązać ten problem? Nie jestem pewien, czy rozumiem, jak się broni przed scenariuszem, który dałeś. – kurtzbot

+0

Dodałem trochę wyjaśnienia. –

1

Jak powiedzieli inni, można argumentować, że serializacja powoduje powstanie zupełnie nowego obiektu, który jest wówczas niezmienny, więc nie, serializacja go nie łamie, ale myślę, że istnieje szerszy obraz niezmienności, którą musimy rozważyć przed udzieleniem odpowiedzi na to pytanie.

Myślę, że prawdziwa odpowiedź zależy całkowicie od klasy serializacyjnej i poziomu niezmienności wymaganego, ale ponieważ ankieter nie dał nam kodu źródłowego, wymyślę własną. Chciałbym również zwrócić uwagę, że gdy ludzie zaczynają mówić o niezmienności, zaczynają rzucać słowo kluczowe - tak, to czyni niezmienną referencję, ale nie jest to jedyny sposób na osiągnięcie niezmienności. Ok, spójrzmy na niektóre kodu:

public class MyImmutableClass implements Serializable{ 
    private double value; 

    public MyImmutableClass(double v){ 
     value = v; 
    } 

    public double getValue(){ return value; } 
} 

Czy ta klasa zmienny bo realizowane Serializable? Czy można go zmienić, ponieważ nie użyłem słowa kluczowego final? Nie ma mowy - jest niezmienny w każdym praktycznym znaczeniu tego słowa, ponieważ nie zamierzam modyfikować kodu źródłowego (nawet jeśli mnie ładnie poprosisz), ale co ważniejsze, jest niezmienny, ponieważ żadna klasa zewnętrzna nie może zmienić wartości value , brak użycia refleksji, aby ją upublicznić, a następnie ją zmodyfikować. Na podstawie tego tokena, przypuszczam, że mógłbyś uruchomić pośredni edytor heksadecymalny i ręcznie zmodyfikować wartość również w pamięci RAM, ale to nie czyni go bardziej zmiennym niż przedtem. Rozszerzanie klas również nie może go modyfikować. Oczywiście, możesz go przedłużyć, a następnie zastąpić getValue(), aby zwrócić coś innego, ale nie spowoduje to zmiany podstawowego value.

Wiem, że może to zepsuć wielu ludzi w niewłaściwy sposób, ale uważam, że niezmienność jest często czysto semantyczna - np. czy jest niezmienny dla kogoś, kto wywołuje twój kod z zewnętrznej klasy, czy też jest niezmienny od kogoś używającego BusPirate na twojej płycie głównej? Istnieją BARDZO dobre powody, aby używać final, aby zapewnić niezmienność, ale myślę, że to znaczenie jest znacznie zawyżone w więcej niż kilku argumentach. Tylko dlatego, że JVM może wykonywać magię pod maską, aby zapewnić działanie serializacji, nie oznacza to, że poziom niezmienności, jakiego wymaga twoja aplikacja, jest w jakiś sposób zepsuty.

0

Brud-Odpowiedź jest prosta

foo
class X implements Serializable { 
    private final transient String foo = "foo"; 
} 

pole będzie równa się „foo”, jeśli obiekt jest nowo utworzone, ale będzie zerowy, gdy rozszeregować (i bez uciekania się do brudnych sztuczek, nie będzie być w stanie to przypisać).

+0

Prawidłowo! Nie zepsujesz niezmienności podczas serializacji obiektów, jeśli nigdy nie serializujesz wewnętrznych danych. Chociaż jest to prawda, nie jest to zbyt użyteczne. –

Powiązane problemy