Форум программистов, компьютерный форум, киберфорум
Программирование Android
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.68/56: Рейтинг темы: голосов - 56, средняя оценка - 4.68
0 / 0 / 0
Регистрация: 07.02.2017
Сообщений: 7

Retrofit 2, POST запрос, авторизация

08.04.2017, 13:07. Показов 10971. Ответов 5

Студворк — интернет-сервис помощи студентам
Добрый день!
На собеседовании (Junior Android Dev) мне дали задание написать приложение с авторизацией. Вот текст задачи:
Кликните здесь для просмотра всего текста
Все методы API подключаются к хосту: https://api.smiber.com/v4.005
1) Написать приложение с авторизацией
- Получение значения "соли":
/salt method POST

Headers:
Content-Type: application/json

Body:
login*(string) – логин пользователя (test)

Пример: {"login":"test"}

Response:
errors* (object[]) - массив ошибок
code (string) - код ошибки:
INTERNAL_ERROR - внутренняя ошибка, предложить пользователю
message (string) - текстовое сообщение ошибки
data* (object) - объект с данными:
salt (string) - запрашиваемое значение salt

Пример: {"errors":[],"data":{"salt": "7zblOuBj92"}}

-Получение токена доступа:
/oauth/token method POST

Headers:
Content-Type: application/x-www-form-urlencoded

Body:
grant_type* (string) = password
username* (string) – введённый логин (test)
password* (string) – хеш пароля пользователя по алгоритму sha-256(sha-256(пароль)+salt), где salt - значение полученное в предыдущем методе, пароль-123456

Response:
Смотрим статус запроса
200 - ответ успешный, имеем следующий ответ
{
"access_token": "b4141a8d-7f7a-4cb8-aaa5-30035b49afa7",
"token_type": "bearer",
"expires_in": 1199
}
Используем параметр access_token (срок действия 20 минут, если истёк, пусть
пользователь заново авторизуется)
401 – неверный логин или пароль (соответствующее сообщение)
если будет другая ошибка, то выводим сообщение «Ошибка сервера!»

Мне удалось получить соль, захэшировать пароль, но не могу получить access_token: приходит ответ с ошибкой 400 (см. строку 95). Помогите разобраться, что я делаю не так. Мой опыт работы с HTTP запросами и с Retrofit всего несколько дней, так что прошу не судить строго. Вот основной код:
Кликните здесь для просмотра всего текста
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
public class ServerHelper {
 
    private String login, password;
    private API api;
 
    private String access_token = "";
 
    ServerHelper(String login, String password) {
        this.login = login;
        this.password = password;
 
    }
 
    public int getAccess() {
 
        int code = 0;
 
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://api.smiber.com/v4.005/")
                //Конвертер, необходимый для преобразования JSON'а в объекты
                .addConverterFactory(GsonConverterFactory.create())
                .build();
 
        api = retrofit.create(API.class);
 
        Login log = new Login();
        log.setLogin("test");
 
        try {
            //отправляю запрос и получаю соль
            Call<Salt> call = api.getSalt(log);
            Response<Salt> res = call.execute();
            code = res.code();
            if (code == 200) {
                String salt = res.body().getData().getSalt();
                Log.d("MyLogs", "соль = " + salt);
                code = tokenCall(encrypt(salt));
            }
 
        } catch (Exception e) {
            e.printStackTrace();
            Log.d("MyLogs", "ошибка получения соли");
 
        }
        return code;
    }
 
    //шифрую пароль
    private String encrypt(String salt) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
 
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append("sha-256(sha-256(");
            stringBuilder.append("123456");
            stringBuilder.append(")+");
            stringBuilder.append(salt);
            stringBuilder.append(")");
 
            md.reset();
            md.update(stringBuilder.toString().getBytes("UTF-8"));
            byte[] digest = md.digest();
            String str = String.format("%0" + (digest.length*2) +
                    "X", new BigInteger(1, digest));
            Log.d("MyLogs", "digest = " + str);
            return str;
 
        } catch (Exception e) {
            Log.d("MyLogs", "ошибка шифрования пароля");
            e.printStackTrace();
            return null;
        }
 
    }
 
    //запрашиваю токен
    private int tokenCall(String digest) {
 
        if (digest.isEmpty()) {
            return 0;
        }
 
        int code = 0;
 
        Credentials credentials = new Credentials();
        credentials.setGrantType("password");
        credentials.setUsername("test");
        credentials.setPassword(digest);
 
 
 
        try {
            Call<MyToken> call = api.getToken(credentials);
            Response<MyToken> res = call.execute();
            code = res.code();
            if (code == 200) {
                access_token = res.body().getAccessToken();
                Log.d("MyLogs", "токен = " + access_token);
            }
        } catch (Exception e) {
            Log.d("MyLogs", "ошибка получения токена");
        }
        return code;
 
    }
 
 
 
    public String getAccess_token() {
        return access_token;
    }

Метод getAccess() выполняется в AsyncTask, в методе doInBackground().
API interface:
Кликните здесь для просмотра всего текста
Java
1
2
3
4
5
6
7
8
9
public interface API {
    @Headers("Content-Type: application/json")
    @POST("salt")
    Call<Salt> getSalt(@Body Login login);
 
    @Headers("Content-Type: application/x-www-form-urlencoded")
    @POST("oauth/token")
    Call<MyToken> getToken(@Body Credentials credentials);
}

Класс Credentials:
Кликните здесь для просмотра всего текста
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class Credentials {
 
    @SerializedName("grant_type")
    @Expose
    private String grantType;
    @SerializedName("username")
    @Expose
    private String username;
    @SerializedName("password")
    @Expose
    private String password;
 
    public String getGrantType() {
        return grantType;
    }
 
    public void setGrantType(String grantType) {
        this.grantType = grantType;
    }
 
    public String getUsername() {
        return username;
    }
 
    public void setUsername(String username) {
        this.username = username;
    }
 
    public String getPassword() {
        return password;
    }
 
    public void setPassword(String password) {
        this.password = password;
    }
 
}

Класс MyToken:
Кликните здесь для просмотра всего текста
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class MyToken {
 
    @SerializedName("access_token")
    @Expose
    private String accessToken;
    @SerializedName("token_type")
    @Expose
    private String tokenType;
    @SerializedName("expires_in")
    @Expose
    private Integer expiresIn;
 
    public String getAccessToken() {
        return accessToken;
    }
 
    public void setAccessToken(String accessToken) {
        this.accessToken = accessToken;
    }
 
    public String getTokenType() {
        return tokenType;
    }
 
    public void setTokenType(String tokenType) {
        this.tokenType = tokenType;
    }
 
    public Integer getExpiresIn() {
        return expiresIn;
    }
 
    public void setExpiresIn(Integer expiresIn) {
        this.expiresIn = expiresIn;
    }
 
}

Класс Login:
Кликните здесь для просмотра всего текста
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Login {
 
    @SerializedName("login")
    @Expose
    private String login = null;
 
    public String getLogin() {
        return login;
    }
 
    public void setLogin(String login) {
        this.login = login;
    }
 
}

Класс Salt:
Кликните здесь для просмотра всего текста
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Salt {
 
    @SerializedName("errors")
    @Expose
    private List<Object> errors = null;
    @SerializedName("data")
    @Expose
    private Data data;
 
    public List<Object> getErrors() {
        return errors;
    }
 
    public void setErrors(List<Object> errors) {
        this.errors = errors;
    }
 
    public Data getData() {
        return data;
    }
 
    public void setData(Data data) {
        this.data = data;
    }
 
}

Класс Data:
Кликните здесь для просмотра всего текста
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Data {
 
    @SerializedName("salt")
    @Expose
    private String salt;
 
    public String getSalt() {
        return salt;
    }
 
    public void setSalt(String salt) {
        this.salt = salt;
    }
 
}
0
Лучшие ответы (1)
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
08.04.2017, 13:07
Ответы с готовыми решениями:

Retrofit 2 post запрос
Делал реализацию клиент-сервер, в андроид при помощи retrofit. и встретился с проблемой как реализовать POST запрос. ( GET запрос у меня...

Retrofit, JSON, @POST, @GET
Уважаемые программисты, прошу вашей помощи! Хожу на курсы по разработке мобильных приложений, месяц, как хожу, мало еще что знаю, но...

Jwt и retrofit - авторизация и обновление токена
В интернете есть примеры типа таких ссылка Но я ни как не могу разобраться что там к чему.. при авторизации получаю token и...

5
111 / 111 / 43
Регистрация: 24.05.2015
Сообщений: 329
08.04.2017, 22:12
Цитата Сообщение от telephon3208 Посмотреть сообщение
Используем параметр access_token (срок действия 20 минут, если истёк, пусть
пользователь заново авторизуется)
401 – неверный логин или пароль (соответствующее сообщение)
если будет другая ошибка, то выводим сообщение «Ошибка сервера!»

А как используется потом access_token?
С серверной частью практически нет опыта, но решил для себя попробовать.

interface AuthorizationService:
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public interface AuthorizationService {
 
    String API_BASE_URL = "https://api.smiber.com/v4.005/";
 
    @Headers("Content-Type: application/json")
    @POST("salt")
    Call<ResponseFromServer> getSalt(@Body Login login);
 
 
    @FormUrlEncoded
    @Headers("Content-Type: application/x-www-form-urlencoded")
    @POST("oauth/token")
    Call<MyToken> getToken(
            @Field("grant_type") String grantType,
            @Field("username") String username,
            @Field("password") String password);
 
    public static final Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(API_BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build();
}
class ResponseFromServer:
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class ResponseFromServer {
 
    @SerializedName("errors")
    @Expose
    private List<Object> errors = null;
 
    @SerializedName("data")
    @Expose
    private Data data;
 
    public List<Object> getErrors() {
        return errors;
    }
 
    public void setErrors(List<Object> errors) {
        this.errors = errors;
    }
 
    public Data getData() {
        return data;
    }
 
    public void setData(Data data) {
        this.data = data;
    }
 
}
MainActivity:
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
public class MainActivity extends AppCompatActivity {
 
    private static final String TAG="TAG";
    private String salt;
    private static final String PASSWORD = "123456";
    private static final String LOGIN = "test";
    private String hashPassword = null;
 
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        Login login = new Login(LOGIN);
 
        AuthorizationService authorizationService = AuthorizationService.retrofit.create(AuthorizationService.class);
        Call<ResponseFromServer> call = authorizationService.getSalt(login);
 
        call.enqueue(new Callback<ResponseFromServer>() {
            @Override
            public void onResponse(Call<ResponseFromServer> call, Response<ResponseFromServer> response) {
 
                ResponseFromServer responseFromServer = null;
                if (response.body() != null) {
                    responseFromServer = response.body();
                   if (responseFromServer.getErrors().size()!=0) {
                        List<Object> errors = responseFromServer.getErrors();
                        for (int i = 0; i < errors.size(); i++) {
                            Log.d(TAG, errors.get(i).toString());
                        }
                    }else{
                        salt = responseFromServer.getData().getSalt();
                        hashPassword = getHash(getHash(PASSWORD) + salt);
                        callGetToken(hashPassword);
                    }
 
                } else {
                    ResponseBody body = response.errorBody();
                    try {
                        String error = body.string();
                        Log.d(TAG, "Error: " + error);
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                }
            }
 
            @Override
            public void onFailure(Call<ResponseFromServer> call, Throwable t) {
                Log.d(TAG, t.getMessage());
 
            }
        });
    }
 
    private void callGetToken(String token) {
        AuthorizationService authorizationService1 = AuthorizationService.retrofit.create(AuthorizationService.class);
        Call<MyToken> callGetToken = authorizationService1.getToken("password", LOGIN, token);
        callGetToken.enqueue(new Callback<MyToken>() {
            @Override
            public void onResponse(Call<MyToken> call, Response<MyToken> response) {
 
                MyToken myToken = response.body();
                String token = myToken.getAccessToken();
                String tokenType = myToken.getTokenType();
                int expiresIn = myToken.getExpiresIn();
                if(expiresIn==0){
                    callGetToken(hashPassword);
                }
                Log.d(TAG, "Token: " + token);
            }
 
            @Override
            public void onFailure(Call<MyToken> call, Throwable t) {
                Log.d(TAG, t.getMessage());
            }
        });
    }
 
    private static String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0xFF & bytes[i]);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }
 
    private String getHash(String password) {
        String hash = null;
        MessageDigest digest = null;
        try {
            digest = MessageDigest.getInstance("SHA-256");
            digest.update(password.getBytes());
            hash = bytesToHexString(digest.digest());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return hash;
    }
}
1
0 / 0 / 0
Регистрация: 07.02.2017
Сообщений: 7
09.04.2017, 11:28  [ТС]
Ваш код работает! Спасибо за помощь. Но я так и не поняла где ошиблась...

По поводу использования токена, вот вторая часть задания:
Кликните здесь для просмотра всего текста
2) После успешной авторизации, показываем пользователю экран, с возможностью снять два отдельных видео и объединить их, а также кнопку “Отправить”. Используем следующие API:

- Загрузка видео файла на сервер:

/file/upload method POST
Headers:
Content-Type: multipart/form-data
Authorization: Bearer {access_token}
Body:
file*(file[]) - один или несколько передаваемых файлов
type_file*(string) – VIDEO
Response:
errors* (object[]) - массив ошибок
code (string) - кодкатор файла
link (string) - абсолютная ссылка на файл
Пример:
{ "errors":[],
"data":{
"id":"kd7s87ds9d8sid.png",
"link":"http://smiber.com/ kd7s87ds9d8sid.png"
}
}

- Добавление контента на сервер:

/content/add method POST
Headers:
Content-Type: application/json
Authorization: Bearer {access_token}
Body:
nameContent* (string) - название контента
typeContent*(string) - VIDEO
params (object) - здесь помещается идентификатор ранее загруженного файла:
video(string) - id загружаемого видео
Пример:
{
"nameContent":"Название",
"typeContent":"VIDEO",
"params": {"video":"11212kd90d0-ds-0"}
}

Response:
errors* (object[]) - массив ошибок
code (string) - код ошибки:
INTERNAL_ERROR - внутренняя ошибка
message (string) - текстовое сообщение ошибки
data* (object) - объект с данными:
id (string) - id контента
link (string) - ссылка на контент в web интерфейсе
Пример:
{
"id":"1",
"link":"http://link"
}
0
111 / 111 / 43
Регистрация: 24.05.2015
Сообщений: 329
09.04.2017, 11:53
Лучший ответ Сообщение было отмечено telephon3208 как решение

Решение

Цитата Сообщение от telephon3208 Посмотреть сообщение
Но я так и не поняла где ошиблась...
Я не сильно вникал в ваш код, но вот такой же вариант почему то не работал с этим API.

Java
1
2
3
@Headers("Content-Type: application/x-www-form-urlencoded")
    @POST("oauth/token")
    Call<MyToken> getToken(@Body Credentials credentials);
поэтому пришлось каждое поле отдельно прописывать.
1
0 / 0 / 0
Регистрация: 07.02.2017
Сообщений: 7
09.04.2017, 14:44  [ТС]
Да, и еще я неправильно вычисляла хэш(
0
535 / 504 / 114
Регистрация: 12.03.2014
Сообщений: 1,671
11.04.2017, 02:20
Не работал т.к. @Body конвертит класс в json и отправляет сразу его, а api принимает обычные параметры. Если много параметров или нужно их вычислять или еще что подобное есть аннотация @FieldMap - можно отправлять HashMap. И вместо @Headers("Content-Type: application/x-www-form-urlencoded") можно просто@FormUrlEncoded
2
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
11.04.2017, 02:20
Помогаю со студенческими работами здесь

Необходим пример по retrofit c POST запросом
Здравствуйте, предложите пожалуйста пример по retrofit c POST запросом Только не ссылки на этот же форум, где тоже что-то спрашивают, а...

Retrofit запрос не получается?
Нужно написать GET запрос для Retrofit'а т.е. запрос на сервер с токеном, не понимаю кук токен в хедер вставлять, или куда его вообще? ...

Запрос retrofit 2 android studio
как в retrofit 2 выполнить такой запрос? или в advanced rest client не могу понять, куда добавлять вот это curl -ikX ...

RX и Retrofit запрос, который ничего не возвращает
Делаю запросы так: subscription = getApi().getData(token) .subscribeOn(Schedulers.io()) ...

Retrofit не получается запрос который работает в Postman'e?
если в постмэн выбрать form-data указать ключ-значение и сделать запрос то все ок , но если выбрать raw и вставить json который...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
6
Ответ Создать тему
Новые блоги и статьи
SDL3 для Web (WebAssembly): Реализация движения на Box2D v3 - трение и коллизии с повёрнутыми стенами
8Observer8 20.02.2026
Содержание блога Box2D позволяет легко создать главного героя, который не проходит сквозь стены и перемещается с заданным трением о препятствия, которые можно располагать под углом, как верхнее. . .
Конвертировать закладки radiotray-ng в m3u-плейлист
damix 19.02.2026
Это можно сделать скриптом для PowerShell. Использование . \СonvertRadiotrayToM3U. ps1 <path_to_bookmarks. json> Рядом с файлом bookmarks. json появится файл bookmarks. m3u с результатом. # Check if. . .
Семь CDC на одном интерфейсе: 5 U[S]ARTов, 1 CAN и 1 SSI
Eddy_Em 18.02.2026
Постепенно допиливаю свою "многоинтерфейсную плату". Выглядит вот так: https:/ / www. cyberforum. ru/ blog_attachment. php?attachmentid=11617&stc=1&d=1771445347 Основана на STM32F303RBT6. На борту пять. . .
Камера Toupcam IUA500KMA
Eddy_Em 12.02.2026
Т. к. у всяких "хикроботов" слишком уж мелкий пиксель, для подсмотра в ESPriF они вообще плохо годятся: уже 14 величину можно рассмотреть еле-еле лишь на экспозициях под 3 секунды (а то и больше),. . .
И ясному Солнцу
zbw 12.02.2026
И ясному Солнцу, и светлой Луне. В мире покоя нет и люди не могут жить в тишине. А жить им немного лет.
«Знание-Сила»
zbw 12.02.2026
«Знание-Сила» «Время-Деньги» «Деньги -Пуля»
SDL3 для Web (WebAssembly): Подключение Box2D v3, физика и отрисовка коллайдеров
8Observer8 12.02.2026
Содержание блога Box2D - это библиотека для 2D физики для анимаций и игр. С её помощью можно определять были ли коллизии между конкретными объектами и вызывать обработчики событий столкновения. . . .
SDL3 для Web (WebAssembly): Загрузка PNG с прозрачным фоном с помощью SDL_LoadPNG (без SDL3_image)
8Observer8 11.02.2026
Содержание блога Библиотека SDL3 содержит встроенные инструменты для базовой работы с изображениями - без использования библиотеки SDL3_image. Пошагово создадим проект для загрузки изображения. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru