ThingsBoard源码中的Security

ThingsBoard 源码地址:https://github.com/thingsboard/thingsboard,启动成功之后,使用系统管理员: [email protected] / sysadmin 进行登陆。

设置和安全

登陆系统之后,点击左边的设置和安全菜单,进行相应的配置。

  1. 设置 -> 基本设置、发送邮件、通知

  2. 安全 -> 基本设置 -> 基本策略、密码策略、JWT 安全设置

  3. 安全 -> 双因素认证 -> 验证 APP、电子邮件、备份验证码、验证限制、验证码检查速率限制

  4. 安全 -> OAuth 2.0 -> 添加OAuth2.0客户端

    系统内置支持以下几种客户端。

​ 以 Google 为例,需要在 API 和服务 -> 凭据 -> OAuth 2.0 客户端 ID ,在 已获授权的重定向 URI 处添加 thingsboard 的重定向地址:http://localhost:8080/login/oauth2/code/

完成配置之后,配置的内存保证到数据库中,系统设置对应数据库表是 admin_settings

CREATE TABLE "public"."admin_settings" (
    "id" uuid NOT NULL,
    "tenant_id" uuid NOT NULL,
    "created_time" int8 NOT NULL,
    "json_value" varchar,
    "key" varchar(255),
    PRIMARY KEY ("id")
);

表中记录:

[
  {
    "id": "2fa02d60-b119-11ef-8167-dfb7a1b13a5a",
    "tenant_id": "13814000-1dd2-11b2-8080-808080808080",
    "created_time": 1733190720310,
    "json_value": "{\"baseUrl\":\"http://localhost:8080\",\"prohibitDifferentUrl\":false}",
    "key": "general"
  },
  {
    "id": "2fa29e60-b119-11ef-8167-dfb7a1b13a5a",
    "tenant_id": "13814000-1dd2-11b2-8080-808080808080",
    "created_time": 1733190720326,
    "json_value": "{\"coap\":{\"enabled\":true,\"host\":\"\",\"port\":\"5683\"},\"mqtt\":{\"enabled\":true,\"host\":\"\",\"port\":\"1883\"},\"http\":{\"enabled\":true,\"host\":\"\",\"port\":\"8080\"},\"mqtts\":{\"enabled\":false,\"host\":\"\",\"port\":\"8883\"},\"https\":{\"enabled\":false,\"host\":\"\",\"port\":\"443\"},\"coaps\":{\"enabled\":false,\"host\":\"\",\"port\":\"5684\"}}",
    "key": "connectivity"
  },
  {
    "id": "2fa18cf0-b119-11ef-8167-dfb7a1b13a5a",
    "tenant_id": "13814000-1dd2-11b2-8080-808080808080",
    "created_time": 1733190720319,
    "json_value": "{\"mailFrom\":\"xxx\",\"smtpProtocol\":\"smtps\",\"smtpHost\":\"smtp.feishu.cn\",\"smtpPort\":\"465\",\"timeout\":\"10000\",\"enableTls\":false,\"username\":\"xxx\",\"tlsVersion\":null,\"enableProxy\":false,\"providerId\":\"CUSTOM\",\"proxyHost\":\"\",\"proxyPort\":null,\"proxyUser\":\"\",\"proxyPassword\":\"\",\"enableOauth2\":false,\"clientId\":\"\",\"clientSecret\":\"\",\"providerTenantId\":\"\",\"authUri\":\"\",\"tokenUri\":\"\",\"scope\":[],\"redirectUri\":\"\",\"password\":\"wQ7ae64vj2mXUBFZ\"}",
    "key": "mail"
  },
  {
    "id": "7c8790e0-b11a-11ef-983d-d79399efbf69",
    "tenant_id": "13814000-1dd2-11b2-8080-808080808080",
    "created_time": 1733191278830,
    "json_value": "{\"passwordPolicy\":{\"minimumLength\":6,\"maximumLength\":72,\"minimumUppercaseLetters\":null,\"minimumLowercaseLetters\":null,\"minimumDigits\":null,\"minimumSpecialCharacters\":null,\"allowWhitespaces\":true,\"forceUserToResetPasswordIfNotValid\":false,\"passwordExpirationPeriodDays\":null,\"passwordReuseFrequencyDays\":null},\"maxFailedLoginAttempts\":5,\"userLockoutNotificationEmail\":null,\"mobileSecretKeyLength\":64,\"userActivationTokenTtl\":24,\"passwordResetTokenTtl\":24}",
    "key": "securitySettings"
  },
  {
    "id": "3d865ba0-b120-11ef-983d-d79399efbf69",
    "tenant_id": "13814000-1dd2-11b2-8080-808080808080",
    "created_time": 1733193750106,
    "json_value": "{\"providers\":[{\"providerType\":\"TOTP\",\"issuerName\":\"ThingsBoard\"},{\"providerType\":\"EMAIL\",\"verificationCodeLifetime\":120},{\"providerType\":\"BACKUP_CODE\",\"codesQuantity\":10}],\"minVerificationCodeSendPeriod\":30,\"verificationCodeCheckRateLimit\":\"3:900\",\"maxVerificationFailuresBeforeUserLockout\":30,\"totalAllowedTimeForVerification\":3600}",
    "key": "twoFaSettings"
  },
  {
    "id": "2fa47320-b119-11ef-8167-dfb7a1b13a5a",
    "tenant_id": "13814000-1dd2-11b2-8080-808080808080",
    "created_time": 1733190720338,
    "json_value": "{\"tokenExpirationTime\":9000,\"refreshTokenExpTime\":604800,\"tokenIssuer\":\"thingsboard.io\",\"tokenSigningKey\":\"anZjUDM5RXRHUEg2WHhNaEo0dE5pMzhiS0x2TURrMktCMkJYSDg0dUtPQ3k3ZzhnVW9yMHZpUjdUNzFYWWplOA==\"}",
    "key": "jwt"
  }
]

OAuth2 相关的表为:

  • domain

  • domain_oauth2_client

  • oauth2_client

  • oauth2_client_registration_template

账号

点击右上角三个竖点的账号按钮,进入个人账号设置页面,可以设置属性、安全和通知设置。

双因素认证可以使用以下身份验证:

  • 验证 APP。使用手机上的 Google Authenticator、Authy或Duo等应用程序进行身份验证,它将生成用于登录的验证码。
  • 电子邮件。使用您电子邮件中的验证码进行身份验证。
  • 备份验证码。这些可打印的一次性密码允许您在离开手机时登录,比如正在旅行。

个人账号的相关设置保存在以下两张表:

  • user_auth_settings
  • user_settings

登陆页面

开启 OAuht2 之后,注销登陆,进入登陆页面。当前登陆页面会通过 http://localhost:8080/api/noauth/oauth2Clients?platform=WEB POST 接口(对应的 Controller 类为 OAuth2Controller)查询开通的 OAuth2 客户端列表。返回内容如下:

[
    {
        "name": "Google",
        "icon": "google-logo",
        "url": "/oauth2/authorization/f02246b0-b121-11ef-983d-d79399efbf69"
    }
]

f02246b0-b121-11ef-983d-d79399efbf69 为 oauth2_client 主键。

在登陆页面,点击使用 Google 登陆,后台在 OAuth2AuthorizationRequestRedirectFilterdoFilterInternal 方法打个断点。

	try {
			OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request);
			if (authorizationRequest != null) {
				this.sendRedirectForAuthorization(request, response, authorizationRequest);
				return;
			}
		}
		catch (Exception ex) {
			this.unsuccessfulRedirectForAuthorization(request, response, ex);
			return;
		}

this.authorizationRequestResolver 对于的 Bean 为 CustomOAuth2AuthorizationRequestResolver,其 resolve 方法逻辑如下:

@Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
    String registrationId = this.resolveRegistrationId(request);
    String redirectUriAction = getAction(request, "login");
    String appPackage = getAppPackage(request);
    String platform = getPlatform(request);
    String appToken = getAppToken(request);
    return resolve(request, registrationId, redirectUriAction, appPackage, platform, appToken);
}
  • 首先获取 registrationId,即 f02246b0-b121-11ef-983d-d79399efbf69

  • 获取 redirectUriAction,即 login

  • 获取 appPackage,为 null

  • 获取 platform,为 null

  • 获取 appToken,为 null

  • 通过 this.clientRegistrationRepositoryfindByRegistrationId 方法 查询 ClientRegistration

  • 如果 ClientRegistration 的授权类型为 authorization_code

    • 如果 scope 包含 OPENID,则添加 nonce 参数
    • 如果 clientAuthenticationMethodnone,则添加 pkce 参数
  • 获取重定向地址,这里为 http://localhost:8080/login/oauth2/code/

  • 构建 OAuth2AuthorizationRequest 对象

    {
    	"authorizationUri": "https://accounts.google.com/o/oauth2/v2/auth",
    	"responseType": {
    		"value": "code"
    	},
    	"clientId": "385465491380-8q1fk7r47tqu6mcc9klob2c1a9sl4jmd.apps.googleusercontent.com",
    	"redirectUri": "http://localhost:8080/login/oauth2/code/",
    	"scopes": ["email", "openid", "profile"],
    	"state": "M0qcUWqzNMKfj7zHKmmvFaaZxISMS5GGDHDZv55acHE=",
    	"additionalParameters": {
    		"nonce": "zz5r7lMyPdaJ1zvAaRZvzH1P_megtjC28nVFG1Dug00"
    	},
    	"authorizationRequestUri": "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=385465491380-8q1fk7r47tqu6mcc9klob2c1a9sl4jmd.apps.googleusercontent.com&scope=email%20openid%20profile&state=M0qcUWqzNMKfj7zHKmmvFaaZxISMS5GGDHDZv55acHE%3D&redirect_uri=http://localhost:8080/login/oauth2/code/&nonce=zz5r7lMyPdaJ1zvAaRZvzH1P_megtjC28nVFG1Dug00",
    	"attributes": {
    		"registration_id": "f02246b0-b121-11ef-983d-d79399efbf69",
    		"nonce": "_toJxm0yUite6Kzjy_yfX4eKq4NtwmygCqgYJVjN83SV2q6sZtneWpBmWeNTN6ZMth8qCIyOWY1up-m_AKmcQmjf_j9HCheruxtNvQL1zTbL9dL-rwOSSonAUtAJ3BGH"
    	},
    	"grantType": {
    		"value": "authorization_code"
    	}
    }
    
  • 重定向到 google 的 OAuth2 登陆页面。

OAuth2 Client 配置

OAuth2 Client 相关配置在 ThingsboardSecurityConfiguration

@Bean
  SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
      //...
      if (oauth2Configuration != null) {
          http.oauth2Login(login -> login
                  .authorizationEndpoint(config -> config
                          .authorizationRequestRepository(httpCookieOAuth2AuthorizationRequestRepository)
                          .authorizationRequestResolver(oAuth2AuthorizationRequestResolver))
                  .loginPage("/oauth2Login")
                  .loginProcessingUrl(oauth2Configuration.getLoginProcessingUrl())
                  .successHandler(oauth2AuthenticationSuccessHandler)
                  .failureHandler(oauth2AuthenticationFailureHandler));
      }
      return http.build();
  }

oauth2Login 的 authorizationEndpoint 配置了一个 authorizationRequestRepositoryauthorizationRequestResolver

authorizationRequestRepository 对应的是 HttpCookieOAuth2AuthorizationRequestRepository,保存 request 请求地址到 cookie;authorizationRequestResolver 对应的是 CustomOAuth2AuthorizationRequestResolver

oauth2Login 的登陆页面为 /oauth2Login,处理登陆请求的地址为 oauth2Configuration.getLoginProcessingUrl(),oauth2Configuration 的配置:

security:
  oauth2:
    # Redirect URL where access code from external user management system will be processed
    loginProcessingUrl: "${SECURITY_OAUTH2_LOGIN_PROCESSING_URL:/login/oauth2/code/}"
    githubMapper:
      # The email addresses that will be mapped from the URL
      emailUrl: "${SECURITY_OAUTH2_GITHUB_MAPPER_EMAIL_URL_KEY:https://api.github.com/user/emails}"

oauth2Login 登陆成功的 handler 为 Oauth2AuthenticationSuccessHandler

  • 通过 httpCookieOAuth2AuthorizationRequestRepository 获取 OAuth2AuthorizationRequest
  • 从 authorizationRequest 属性 获取 callback_url_scheme
  • 获取 baseUrl
  • 获取 OAuth2AuthenticationToken
  • 使用 OAuth2ClientService 通过 OAuth2AuthenticationTokenauthorizedClientRegistrationId 获取 OAuth2Client
  • 使用 OAuth2AuthorizedClientService 通过 OAuth2AuthenticationTokenauthorizedClientRegistrationId 和 principal name 获取 OAuth2AuthorizedClient
  • 使用 OAuth2ClientMapperProvider 获取 OAuth2ClientMapper
  • 使用 OAuth2ClientMapper 创建 SecurityUser
  • 清除 Authentication 属性
  • 创建 JwtPair
  • 重定向到地址
  • 使用 SystemSecurityService 记录登陆成功日志

oauth2Login 登陆失败的 handler 为 Oauth2AuthenticationFailureHandler

  • 通过 httpCookieOAuth2AuthorizationRequestRepository 获取 OAuth2AuthorizationRequest
  • authorizationRequest 属性 获取 callback_url_scheme
  • 获取 baseUrl
  • httpCookieOAuth2AuthorizationRequestRepository 清除 cookie
  • 重定向到地址

所以,OAuth2 client 相关代码重点在以下类:

  • CustomOAuth2AuthorizationRequestResolver

  • OAuth2ControllerOAuth2ConfigTemplateController

  • OAuth2ClientServiceOAuth2ConfigTemplateService

    • OAuth2ClientDaoOAuth2ClientRegistrationTemplateDao
      • OAuth2ClientOAuth2ClientRegistrationTemplate
  • OAuth2AppTokenFactory

  • HybridClientRegistrationRepository

Share this post:

Related content