Commit 7fc46010b790f0453a14a70ffba77383351e8734

Authored by Volodymyr Babak
Committed by Andrew Shvayka
1 parent 08ab9752

Added base impl for OAuth-2

... ... @@ -133,6 +133,18 @@
133 133 <artifactId>spring-boot-starter-websocket</artifactId>
134 134 </dependency>
135 135 <dependency>
  136 + <groupId>org.springframework.cloud</groupId>
  137 + <artifactId>spring-cloud-starter-oauth2</artifactId>
  138 + </dependency>
  139 + <dependency>
  140 + <groupId>org.springframework.security</groupId>
  141 + <artifactId>spring-security-oauth2-client</artifactId>
  142 + </dependency>
  143 + <dependency>
  144 + <groupId>org.springframework.security</groupId>
  145 + <artifactId>spring-security-oauth2-jose</artifactId>
  146 + </dependency>
  147 + <dependency>
136 148 <groupId>io.jsonwebtoken</groupId>
137 149 <artifactId>jjwt</artifactId>
138 150 </dependency>
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.config;
  17 +
  18 +import org.springframework.beans.factory.annotation.Value;
  19 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  20 +import org.springframework.context.annotation.Bean;
  21 +import org.springframework.context.annotation.Configuration;
  22 +import org.springframework.security.oauth2.client.registration.ClientRegistration;
  23 +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
  24 +import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
  25 +import org.springframework.security.oauth2.core.AuthorizationGrantType;
  26 +import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
  27 +
  28 +import java.util.Collections;
  29 +
  30 +@ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true")
  31 +@Configuration
  32 +public class ThingsboardOAuth2Configuration {
  33 +
  34 + @Value("${security.oauth2.registrationId}")
  35 + private String registrationId;
  36 + @Value("${security.oauth2.userNameAttributeName}")
  37 + private String userNameAttributeName;
  38 +
  39 + @Value("${security.oauth2.client.clientId}")
  40 + private String clientId;
  41 + @Value("${security.oauth2.client.clientName}")
  42 + private String clientName;
  43 + @Value("${security.oauth2.client.clientSecret}")
  44 + private String clientSecret;
  45 + @Value("${security.oauth2.client.accessTokenUri}")
  46 + private String accessTokenUri;
  47 + @Value("${security.oauth2.client.authorizationUri}")
  48 + private String authorizationUri;
  49 + @Value("${security.oauth2.client.redirectUriTemplate}")
  50 + private String redirectUriTemplate;
  51 + @Value("${security.oauth2.client.scope}")
  52 + private String scope;
  53 + @Value("${security.oauth2.client.jwkSetUri}")
  54 + private String jwkSetUri;
  55 + @Value("${security.oauth2.client.authorizationGrantType}")
  56 + private String authorizationGrantType;
  57 + @Value("${security.oauth2.client.clientAuthenticationMethod}")
  58 + private String clientAuthenticationMethod;
  59 +
  60 + @Value("${security.oauth2.resource.userInfoUri}")
  61 + private String userInfoUri;
  62 +
  63 + @Bean
  64 + public ClientRegistrationRepository clientRegistrationRepository() {
  65 + ClientRegistration registration = ClientRegistration.withRegistrationId(registrationId)
  66 + .clientId(clientId)
  67 + .authorizationUri(authorizationUri)
  68 + .clientSecret(clientSecret)
  69 + .tokenUri(accessTokenUri)
  70 + .redirectUriTemplate(redirectUriTemplate)
  71 + .scope(scope.split(","))
  72 + .clientName(clientName)
  73 + .authorizationGrantType(new AuthorizationGrantType(authorizationGrantType))
  74 + .userInfoUri(userInfoUri)
  75 + .userNameAttributeName(userNameAttributeName)
  76 + .jwkSetUri(jwkSetUri)
  77 + .clientAuthenticationMethod(new ClientAuthenticationMethod(clientAuthenticationMethod))
  78 + .build();
  79 + return new InMemoryClientRegistrationRepository(Collections.singletonList(registration));
  80 + }
  81 +}
\ No newline at end of file
... ...
... ... @@ -18,6 +18,8 @@ package org.thingsboard.server.config;
18 18 import com.fasterxml.jackson.databind.ObjectMapper;
19 19 import org.springframework.beans.factory.annotation.Autowired;
20 20 import org.springframework.beans.factory.annotation.Qualifier;
  21 +import org.springframework.beans.factory.annotation.Required;
  22 +import org.springframework.beans.factory.annotation.Value;
21 23 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
22 24 import org.springframework.boot.autoconfigure.security.SecurityProperties;
23 25 import org.springframework.context.annotation.Bean;
... ... @@ -73,12 +75,25 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
73 75 public static final String WS_TOKEN_BASED_AUTH_ENTRY_POINT = "/api/ws/**";
74 76
75 77 @Autowired private ThingsboardErrorResponseHandler restAccessDeniedHandler;
76   - @Autowired private AuthenticationSuccessHandler successHandler;
  78 +
  79 + @Autowired(required = false)
  80 + @Qualifier("oauth2AuthenticationSuccessHandler")
  81 + private AuthenticationSuccessHandler oauth2AuthenticationSuccessHandler;
  82 +
  83 + @Autowired
  84 + @Qualifier("defaultAuthenticationSuccessHandler")
  85 + private AuthenticationSuccessHandler successHandler;
  86 +
77 87 @Autowired private AuthenticationFailureHandler failureHandler;
78 88 @Autowired private RestAuthenticationProvider restAuthenticationProvider;
79 89 @Autowired private JwtAuthenticationProvider jwtAuthenticationProvider;
80 90 @Autowired private RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider;
81 91
  92 + @Value("${security.oauth2.enabled}")
  93 + private boolean oauth2Enabled;
  94 + @Value("${security.oauth2.client.loginProcessingUrl}")
  95 + private String loginProcessingUrl;
  96 +
82 97 @Autowired
83 98 @Qualifier("jwtHeaderTokenExtractor")
84 99 private TokenExtractor jwtHeaderTokenExtractor;
... ... @@ -189,6 +204,11 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
189 204 .addFilterBefore(buildRefreshTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
190 205 .addFilterBefore(buildWsJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
191 206 .addFilterAfter(rateLimitProcessingFilter, UsernamePasswordAuthenticationFilter.class);
  207 + if (oauth2Enabled) {
  208 + http.oauth2Login()
  209 + .loginProcessingUrl(loginProcessingUrl)
  210 + .successHandler(oauth2AuthenticationSuccessHandler);
  211 + }
192 212 }
193 213
194 214
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.security.auth.oauth;
  17 +
  18 +import com.fasterxml.jackson.databind.ObjectMapper;
  19 +import org.springframework.beans.factory.annotation.Autowired;
  20 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  21 +import org.springframework.security.authentication.InsufficientAuthenticationException;
  22 +import org.springframework.security.authentication.LockedException;
  23 +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  24 +import org.springframework.security.core.Authentication;
  25 +import org.springframework.security.core.userdetails.UsernameNotFoundException;
  26 +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
  27 +import org.springframework.stereotype.Component;
  28 +import org.thingsboard.server.common.data.User;
  29 +import org.thingsboard.server.common.data.id.TenantId;
  30 +import org.thingsboard.server.common.data.security.UserCredentials;
  31 +import org.thingsboard.server.dao.user.UserService;
  32 +import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
  33 +import org.thingsboard.server.service.security.model.SecurityUser;
  34 +import org.thingsboard.server.service.security.model.UserPrincipal;
  35 +import org.thingsboard.server.service.security.model.token.JwtToken;
  36 +import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
  37 +import org.thingsboard.server.service.security.system.SystemSecurityService;
  38 +
  39 +import javax.servlet.ServletException;
  40 +import javax.servlet.http.HttpServletRequest;
  41 +import javax.servlet.http.HttpServletResponse;
  42 +import java.io.IOException;
  43 +import java.util.HashMap;
  44 +import java.util.Map;
  45 +
  46 +@Component(value="oauth2AuthenticationSuccessHandler")
  47 +@ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true")
  48 +public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
  49 +
  50 + private final ObjectMapper mapper;
  51 + private final JwtTokenFactory tokenFactory;
  52 + private final RefreshTokenRepository refreshTokenRepository;
  53 + private final SystemSecurityService systemSecurityService;
  54 + private final UserService userService;
  55 +
  56 + @Autowired
  57 + public Oauth2AuthenticationSuccessHandler(final ObjectMapper mapper,
  58 + final JwtTokenFactory tokenFactory,
  59 + final RefreshTokenRepository refreshTokenRepository,
  60 + final UserService userService,
  61 + final SystemSecurityService systemSecurityService) {
  62 + this.mapper = mapper;
  63 + this.tokenFactory = tokenFactory;
  64 + this.refreshTokenRepository = refreshTokenRepository;
  65 + this.userService = userService;
  66 + this.systemSecurityService = systemSecurityService;
  67 + }
  68 +
  69 + @Override
  70 + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
  71 + Object object = authentication.getPrincipal();
  72 +
  73 + System.out.println(object);
  74 +
  75 + // active user check
  76 +
  77 + UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, "tenant@thingsboard.org");
  78 + SecurityUser securityUser = (SecurityUser) authenticateByUsernameAndPassword(principal,"tenant@thingsboard.org", "tenant").getPrincipal();
  79 +
  80 + JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
  81 + JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
  82 +
  83 + Map<String, String> tokenMap = new HashMap<String, String>();
  84 + tokenMap.put("token", accessToken.getToken());
  85 + tokenMap.put("refreshToken", refreshToken.getToken());
  86 +
  87 +// response.setStatus(HttpStatus.OK.value());
  88 +// response.setContentType(MediaType.APPLICATION_JSON_VALUE);
  89 +// mapper.writeValue(response.getWriter(), tokenMap);
  90 +
  91 + request.setAttribute("token", accessToken.getToken());
  92 + response.addHeader("token", accessToken.getToken());
  93 +
  94 + getRedirectStrategy().sendRedirect(request, response, "http://localhost:4200/?accessToken=" + accessToken.getToken() + "&refreshToken=" + refreshToken.getToken());
  95 + }
  96 +
  97 + private Authentication authenticateByUsernameAndPassword(UserPrincipal userPrincipal, String username, String password) {
  98 + User user = userService.findUserByEmail(TenantId.SYS_TENANT_ID, username);
  99 + if (user == null) {
  100 + throw new UsernameNotFoundException("User not found: " + username);
  101 + }
  102 +
  103 + try {
  104 +
  105 + UserCredentials userCredentials = userService.findUserCredentialsByUserId(TenantId.SYS_TENANT_ID, user.getId());
  106 + if (userCredentials == null) {
  107 + throw new UsernameNotFoundException("User credentials not found");
  108 + }
  109 +
  110 + try {
  111 + systemSecurityService.validateUserCredentials(user.getTenantId(), userCredentials, username, password);
  112 + } catch (LockedException e) {
  113 + throw e;
  114 + }
  115 +
  116 + if (user.getAuthority() == null)
  117 + throw new InsufficientAuthenticationException("User has no authority assigned");
  118 +
  119 + SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled(), userPrincipal);
  120 + return new UsernamePasswordAuthenticationToken(securityUser, null, securityUser.getAuthorities());
  121 + } catch (Exception e) {
  122 + throw e;
  123 + }
  124 + }
  125 +}
\ No newline at end of file
... ...
... ... @@ -36,7 +36,7 @@ import java.io.IOException;
36 36 import java.util.HashMap;
37 37 import java.util.Map;
38 38
39   -@Component
  39 +@Component(value="defaultAuthenticationSuccessHandler")
40 40 public class RestAwareAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
41 41 private final ObjectMapper mapper;
42 42 private final JwtTokenFactory tokenFactory;
... ...
... ... @@ -26,6 +26,7 @@
26 26 </appender>
27 27
28 28 <logger name="org.thingsboard.server" level="INFO" />
  29 + <logger name="org.springframework.security" level="DEBUG" />
29 30 <logger name="akka" level="INFO" />
30 31
31 32 <!-- <logger name="org.thingsboard.server.service.queue" level="TRACE" />-->
... ...
... ... @@ -97,6 +97,44 @@ security:
97 97 allowClaimingByDefault: "${SECURITY_CLAIM_ALLOW_CLAIMING_BY_DEFAULT:true}"
98 98 # Time allowed to claim the device in milliseconds
99 99 duration: "${SECURITY_CLAIM_DURATION:60000}" # 1 minute, note this value must equal claimDevices.timeToLiveInMinutes value
  100 + basic:
  101 + enabled: false
  102 + # oauth2:
  103 + # enabled: true
  104 + # registrationId: A
  105 + # userNameAttributeName: email
  106 + # client:
  107 + # clientName: Thingsboard Dev Test Q
  108 + # clientId: 5f5c0998-1d9b-4679-9610-6108fb91af2a
  109 + # clientSecret: h_kXVb7Ee1LgDDinix_nkAh_owWX7YCO783NNteF9AIOqlTWu2L03YoFjv5KL8yRVyx4uYAE-r_N3tFbupE8Kw
  110 + # accessTokenUri: https://federation-q.auth.schwarz/nidp/oauth/nam/token
  111 + # authorizationUri: https://federation-q.auth.schwarz/nidp/oauth/nam/authz
  112 + # scope: openid,profile,email,siam
  113 + # redirectUriTemplate: http://localhost:8080/login/oauth2/code/
  114 + # loginProcessingUrl: /login/oauth2/code/
  115 + # jwkSetUri: https://federation-q.auth.schwarz/nidp/oauth/nam/keys
  116 + # authorizationGrantType: authorization_code # authorization_code, implicit, refresh_token, client_credentials
  117 + # clientAuthenticationMethod: post # basic, post
  118 + # resource:
  119 + # userInfoUri: https://federation-q.auth.schwarz/nidp/oauth/nam/userinfo
  120 + oauth2:
  121 + enabled: true
  122 + registrationId: A
  123 + userNameAttributeName: email
  124 + client:
  125 + clientName: Test app
  126 + clientId: dVH9reqyqiXIG7M2wmamb0ySue8zaM4g
  127 + clientSecret: EYAfAGxwkwoeYnb2o2cDgaWZB5k97OStpZQPPvcMMD-SVH2BuughTGeBazXtF5I6
  128 + accessTokenUri: https://dev-r9m8ht0k.auth0.com/oauth/token
  129 + authorizationUri: https://dev-r9m8ht0k.auth0.com/authorize
  130 + scope: openid,profile,email
  131 + redirectUriTemplate: http://localhost:8080/login/oauth2/code/
  132 + loginProcessingUrl: /login/oauth2/code/
  133 + jwkSetUri: https://dev-r9m8ht0k.auth0.com/.well-known/jwks.json
  134 + authorizationGrantType: authorization_code # authorization_code, implicit, refresh_token, client_credentials
  135 + clientAuthenticationMethod: post # basic, post
  136 + resource:
  137 + userInfoUri: https://dev-r9m8ht0k.auth0.com/userinfo
100 138
101 139 # Dashboard parameters
102 140 dashboard:
... ...
... ... @@ -459,6 +459,21 @@
459 459 <version>${spring-boot.version}</version>
460 460 </dependency>
461 461 <dependency>
  462 + <groupId>org.springframework.cloud</groupId>
  463 + <artifactId>spring-cloud-starter-oauth2</artifactId>
  464 + <version>${spring-boot.version}</version>
  465 + </dependency>
  466 + <dependency>
  467 + <groupId>org.springframework.security</groupId>
  468 + <artifactId>spring-security-oauth2-client</artifactId>
  469 + <version>${spring.version}</version>
  470 + </dependency>
  471 + <dependency>
  472 + <groupId>org.springframework.security</groupId>
  473 + <artifactId>spring-security-oauth2-jose</artifactId>
  474 + <version>${spring.version}</version>
  475 + </dependency>
  476 + <dependency>
462 477 <groupId>org.springframework.boot</groupId>
463 478 <artifactId>spring-boot-starter-web</artifactId>
464 479 <version>${spring-boot.version}</version>
... ...
... ... @@ -47,6 +47,7 @@
47 47 </div>
48 48 </div>
49 49 <md-button class="md-raised" type="submit">{{ 'login.login' | translate }}</md-button>
  50 + <a href="oauth2/authorization/A">OAUTH2 LOGIN</a>
50 51 </div>
51 52 </form>
52 53 </md-card-content>
... ...