diff --git a/application/pom.xml b/application/pom.xml
index 21a319491d..55009709d1 100644
--- a/application/pom.xml
+++ b/application/pom.xml
@@ -132,6 +132,18 @@
org.springframework.boot
spring-boot-starter-websocket
+
+ org.springframework.cloud
+ spring-cloud-starter-oauth2
+
+
+ org.springframework.security
+ spring-security-oauth2-client
+
+
+ org.springframework.security
+ spring-security-oauth2-jose
+
io.jsonwebtoken
jjwt
diff --git a/application/src/main/java/org/thingsboard/server/config/ThingsboardOAuth2Configuration.java b/application/src/main/java/org/thingsboard/server/config/ThingsboardOAuth2Configuration.java
new file mode 100644
index 0000000000..45048fddf3
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardOAuth2Configuration.java
@@ -0,0 +1,81 @@
+/**
+ * Copyright © 2016-2020 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.oauth2.client.registration.ClientRegistration;
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
+import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
+
+import java.util.Collections;
+
+@ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true")
+@Configuration
+public class ThingsboardOAuth2Configuration {
+
+ @Value("${security.oauth2.registrationId}")
+ private String registrationId;
+ @Value("${security.oauth2.userNameAttributeName}")
+ private String userNameAttributeName;
+
+ @Value("${security.oauth2.client.clientId}")
+ private String clientId;
+ @Value("${security.oauth2.client.clientName}")
+ private String clientName;
+ @Value("${security.oauth2.client.clientSecret}")
+ private String clientSecret;
+ @Value("${security.oauth2.client.accessTokenUri}")
+ private String accessTokenUri;
+ @Value("${security.oauth2.client.authorizationUri}")
+ private String authorizationUri;
+ @Value("${security.oauth2.client.redirectUriTemplate}")
+ private String redirectUriTemplate;
+ @Value("${security.oauth2.client.scope}")
+ private String scope;
+ @Value("${security.oauth2.client.jwkSetUri}")
+ private String jwkSetUri;
+ @Value("${security.oauth2.client.authorizationGrantType}")
+ private String authorizationGrantType;
+ @Value("${security.oauth2.client.clientAuthenticationMethod}")
+ private String clientAuthenticationMethod;
+
+ @Value("${security.oauth2.resource.userInfoUri}")
+ private String userInfoUri;
+
+ @Bean
+ public ClientRegistrationRepository clientRegistrationRepository() {
+ ClientRegistration registration = ClientRegistration.withRegistrationId(registrationId)
+ .clientId(clientId)
+ .authorizationUri(authorizationUri)
+ .clientSecret(clientSecret)
+ .tokenUri(accessTokenUri)
+ .redirectUriTemplate(redirectUriTemplate)
+ .scope(scope.split(","))
+ .clientName(clientName)
+ .authorizationGrantType(new AuthorizationGrantType(authorizationGrantType))
+ .userInfoUri(userInfoUri)
+ .userNameAttributeName(userNameAttributeName)
+ .jwkSetUri(jwkSetUri)
+ .clientAuthenticationMethod(new ClientAuthenticationMethod(clientAuthenticationMethod))
+ .build();
+ return new InMemoryClientRegistrationRepository(Collections.singletonList(registration));
+ }
+}
\ No newline at end of file
diff --git a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
index 53876f4040..36490b5166 100644
--- a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
+++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
@@ -18,6 +18,8 @@ package org.thingsboard.server.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Required;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Bean;
@@ -73,12 +75,25 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
public static final String WS_TOKEN_BASED_AUTH_ENTRY_POINT = "/api/ws/**";
@Autowired private ThingsboardErrorResponseHandler restAccessDeniedHandler;
- @Autowired private AuthenticationSuccessHandler successHandler;
+
+ @Autowired(required = false)
+ @Qualifier("oauth2AuthenticationSuccessHandler")
+ private AuthenticationSuccessHandler oauth2AuthenticationSuccessHandler;
+
+ @Autowired
+ @Qualifier("defaultAuthenticationSuccessHandler")
+ private AuthenticationSuccessHandler successHandler;
+
@Autowired private AuthenticationFailureHandler failureHandler;
@Autowired private RestAuthenticationProvider restAuthenticationProvider;
@Autowired private JwtAuthenticationProvider jwtAuthenticationProvider;
@Autowired private RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider;
+ @Value("${security.oauth2.enabled}")
+ private boolean oauth2Enabled;
+ @Value("${security.oauth2.client.loginProcessingUrl}")
+ private String loginProcessingUrl;
+
@Autowired
@Qualifier("jwtHeaderTokenExtractor")
private TokenExtractor jwtHeaderTokenExtractor;
@@ -189,6 +204,11 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
.addFilterBefore(buildRefreshTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildWsJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(rateLimitProcessingFilter, UsernamePasswordAuthenticationFilter.class);
+ if (oauth2Enabled) {
+ http.oauth2Login()
+ .loginProcessingUrl(loginProcessingUrl)
+ .successHandler(oauth2AuthenticationSuccessHandler);
+ }
}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth/Oauth2AuthenticationSuccessHandler.java
new file mode 100644
index 0000000000..e99e5896e1
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth/Oauth2AuthenticationSuccessHandler.java
@@ -0,0 +1,125 @@
+/**
+ * Copyright © 2016-2020 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.security.auth.oauth;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.security.authentication.InsufficientAuthenticationException;
+import org.springframework.security.authentication.LockedException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.User;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.security.UserCredentials;
+import org.thingsboard.server.dao.user.UserService;
+import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
+import org.thingsboard.server.service.security.model.SecurityUser;
+import org.thingsboard.server.service.security.model.UserPrincipal;
+import org.thingsboard.server.service.security.model.token.JwtToken;
+import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
+import org.thingsboard.server.service.security.system.SystemSecurityService;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+@Component(value="oauth2AuthenticationSuccessHandler")
+@ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true")
+public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
+
+ private final ObjectMapper mapper;
+ private final JwtTokenFactory tokenFactory;
+ private final RefreshTokenRepository refreshTokenRepository;
+ private final SystemSecurityService systemSecurityService;
+ private final UserService userService;
+
+ @Autowired
+ public Oauth2AuthenticationSuccessHandler(final ObjectMapper mapper,
+ final JwtTokenFactory tokenFactory,
+ final RefreshTokenRepository refreshTokenRepository,
+ final UserService userService,
+ final SystemSecurityService systemSecurityService) {
+ this.mapper = mapper;
+ this.tokenFactory = tokenFactory;
+ this.refreshTokenRepository = refreshTokenRepository;
+ this.userService = userService;
+ this.systemSecurityService = systemSecurityService;
+ }
+
+ @Override
+ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
+ Object object = authentication.getPrincipal();
+
+ System.out.println(object);
+
+ // active user check
+
+ UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, "tenant@thingsboard.org");
+ SecurityUser securityUser = (SecurityUser) authenticateByUsernameAndPassword(principal,"tenant@thingsboard.org", "tenant").getPrincipal();
+
+ JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
+ JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
+
+ Map tokenMap = new HashMap();
+ tokenMap.put("token", accessToken.getToken());
+ tokenMap.put("refreshToken", refreshToken.getToken());
+
+// response.setStatus(HttpStatus.OK.value());
+// response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+// mapper.writeValue(response.getWriter(), tokenMap);
+
+ request.setAttribute("token", accessToken.getToken());
+ response.addHeader("token", accessToken.getToken());
+
+ getRedirectStrategy().sendRedirect(request, response, "http://localhost:4200/?accessToken=" + accessToken.getToken() + "&refreshToken=" + refreshToken.getToken());
+ }
+
+ private Authentication authenticateByUsernameAndPassword(UserPrincipal userPrincipal, String username, String password) {
+ User user = userService.findUserByEmail(TenantId.SYS_TENANT_ID, username);
+ if (user == null) {
+ throw new UsernameNotFoundException("User not found: " + username);
+ }
+
+ try {
+
+ UserCredentials userCredentials = userService.findUserCredentialsByUserId(TenantId.SYS_TENANT_ID, user.getId());
+ if (userCredentials == null) {
+ throw new UsernameNotFoundException("User credentials not found");
+ }
+
+ try {
+ systemSecurityService.validateUserCredentials(user.getTenantId(), userCredentials, username, password);
+ } catch (LockedException e) {
+ throw e;
+ }
+
+ if (user.getAuthority() == null)
+ throw new InsufficientAuthenticationException("User has no authority assigned");
+
+ SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled(), userPrincipal);
+ return new UsernamePasswordAuthenticationToken(securityUser, null, securityUser.getAuthorities());
+ } catch (Exception e) {
+ throw e;
+ }
+ }
+}
\ No newline at end of file
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java
index aa55818084..d26b02174e 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java
@@ -36,7 +36,7 @@ import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
-@Component
+@Component(value="defaultAuthenticationSuccessHandler")
public class RestAwareAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private final ObjectMapper mapper;
private final JwtTokenFactory tokenFactory;
diff --git a/application/src/main/resources/logback.xml b/application/src/main/resources/logback.xml
index e25fb72ccb..9b14ff06f5 100644
--- a/application/src/main/resources/logback.xml
+++ b/application/src/main/resources/logback.xml
@@ -26,6 +26,7 @@
+
diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml
index bf82c586c3..97e22fa2c6 100644
--- a/application/src/main/resources/thingsboard.yml
+++ b/application/src/main/resources/thingsboard.yml
@@ -97,6 +97,44 @@ security:
allowClaimingByDefault: "${SECURITY_CLAIM_ALLOW_CLAIMING_BY_DEFAULT:true}"
# Time allowed to claim the device in milliseconds
duration: "${SECURITY_CLAIM_DURATION:60000}" # 1 minute, note this value must equal claimDevices.timeToLiveInMinutes value
+ basic:
+ enabled: false
+ # oauth2:
+ # enabled: true
+ # registrationId: A
+ # userNameAttributeName: email
+ # client:
+ # clientName: Thingsboard Dev Test Q
+ # clientId: 5f5c0998-1d9b-4679-9610-6108fb91af2a
+ # clientSecret: h_kXVb7Ee1LgDDinix_nkAh_owWX7YCO783NNteF9AIOqlTWu2L03YoFjv5KL8yRVyx4uYAE-r_N3tFbupE8Kw
+ # accessTokenUri: https://federation-q.auth.schwarz/nidp/oauth/nam/token
+ # authorizationUri: https://federation-q.auth.schwarz/nidp/oauth/nam/authz
+ # scope: openid,profile,email,siam
+ # redirectUriTemplate: http://localhost:8080/login/oauth2/code/
+ # loginProcessingUrl: /login/oauth2/code/
+ # jwkSetUri: https://federation-q.auth.schwarz/nidp/oauth/nam/keys
+ # authorizationGrantType: authorization_code # authorization_code, implicit, refresh_token, client_credentials
+ # clientAuthenticationMethod: post # basic, post
+ # resource:
+ # userInfoUri: https://federation-q.auth.schwarz/nidp/oauth/nam/userinfo
+ oauth2:
+ enabled: true
+ registrationId: A
+ userNameAttributeName: email
+ client:
+ clientName: Test app
+ clientId: dVH9reqyqiXIG7M2wmamb0ySue8zaM4g
+ clientSecret: EYAfAGxwkwoeYnb2o2cDgaWZB5k97OStpZQPPvcMMD-SVH2BuughTGeBazXtF5I6
+ accessTokenUri: https://dev-r9m8ht0k.auth0.com/oauth/token
+ authorizationUri: https://dev-r9m8ht0k.auth0.com/authorize
+ scope: openid,profile,email
+ redirectUriTemplate: http://localhost:8080/login/oauth2/code/
+ loginProcessingUrl: /login/oauth2/code/
+ jwkSetUri: https://dev-r9m8ht0k.auth0.com/.well-known/jwks.json
+ authorizationGrantType: authorization_code # authorization_code, implicit, refresh_token, client_credentials
+ clientAuthenticationMethod: post # basic, post
+ resource:
+ userInfoUri: https://dev-r9m8ht0k.auth0.com/userinfo
# Dashboard parameters
dashboard:
diff --git a/pom.xml b/pom.xml
index 494a43cb56..f263307538 100755
--- a/pom.xml
+++ b/pom.xml
@@ -458,6 +458,21 @@
spring-boot-starter-security
${spring-boot.version}
+
+ org.springframework.cloud
+ spring-cloud-starter-oauth2
+ ${spring-boot.version}
+
+
+ org.springframework.security
+ spring-security-oauth2-client
+ ${spring.version}
+
+
+ org.springframework.security
+ spring-security-oauth2-jose
+ ${spring.version}
+
org.springframework.boot
spring-boot-starter-web
diff --git a/ui/src/app/login/login.tpl.html b/ui/src/app/login/login.tpl.html
index 6e9d7d8977..409f5cd4ad 100644
--- a/ui/src/app/login/login.tpl.html
+++ b/ui/src/app/login/login.tpl.html
@@ -47,6 +47,7 @@
{{ 'login.login' | translate }}
+ OAUTH2 LOGIN