Added base impl for OAuth-2

This commit is contained in:
Volodymyr Babak 2020-04-29 16:37:03 +03:00 committed by Andrew Shvayka
parent 08ab9752fc
commit 7fc46010b7
9 changed files with 295 additions and 2 deletions

View File

@ -132,6 +132,18 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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<String, String> tokenMap = new HashMap<String, String>();
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;
}
}
}

View File

@ -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;

View File

@ -26,6 +26,7 @@
</appender>
<logger name="org.thingsboard.server" level="INFO" />
<logger name="org.springframework.security" level="DEBUG" />
<logger name="akka" level="INFO" />
<!-- <logger name="org.thingsboard.server.service.queue" level="TRACE" />-->

View File

@ -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:

15
pom.xml
View File

@ -458,6 +458,21 @@
<artifactId>spring-boot-starter-security</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>

View File

@ -47,6 +47,7 @@
</div>
</div>
<md-button class="md-raised" type="submit">{{ 'login.login' | translate }}</md-button>
<a href="oauth2/authorization/A">OAUTH2 LOGIN</a>
</div>
</form>
</md-card-content>