Spring - сохранение / восстановление HttpCookie (в виде строки) в базе данных дает проблему: последний блок не имеет достаточно допустимых бит - PullRequest
1 голос
/ 07 июля 2019

Мое приложение должно сохранять и восстанавливать HttpCookie в базе данных. Поэтому я попытался закодировать / декодировать объекты HttpCookie в строку с помощью следующего кода. В некоторых случаях в результате появляется сообщение об ошибке: последний блок не имеет достаточного количества правильных битов.

Да, я прочитал пост об ошибке, но речь идет о чтении через буфер и преобразовании буфера. Это другое, потому что чтение потока происходит за 1 раз !

В некоторых случаях этот код выдает указанное сообщение об ошибке. Как я могу решить это?

public class SerializableHttpCookie implements Serializable {
    private static final long serialVersionUID = 6374381323722046732L;
    private transient HttpCookie cookie;
    private Field fieldHttpOnly; // needed for a workaround
    ...

public String encode2(HttpCookie cookie) {
    this.cookie = cookie;
    ByteArrayOutputStream os = new ByteArrayOutputStream();
    try {
        ObjectOutputStream outputStream = new ObjectOutputStream(os);
        outputStream.writeObject(this);
    } catch (IOException e) {
        logger.error(  "IOException in encodeCookie", e);
        return null;
    }
    return Base64.getUrlEncoder().encodeToString( os.toByteArray());
}
public HttpCookie decode2(String encodedCookie) {
    byte[] bytes = Base64.getUrlDecoder().decode(encodedCookie);
    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream( bytes);
    HttpCookie cookie = null;
    try {
        ObjectInputStream objectInputStream = new ObjectInputStream( byteArrayInputStream);
        cookie = ((SerializableHttpCookie) objectInputStream.readObject()).cookie;
    } catch (IOException e) {
        logger.error(   "IOException in decodeCookie", e);
    } catch (ClassNotFoundException e) {
        logger.error(   "ClassNotFoundException in decodeCookie", e);
    }
    return cookie;
}

ReadObject и WriteObject:

private void writeObject(ObjectOutputStream out) throws IOException {
    out.writeObject(cookie.getName());
    out.writeObject(cookie.getValue());
    out.writeObject(cookie.getComment());
    out.writeObject(cookie.getCommentURL());
    out.writeObject(cookie.getDomain());
    out.writeLong(cookie.getMaxAge());
    out.writeObject(cookie.getPath());
    out.writeObject(cookie.getPortlist());
    out.writeInt(cookie.getVersion());
    out.writeBoolean(cookie.getSecure());
    out.writeBoolean(cookie.getDiscard());
    out.writeBoolean(getHttpOnly());
}

private void readObject(ObjectInputStream in) throws IOException,
        ClassNotFoundException {
    String name = (String) in.readObject();
    String value = (String) in.readObject();
    cookie = new HttpCookie(name, value);
    cookie.setComment((String) in.readObject());
    cookie.setCommentURL((String) in.readObject());
    cookie.setDomain((String) in.readObject());
    cookie.setMaxAge(in.readLong());
    cookie.setPath((String) in.readObject());
    cookie.setPortlist((String) in.readObject());
    cookie.setVersion(in.readInt());
    cookie.setSecure(in.readBoolean());
    cookie.setDiscard(in.readBoolean());
    setHttpOnly(in.readBoolean());
}

Я использовал разные подходы, такие как следующие, что также приводило к ошибкам (при подсчете). Ошибка помечена как комментарий.

private String byteArrayToHexString(byte[] bytes) {
    StringBuilder sb = new StringBuilder(bytes.length * 2);
    for (byte element : bytes) {
        int v = element & 0xff;
        if (v < 16) {
            sb.append('0');
        }
        sb.append(Integer.toHexString(v));
    }
    return sb.toString();
}
private byte[] hexStringToByteArray(String hexString) {
    int len = hexString.length();
    byte[] data = new byte[len / 2];
    for (int i = 0; i < len; i += 2) {
        data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character
                .digit(hexString.charAt(i + 1), 16)); // ERROR: hexString.charAt(i+1) out of range
    }
    return data;
}

EncodeAndSerialize

Еще один способ сделать это - вызвать код в ответе. Увы, я получаю ту же ошибку при декодировании строки.

new SerializableHttpCookie2().serializeAndEncode(cookie)));

И

HttpCookie cookie = new SerializableHttpCookie2().decodeAndDeserialize(encodedCookie);

Использование библиотеки commons-codec:

public String serializeAndEncode(final HttpCookie cookie) throws IllegalAccessException, IllegalArgumentException {
    final String serialized = this.serialize(cookie);
    return new String( Hex.encodeHex(serialized.getBytes()));
}

А

public HttpCookie decodeAndDeserialize(final String string)
        throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
    // final String decoded = this.decode(string);
    String decoded;
    try {
        decoded = new String(Hex.decodeHex(string.toCharArray()));
    } catch ( Exception e) {
        return null;
    }
    return this.deserialize(decoded);
}

1 Ответ

1 голос
/ 16 июля 2019

возможно, следующий класс решает вашу проблему

, потому что HttpCookie не реализует Serializable, значения считываются и записываются обратно с помощью отражения.

im с использованием Java 12 и в моем случае отражениявызывает предупреждение, потому что код обращается к private final полям.предупреждение гласит:

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by HttpCookieDeSerializer (file:...) to field java.net.HttpCookie.name
WARNING: Please consider reporting this to the maintainers of HttpCookieDeSerializer
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

класс:

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.HttpCookie;
import java.util.Base64;

public class HttpCookieDeSerializer {
    // TODO: need to be changed?
    private final String fieldValueDelimiter = "===";
    // TODO: need to be changed?
    private final String fieldValuePairDelimiter = "###";

    public HttpCookieDeSerializer() {
        super();
    }

    public String decode(final String string) {
        return new String(Base64.getUrlDecoder().decode(string));
    }

    public HttpCookie decodeAndDeserialize(final String string)
            throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
        final String decoded = this.decode(string);
        // TODO: remove sysout
        System.out.println(decoded);
        return this.deserialize(decoded);
    }

    public HttpCookie deserialize(final String decoded)
            throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
        final String name = this.preGet(decoded, "name");
        final String value = this.preGet(decoded, "value");
        final HttpCookie cookie = new HttpCookie(name, value);
        final String[] fieldsAndValues = decoded.split("(" + this.fieldValuePairDelimiter + ")");
        for (final String fieldAndValue : fieldsAndValues) {
            final String[] fieldAndValueSplitted = fieldAndValue.split("(" + this.fieldValueDelimiter + ")");
            final Field field = HttpCookie.class.getDeclaredField(fieldAndValueSplitted[0]);
            if (Modifier.isFinal(field.getModifiers())) {
                // ???
                // continue;
            }
            field.setAccessible(true);
            final Class<?> type = field.getType();
            if (String.class.equals(type)) {
                field.set(cookie, this.convertNullStringToNullObject(fieldAndValueSplitted[1]));
            } else if (Long.TYPE.equals(type)) {
                field.setLong(cookie, Long.parseLong(fieldAndValueSplitted[1]));
            } else if (Integer.TYPE.equals(type)) {
                field.setInt(cookie, Integer.parseInt(fieldAndValueSplitted[1]));
            } else if (Boolean.TYPE.equals(type)) {
                field.setBoolean(cookie, Boolean.parseBoolean(fieldAndValueSplitted[1]));
            }
        }
        return cookie;
    }

    public String encode(final String string) {
        return Base64.getUrlEncoder().encodeToString(string.getBytes());
    }

    public String serialize(final HttpCookie cookie) throws IllegalAccessException, IllegalArgumentException {
        final StringBuilder builder = new StringBuilder();
        final Field[] fields = HttpCookie.class.getDeclaredFields();
        boolean first = true;
        for (final Field field : fields) {
            if (Modifier.isStatic(field.getModifiers())) {
                continue;
            }
            if (!first) {
                builder.append(this.fieldValuePairDelimiter);
            }
            builder.append(field.getName());
            builder.append(this.fieldValueDelimiter);
            final Class<?> type = field.getType();
            field.setAccessible(true);
            if (String.class.equals(type)) {
                builder.append((String) field.get(cookie));
            } else if (Long.TYPE.equals(type)) {
                builder.append(Long.toString(field.getLong(cookie)));
            } else if (Integer.TYPE.equals(type)) {
                builder.append(Integer.toString(field.getInt(cookie)));
            } else if (Boolean.TYPE.equals(type)) {
                builder.append(Boolean.toString(field.getBoolean(cookie)));
            }
            first = false;
        }
        final String serialized = builder.toString();
        return serialized;
    }

    public String serializeAndEncode(final HttpCookie cookie) throws IllegalAccessException, IllegalArgumentException {
        final String serialized = this.serialize(cookie);
        // TODO: remove sysout
        System.out.println(serialized);
        return this.encode(serialized);
    }

    private Object convertNullStringToNullObject(final String string) {
        if ("null".equals(string)) {
            return null;
        }
        return string;
    }

    private String preGet(final String decoded, final String fieldName) {
        final String[] fieldsAndValues = decoded.split("(" + this.fieldValuePairDelimiter + ")");
        for (final String fieldAndValue : fieldsAndValues) {
            if (fieldAndValue.startsWith(fieldName + this.fieldValueDelimiter)) {
                return fieldAndValue.split("(" + this.fieldValueDelimiter + ")")[1];
            }
        }
        return null;
    }

    public static void main(final String[] args) {
        final HttpCookieDeSerializer hcds = new HttpCookieDeSerializer();
        try {
            final HttpCookie cookie = new HttpCookie("myCookie", "first");
            final String serializedAndEncoded = hcds.serializeAndEncode(cookie);
            // TODO: remove sysout
            System.out.println(serializedAndEncoded);
            final HttpCookie other = hcds.decodeAndDeserialize(serializedAndEncoded);
            // TODO: remove sysout
            System.out.println(cookie.equals(other));
        } catch (final Throwable t) {
            t.printStackTrace();
        }
    }
}

, на мой взгляд, нет необходимости кодировать / или закодировать строку сериализации.

, ноесли вы хотите сделать это, я предлагаю использовать библиотеку Apache * Commons-Codec org.apache.commons.codec.binary.Hex, потому что это проверенная и стабильная библиотека без зависимостей времени выполнения размером ~ 331 КБ

, тем не менее, я попробовал следующеевозможности сериализации и десериализации

  1. без base64-кодирования / hexing
  2. с base64-кодированием
  3. с hexing
  4. с base-64-кодировкойи hexing

для меня, все возможности работают нормально.

...