Added base impl for OAuth-2
This commit is contained in:
parent
08ab9752fc
commit
7fc46010b7
@ -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>
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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" />-->
|
||||
|
||||
@ -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
15
pom.xml
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user