jwt settings added message on base64 validation exception, default key is to be validated ok, tests added
This commit is contained in:
parent
335d88c82e
commit
46b2adeb2c
@ -15,15 +15,22 @@
|
|||||||
*/
|
*/
|
||||||
package org.thingsboard.server.config.jwt;
|
package org.thingsboard.server.config.jwt;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.thingsboard.server.common.data.security.model.JwtToken;
|
import org.thingsboard.server.common.data.security.model.JwtToken;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@ConfigurationProperties(prefix = "security.jwt")
|
@ConfigurationProperties(prefix = "security.jwt")
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
@Data
|
@Data
|
||||||
public class JwtSettings {
|
public class JwtSettings {
|
||||||
|
static final String ADMIN_SETTINGS_JWT_KEY = "jwt";
|
||||||
|
static final String TOKEN_SIGNING_KEY_DEFAULT = "thingsboardDefaultSigningKey";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link JwtToken} will expire after this time.
|
* {@link JwtToken} will expire after this time.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -37,13 +37,14 @@ import java.util.Base64;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static org.thingsboard.server.config.jwt.JwtSettings.ADMIN_SETTINGS_JWT_KEY;
|
||||||
|
import static org.thingsboard.server.config.jwt.JwtSettings.TOKEN_SIGNING_KEY_DEFAULT;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class JwtSettingsServiceDefault implements JwtSettingsService {
|
public class JwtSettingsServiceDefault implements JwtSettingsService {
|
||||||
|
|
||||||
static final String ADMIN_SETTINGS_JWT_KEY = "jwt";
|
|
||||||
static final String TOKEN_SIGNING_KEY_DEFAULT = "thingsboardDefaultSigningKey";
|
|
||||||
@Lazy
|
@Lazy
|
||||||
private final AdminSettingsService adminSettingsService;
|
private final AdminSettingsService adminSettingsService;
|
||||||
@Lazy
|
@Lazy
|
||||||
|
|||||||
@ -26,6 +26,8 @@ import java.util.Base64;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static org.thingsboard.server.config.jwt.JwtSettings.TOKEN_SIGNING_KEY_DEFAULT;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class JwtSettingsValidatorDefault implements JwtSettingsValidator {
|
public class JwtSettingsValidatorDefault implements JwtSettingsValidator {
|
||||||
@ -58,7 +60,7 @@ public class JwtSettingsValidatorDefault implements JwtSettingsValidator {
|
|||||||
if (Arrays.isNullOrEmpty(decodedKey)) {
|
if (Arrays.isNullOrEmpty(decodedKey)) {
|
||||||
throw new DataValidationException("JWT token signing key should be non-empty after Base64 decoding!");
|
throw new DataValidationException("JWT token signing key should be non-empty after Base64 decoding!");
|
||||||
}
|
}
|
||||||
if (decodedKey.length * Byte.SIZE < 256) {
|
if (decodedKey.length * Byte.SIZE < 256 && !TOKEN_SIGNING_KEY_DEFAULT.equals(jwtSettings.getTokenSigningKey())) {
|
||||||
throw new DataValidationException("JWT token signing key should be a Base64 encoded string representing at least 256 bits of data!");
|
throw new DataValidationException("JWT token signing key should be a Base64 encoded string representing at least 256 bits of data!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,14 +17,22 @@ package org.thingsboard.server.controller;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.thingsboard.common.util.JacksonUtil;
|
import org.thingsboard.common.util.JacksonUtil;
|
||||||
import org.thingsboard.rule.engine.api.MailService;
|
import org.thingsboard.rule.engine.api.MailService;
|
||||||
import org.thingsboard.server.common.data.AdminSettings;
|
import org.thingsboard.server.common.data.AdminSettings;
|
||||||
|
import org.thingsboard.server.config.jwt.JwtSettings;
|
||||||
|
import org.thingsboard.server.config.jwt.JwtSettingsService;
|
||||||
import org.thingsboard.server.service.mail.DefaultMailService;
|
import org.thingsboard.server.service.mail.DefaultMailService;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.hamcrest.Matchers.notNullValue;
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
@ -32,8 +40,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public abstract class BaseAdminControllerTest extends AbstractControllerTest {
|
public abstract class BaseAdminControllerTest extends AbstractControllerTest {
|
||||||
|
final JwtSettings defaultJwtSettings = new JwtSettings(9000, "thingsboard.io", "thingsboardDefaultSigningKey", 604800);
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
MailService mailService;
|
MailService mailService;
|
||||||
@ -45,23 +54,23 @@ public abstract class BaseAdminControllerTest extends AbstractControllerTest {
|
|||||||
public void testFindAdminSettingsByKey() throws Exception {
|
public void testFindAdminSettingsByKey() throws Exception {
|
||||||
loginSysAdmin();
|
loginSysAdmin();
|
||||||
doGet("/api/admin/settings/general")
|
doGet("/api/admin/settings/general")
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(content().contentType(contentType))
|
.andExpect(content().contentType(contentType))
|
||||||
.andExpect(jsonPath("$.id", notNullValue()))
|
.andExpect(jsonPath("$.id", notNullValue()))
|
||||||
.andExpect(jsonPath("$.key", is("general")))
|
.andExpect(jsonPath("$.key", is("general")))
|
||||||
.andExpect(jsonPath("$.jsonValue.baseUrl", is("http://localhost:8080")));
|
.andExpect(jsonPath("$.jsonValue.baseUrl", is("http://localhost:8080")));
|
||||||
|
|
||||||
doGet("/api/admin/settings/mail")
|
doGet("/api/admin/settings/mail")
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(content().contentType(contentType))
|
.andExpect(content().contentType(contentType))
|
||||||
.andExpect(jsonPath("$.id", notNullValue()))
|
.andExpect(jsonPath("$.id", notNullValue()))
|
||||||
.andExpect(jsonPath("$.key", is("mail")))
|
.andExpect(jsonPath("$.key", is("mail")))
|
||||||
.andExpect(jsonPath("$.jsonValue.smtpProtocol", is("smtp")))
|
.andExpect(jsonPath("$.jsonValue.smtpProtocol", is("smtp")))
|
||||||
.andExpect(jsonPath("$.jsonValue.smtpHost", is("localhost")))
|
.andExpect(jsonPath("$.jsonValue.smtpHost", is("localhost")))
|
||||||
.andExpect(jsonPath("$.jsonValue.smtpPort", is("25")));
|
.andExpect(jsonPath("$.jsonValue.smtpPort", is("25")));
|
||||||
|
|
||||||
doGet("/api/admin/settings/unknown")
|
doGet("/api/admin/settings/unknown")
|
||||||
.andExpect(status().isNotFound());
|
.andExpect(status().isNotFound());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,15 +86,15 @@ public abstract class BaseAdminControllerTest extends AbstractControllerTest {
|
|||||||
doPost("/api/admin/settings", adminSettings).andExpect(status().isOk());
|
doPost("/api/admin/settings", adminSettings).andExpect(status().isOk());
|
||||||
|
|
||||||
doGet("/api/admin/settings/general")
|
doGet("/api/admin/settings/general")
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(content().contentType(contentType))
|
.andExpect(content().contentType(contentType))
|
||||||
.andExpect(jsonPath("$.jsonValue.baseUrl", is("http://myhost.org")));
|
.andExpect(jsonPath("$.jsonValue.baseUrl", is("http://myhost.org")));
|
||||||
|
|
||||||
((ObjectNode) jsonValue).put("baseUrl", "http://localhost:8080");
|
((ObjectNode) jsonValue).put("baseUrl", "http://localhost:8080");
|
||||||
adminSettings.setJsonValue(jsonValue);
|
adminSettings.setJsonValue(jsonValue);
|
||||||
|
|
||||||
doPost("/api/admin/settings", adminSettings)
|
doPost("/api/admin/settings", adminSettings)
|
||||||
.andExpect(status().isOk());
|
.andExpect(status().isOk());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -94,8 +103,8 @@ public abstract class BaseAdminControllerTest extends AbstractControllerTest {
|
|||||||
AdminSettings adminSettings = doGet("/api/admin/settings/mail", AdminSettings.class);
|
AdminSettings adminSettings = doGet("/api/admin/settings/mail", AdminSettings.class);
|
||||||
adminSettings.setKey(null);
|
adminSettings.setKey(null);
|
||||||
doPost("/api/admin/settings", adminSettings)
|
doPost("/api/admin/settings", adminSettings)
|
||||||
.andExpect(status().isBadRequest())
|
.andExpect(status().isBadRequest())
|
||||||
.andExpect(statusReason(containsString("Key should be specified")));
|
.andExpect(statusReason(containsString("Key should be specified")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -104,8 +113,8 @@ public abstract class BaseAdminControllerTest extends AbstractControllerTest {
|
|||||||
AdminSettings adminSettings = doGet("/api/admin/settings/mail", AdminSettings.class);
|
AdminSettings adminSettings = doGet("/api/admin/settings/mail", AdminSettings.class);
|
||||||
adminSettings.setKey("newKey");
|
adminSettings.setKey("newKey");
|
||||||
doPost("/api/admin/settings", adminSettings)
|
doPost("/api/admin/settings", adminSettings)
|
||||||
.andExpect(status().isBadRequest())
|
.andExpect(status().isBadRequest())
|
||||||
.andExpect(statusReason(containsString("is prohibited")));
|
.andExpect(statusReason(containsString("is prohibited")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -113,7 +122,7 @@ public abstract class BaseAdminControllerTest extends AbstractControllerTest {
|
|||||||
loginSysAdmin();
|
loginSysAdmin();
|
||||||
AdminSettings adminSettings = doGet("/api/admin/settings/mail", AdminSettings.class);
|
AdminSettings adminSettings = doGet("/api/admin/settings/mail", AdminSettings.class);
|
||||||
doPost("/api/admin/settings/testMail", adminSettings)
|
doPost("/api/admin/settings/testMail", adminSettings)
|
||||||
.andExpect(status().isOk());
|
.andExpect(status().isOk());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -139,4 +148,48 @@ public abstract class BaseAdminControllerTest extends AbstractControllerTest {
|
|||||||
doPost("/api/admin/settings/testMail", adminSettings).andExpect(status().is5xxServerError());
|
doPost("/api/admin/settings/testMail", adminSettings).andExpect(status().is5xxServerError());
|
||||||
Mockito.doNothing().when(mailService).sendTestMail(Mockito.any(), Mockito.any());
|
Mockito.doNothing().when(mailService).sendTestMail(Mockito.any(), Mockito.any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void resetJwtSettingsToDefault() throws Exception {
|
||||||
|
loginSysAdmin();
|
||||||
|
doPost("/api/admin/jwtSettings", defaultJwtSettings).andExpect(status().isOk()); // jwt test scenarios are always started from
|
||||||
|
loginTenantAdmin();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetAndSaveDefaultJwtSettings() throws Exception {
|
||||||
|
JwtSettings jwtSettings;
|
||||||
|
loginSysAdmin();
|
||||||
|
|
||||||
|
jwtSettings = doGet("/api/admin/jwtSettings", JwtSettings.class);
|
||||||
|
assertThat(jwtSettings).isEqualTo(defaultJwtSettings);
|
||||||
|
|
||||||
|
doPost("/api/admin/jwtSettings", jwtSettings).andExpect(status().isOk());
|
||||||
|
|
||||||
|
jwtSettings = doGet("/api/admin/jwtSettings", JwtSettings.class);
|
||||||
|
assertThat(jwtSettings).isEqualTo(defaultJwtSettings);
|
||||||
|
|
||||||
|
resetJwtSettingsToDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateJwtSettings() throws Exception {
|
||||||
|
loginSysAdmin();
|
||||||
|
|
||||||
|
JwtSettings jwtSettings = doGet("/api/admin/jwtSettings", JwtSettings.class);
|
||||||
|
assertThat(jwtSettings).isEqualTo(defaultJwtSettings);
|
||||||
|
|
||||||
|
jwtSettings.setTokenSigningKey(Base64.getEncoder().encodeToString(
|
||||||
|
RandomStringUtils.randomAlphanumeric(256 / Byte.SIZE).getBytes(StandardCharsets.UTF_8)));
|
||||||
|
|
||||||
|
doPost("/api/admin/jwtSettings", jwtSettings).andExpect(status().isOk());
|
||||||
|
|
||||||
|
doGet("/api/admin/jwtSettings").andExpect(status().isUnauthorized()); //the old JWT token does not work after signing key was changed!
|
||||||
|
|
||||||
|
loginSysAdmin();
|
||||||
|
JwtSettings newJwtSettings = doGet("/api/admin/jwtSettings", JwtSettings.class);
|
||||||
|
assertThat(jwtSettings).isEqualTo(newJwtSettings);
|
||||||
|
|
||||||
|
resetJwtSettingsToDefault();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user