-
首先来说明一下 OAuth2
授权码模式
快捷登录流程:- 开发者先去Github申请一个应用,然后认证平台会给我们一个客户端Id(clientId)和密钥(clientSecret),还需要预留一个回调地址(http://localhost:8080/login/oauth2/code/)。
- 当tb使用Github登录时,由thingsboard向Github发起验证请求,跳转到认证平台的用户授权页面(https://github.com/login/oauth/authorize)。
- 如果用户确认授权,会得到一个code,然后认证平台会根据我们预留的回调地址重定向到thingsboard。然后thingsboard继续带着code去访问获取token的URI(https://github.com/login/oauth/access_token),认证平台确认无误后会发放token,从而完成认定。注意这个code只能用一次,用过即会过期。
- 这样tb要获取用户信息(https://api.github.com/user)的时候就可以带着这个token去访问相应的接口。然后根据得到的用户信息创建或者查找User。
-
安全起见,我已经将部分图片和解释进行处理,localhost:8080代表您部署thingsboard的主机IP和端口号
-
下面开始分析源码,我之前已经使用Github账号登录过。在登录页面打开开发者工具(F12),选择网络准备进行抓包
-
点击Github登录按钮,进行抓包,可以看到第一个请求地址是:
http://localhost:8080/oauth2/authorization/83720530-0654-11ec-bfe6-372e3588ed6c
,这是为了获取并跳转到Github授权网页 -
此时我们分析源码:ThingsboardSecurityConfiguration
@Override protected void configure(HttpSecurity http) throws Exception { ........................... if (oauth2Configuration != null) { /* oauth2Configuration 已经注入到IOC容器中,在 dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Configuration.java 后面会分析 */ http.oauth2Login() .authorizationEndpoint() .authorizationRequestRepository(httpCookieOAuth2AuthorizationRequestRepository) .authorizationRequestResolver(oAuth2AuthorizationRequestResolver) // oAuth2AuthorizationRequestResolver 也已经注入到IOC容器,默认的是 // CustomOAuth2AuthorizationRequestResolver 后面会分析 .and() .loginPage("/oauth2Login") // 自定义登录界面 .loginProcessingUrl(oauth2Configuration.getLoginProcessingUrl()) // oauth2Configuration.getLoginProcessingUrl() 就是/login/oauth2/code/ .successHandler(oauth2AuthenticationSuccessHandler) // 授权成功处理器 .failureHandler(oauth2AuthenticationFailureHandler); // 授权失败处理器 } }
-
下面分析OAuth2Configuration :
@Configuration // 扫描时会注入到IOC容器 @ConfigurationProperties(prefix = "security.oauth2") //从resources/thingsboard.yml中读取配置 @Data public class OAuth2Configuration { private String loginProcessingUrl; // 即/login/oauth2/code/ private Map<String, String> githubMapper; }
# 见application/resources/thingsboard.yml 第116行 oauth2: # Redirect URL where access code from external user management system will be processed loginProcessingUrl: "${SECURITY_OAUTH2_LOGIN_PROCESSING_URL:/login/oauth2/code/}" githubMapper: emailUrl: "${SECURITY_OAUTH2_GITHUB_MAPPER_EMAIL_URL_KEY:https://api.github.com/user/emails}"
-
OAuth2AuthorizationRequestResolver 是一个接口,Spring会将其实现类CustomOAuth2AuthorizationRequestResolver注入到容器中,并自动装载
-
源码:
@Service // 注入IOC容器 @Slf4j public class CustomOAuth2AuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver { private static final String DEFAULT_AUTHORIZATION_REQUEST_BASE_URI = "/oauth2/authorization"; ....... private final AntPathRequestMatcher authorizationRequestMatcher = new AntPathRequestMatcher( DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/{" + REGISTRATION_ID_URI_VARIABLE_NAME + "}"); // 拦截 /oauth2/authorization 路径的请求 ....... }
-
接下来是resolve方法,在讲resolve方法前先来解释一下ClientRegistration类:
public final class ClientRegistration implements Serializable { private static final long serialVersionUID = 540L; private String registrationId; private String clientId; private String clientSecret; private ClientAuthenticationMethod clientAuthenticationMethod; private AuthorizationGrantType authorizationGrantType; private String redirectUri; private Set<String> scopes; private ClientRegistration.ProviderDetails providerDetails; private String clientName; .......... public static final class Builder implements Serializable { private static final long serialVersionUID = 540L; private String registrationId; private String clientId; private String clientSecret; private ClientAuthenticationMethod clientAuthenticationMethod; private AuthorizationGrantType authorizationGrantType; private String redirectUri; private Set<String> scopes; private String authorizationUri; private String tokenUri; private String userInfoUri; private AuthenticationMethod userInfoAuthenticationMethod; private String userNameAttributeName; private String jwkSetUri; private String issuerUri; private Map<String, Object> configurationMetadata; private String clientName; ........... } public class ProviderDetails implements Serializable { private static final long serialVersionUID = 540L; private String authorizationUri; private String tokenUri; private ClientRegistration.ProviderDetails.UserInfoEndpoint userInfoEndpoint = new ClientRegistration.ProviderDetails.UserInfoEndpoint(); private String jwkSetUri; private String issuerUri; private Map<String, Object> configurationMetadata = Collections.emptyMap(); ........... } ........... } /** 有两个内部类,Builder是用来构建ClientRegistration对象的。主要是一些链式编程的方法来设置各个参数,最后build方法来构成一个ClientRegistration。ProviderDetails类主要是用来获取各种需要的参数。比如我们要获取Github授权地址(Authorization_url)应当使用clientRegistration.getProviderDetails().getAuthorizationUri()方法;要获取TokenUri(code换取access_token的地址)应该是clientRegistration.getProviderDetails().getTokenUri()**/
-
下面来介绍CustomOAuth2AuthorizationRequestResolver的resolve方法。前面AntPathRequestMatcher拦截到/oauth2/authorization路径的登录请求,下面来重定向到获取授权地址以获取code
@Override public OAuth2AuthorizationRequest resolve(HttpServletRequest request) { String registrationId = this.resolveRegistrationId(request); /* 我们捕获的第一个请求: http://localhost:8080/oauth2/authorization/83720530-0654- 11ec-bfe6-372e3588ed6c 。 这里registrationId = 83720530-0654-11ec-bfe6-372e3588ed6c */ String redirectUriAction = getAction(request, "login"); // redirectUriAction = "login" String appPackage = getAppPackage(request); String appToken = getAppToken(request); // appPackage = appToken = null; return resolve(request, registrationId, redirectUriAction, appPackage, appToken); } //该方法用于向指定Authorization_URL发起一次OAuth2请求 @SuppressWarnings("deprecation") private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId, String redirectUriAction, String appPackage, String appToken) { if (registrationId == null) { return null; } ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId); // ClientRegistration 是用于存放OAuth记录的实体类,包含了一些client_id,client_secret等相关的参数,后面解释 if (clientRegistration == null) { throw new IllegalArgumentException("Invalid Client Registration with Id: " + registrationId); } Map<String, Object> attributes = new HashMap<>(); attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()); // OAuth2ParameterNames.REGISTRATION_ID = "registration_id" //appPackage = null ,下面的if不会执行 /** if (!StringUtils.isEmpty(appPackage)) { if (StringUtils.isEmpty(appToken)) { throw new IllegalArgumentException("Invalid application token."); } else { String appSecret = this.oAuth2Service.findAppSecret(UUID.fromString(registrationId), appPackage); if (StringUtils.isEmpty(appSecret)) { throw new IllegalArgumentException("Invalid package: " + appPackage + ". No application secret found for Client Registration with given application package."); } String callbackUrlScheme = this.oAuth2AppTokenFactory.validateTokenAndGetCallbackUrlScheme(appPackage, appToken, appSecret); attributes.put(TbOAuth2ParameterNames.CALLBACK_URL_SCHEME, callbackUrlScheme); } } **/ OAuth2AuthorizationRequest.Builder builder; if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) { // AuthorizationGrantType.AUTHORIZATION_CODE = new AuthorizationGrantType("authorization_code"); builder = OAuth2AuthorizationRequest.authorizationCode(); // 下面添加一些附加信息 Map<String, Object> additionalParameters = new HashMap<>(); if (!CollectionUtils.isEmpty(clientRegistration.getScopes()) && clientRegistration.getScopes().contains(OidcScopes.OPENID)) { // Section 3.1.2.1 Authentication Request - https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest // scope // REQUIRED. OpenID Connect requests MUST contain the "openid" scope value. addNonceParameters(attributes, additionalParameters); } if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())) { addPkceParameters(attributes, additionalParameters); } builder.additionalParameters(additionalParameters); } else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) { builder = OAuth2AuthorizationRequest.implicit(); } else { throw new IllegalArgumentException("Invalid Authorization Grant Type (" + clientRegistration.getAuthorizationGrantType().getValue() + ") for Client Registration with Id: " + clientRegistration.getRegistrationId()); } String redirectUriStr = expandRedirectUri(request, clientRegistration, redirectUriAction); return builder .clientId(clientRegistration.getClientId()) // OAth2 认证时在Github申请的client_id .authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri()) // Github授权链接:https://github.com/login/oauth/authorize .redirectUri(redirectUriStr) // Github授权之后重定向地址:http://localhost:8080/login/oauth2/code/ .scopes(clientRegistration.getScopes()) // 获取信息范围,cope=read:user%20user:email .state(this.stateGenerator.generateKey()) // state,这里没有具体含义。第Github认证通过之后还会原封不动返回 .attributes(attributes) .build(); // 因此,thingsboard会发送一次OAuth2请求,同时也是我们抓包抓到的第二个请求: //https://github.com/login/oauth/authorize?response_type=code&client_id=c1b40b3634e31c62d4fc&scope=read:user%20user:email&state=0orHfMSwdDQl2IB-PCPckAWxFhUgFa8-UuddUsPMueQ%3D&redirect_uri=http://localhost:8080/login/oauth2/code/ }
-
-
当Github完成授权,会将code按照redirect_URL返回给thingsboard:
http://localhost:8080/login/oauth2/code/?code=80ef8e5fdc54e35a1cce&state=0orHfMSwdDQl2IB-PCPckAWxFhUgFa8-UuddUsPMueQ=
这正是我们抓包第三次请求。然后我们在请求头中发现了返回的token(注意,为了安全考虑,由Github返回的token不可能返回给前端,这由thingsboard为当前登录的User创建的accessToken 和 refreshToken,而不是Github的accessToken):
可是这个accessToken 和 refreshToken 是哪里来的呢?继续分析源码
-
当Github授权完成后向thingsboard后端/login/oauth2/code接口发送code。后面具体操作有待研究,我的猜想是:后端拦截到这个路径的请求,再调用clientRegistration.getProviderDetails().getTokenUri()方法获取到tokenUri ,然后来根据code换取accessToken 。在源码中可以看到以下相关代码:
-
public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> extends AbstractAuthenticationFilterConfigurer<B, OAuth2LoginConfigurer<B>, OAuth2LoginAuthenticationFilter> { private final OAuth2LoginConfigurer<B>.AuthorizationEndpointConfig authorizationEndpointConfig = new OAuth2LoginConfigurer.AuthorizationEndpointConfig(); private final OAuth2LoginConfigurer<B>.TokenEndpointConfig tokenEndpointConfig = new OAuth2LoginConfigurer.TokenEndpointConfig(); private final OAuth2LoginConfigurer<B>.RedirectionEndpointConfig redirectionEndpointConfig = new OAuth2LoginConfigurer.RedirectionEndpointConfig(); private final OAuth2LoginConfigurer<B>.UserInfoEndpointConfig userInfoEndpointConfig = new OAuth2LoginConfigurer.UserInfoEndpointConfig(); private String loginPage; private String loginProcessingUrl = "/login/oauth2/code/*"; // 修改回调地址 public OAuth2LoginConfigurer<B> loginProcessingUrl(String loginProcessingUrl) { Assert.hasText(loginProcessingUrl, "loginProcessingUrl cannot be empty"); this.loginProcessingUrl = loginProcessingUrl; return this; } ......... public void init(B http) throws Exception { // 由构造器第三个参数可以见得,这是在添加一个对 /login/oauth2/code/ 路径的拦截器 OAuth2LoginAuthenticationFilter authenticationFilter = new OAuth2LoginAuthenticationFilter( OAuth2ClientConfigurerUtils.getClientRegistrationRepository((HttpSecurityBuilder)this.getBuilder()), OAuth2ClientConfigurerUtils.getAuthorizedClientRepository((HttpSecurityBuilder)this.getBuilder()), this.loginProcessingUrl); this.setAuthenticationFilter(authenticationFilter); super.loginProcessingUrl(this.loginProcessingUrl); if (this.loginPage != null) { super.loginPage(this.loginPage); // loginPage = "/oauth2Login" super.init(http); } else { Map<String, String> loginUrlToClientName = this.getLoginLinks(); if (loginUrlToClientName.size() == 1) { this.updateAuthenticationDefaults(); this.updateAccessDefaults(http); String providerLoginPage = (String)loginUrlToClientName.keySet().iterator().next(); this.registerAuthenticationEntryPoint(http, this.getLoginEntryPoint(http, providerLoginPage)); } else { super.init(http); } } OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient = this.tokenEndpointConfig.accessTokenResponseClient; if (accessTokenResponseClient == null) { accessTokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient(); } OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService = this.getOAuth2UserService(); OAuth2LoginAuthenticationProvider oauth2LoginAuthenticationProvider = new OAuth2LoginAuthenticationProvider((OAuth2AccessTokenResponseClient)accessTokenResponseClient, oauth2UserService); GrantedAuthoritiesMapper userAuthoritiesMapper = this.getGrantedAuthoritiesMapper(); if (userAuthoritiesMapper != null) { oauth2LoginAuthenticationProvider.setAuthoritiesMapper(userAuthoritiesMapper); } http.authenticationProvider((AuthenticationProvider)this.postProcess(oauth2LoginAuthenticationProvider)); boolean oidcAuthenticationProviderEnabled = ClassUtils.isPresent("org.springframework.security.oauth2.jwt.JwtDecoder", this.getClass().getClassLoader()); if (oidcAuthenticationProviderEnabled) { OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService = this.getOidcUserService(); OidcAuthorizationCodeAuthenticationProvider oidcAuthorizationCodeAuthenticationProvider = new OidcAuthorizationCodeAuthenticationProvider((OAuth2AccessTokenResponseClient)accessTokenResponseClient, oidcUserService); JwtDecoderFactory<ClientRegistration> jwtDecoderFactory = this.getJwtDecoderFactoryBean(); if (jwtDecoderFactory != null) { oidcAuthorizationCodeAuthenticationProvider.setJwtDecoderFactory(jwtDecoderFactory); } if (userAuthoritiesMapper != null) { oidcAuthorizationCodeAuthenticationProvider.setAuthoritiesMapper(userAuthoritiesMapper); } http.authenticationProvider((AuthenticationProvider)this.postProcess(oidcAuthorizationCodeAuthenticationProvider)); } else { http.authenticationProvider(new OAuth2LoginConfigurer.OidcAuthenticationRequestChecker()); } this.initDefaultLoginFilter(http); } ......... // 创建匹配器 protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) { return new AntPathRequestMatcher(loginProcessingUrl); } ........ }
-
-
授权成功,应当交给Oauth2AuthenticationSuccessHandler处理:
package org.thingsboard.server.service.security.auth.oauth2; @Slf4j @Component(value = "oauth2AuthenticationSuccessHandler") public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { // 如果授权成功 @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { OAuth2AuthorizationRequest authorizationRequest = httpCookieOAuth2AuthorizationRequestRepository.loadAuthorizationRequest(request); /** httpCookieOAuth2AuthorizationRequestRepository主要时用来持久化OAuth2请求,部分代码如下: public static final String OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME = "oauth2_auth_request"; @Override public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) { return CookieUtils.getCookie(request, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME) .map(cookie -> CookieUtils.deserialize(cookie, OAuth2AuthorizationRequest.class)) .orElse(null); } **/ // String CALLBACK_URL_SCHEME = "callback_url_scheme"; String callbackUrlScheme = authorizationRequest.getAttribute(TbOAuth2ParameterNames.CALLBACK_URL_SCHEME); // callbackUrlScheme = null String baseUrl; if (!StringUtils.isEmpty(callbackUrlScheme)) { baseUrl = callbackUrlScheme + ":"; } else { baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request); } // baseUrl = http://localhost:8080 try { OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication; OAuth2Registration registration = oAuth2Service.findRegistration(UUID.fromString(token.getAuthorizedClientRegistrationId())); OAuth2AuthorizedClient oAuth2AuthorizedClient = oAuth2AuthorizedClientService.loadAuthorizedClient( token.getAuthorizedClientRegistrationId(), token.getPrincipal().getName()); OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(registration.getMapperConfig().getType()); // mapper = GithubOAuth2ClientMapper.java , 后面会解释 // 根据Github提供的AcessToken来获取Email,进而获取或者新建一个User SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(request, token, oAuth2AuthorizedClient.getAccessToken().getTokenValue(), registration); // 为当前用户产生 accessToken和refreshToken JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); clearAuthenticationAttributes(request, response); getRedirectStrategy().sendRedirect(request, response, baseUrl + "/?accessToken=" + accessToken.getToken() + "&refreshToken=" + refreshToken.getToken()); } catch (Exception e) { log.debug("Error occurred during processing authentication success result. " + "request [{}], response [{}], authentication [{}]", request, response, authentication, e); clearAuthenticationAttributes(request, response); String errorPrefix; if (!StringUtils.isEmpty(callbackUrlScheme)) { errorPrefix = "/?error="; } else { errorPrefix = "/login?loginError="; } getRedirectStrategy().sendRedirect(request, response, baseUrl + errorPrefix + URLEncoder.encode(e.getMessage(), StandardCharsets.UTF_8.toString())); } } }
-
上面有提到GithubOAuth2ClientMapper,它是OAuth2ClientMapper的一个实现类
@Service(value = "githubOAuth2ClientMapper") @Slf4j public class GithubOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper { private static final String EMAIL_URL_KEY = "emailUrl"; private static final String AUTHORIZATION = "Authorization"; private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder(); @Override public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { OAuth2MapperConfig config = registration.getMapperConfig(); Map<String, String> githubMapperConfig = oAuth2Configuration.getGithubMapper(); /** githubMapper: emailUrl:"${SECURITY_OAUTH2_GITHUB_MAPPER_EMAIL_URL_KEY:https://api.github.com/user/emails}" **/ // 根据Github提供的AccessToken来获取Email String email = getEmail(githubMapperConfig.get(EMAIL_URL_KEY), providerAccessToken); Map<String, Object> attributes = token.getPrincipal().getAttributes(); OAuth2User oAuth2User = BasicMapperUtils.getOAuth2User(email, attributes, config); // 这是父类 AbstractOAuth2ClientMapper 的一个方法 return getOrCreateSecurityUserFromOAuth2User(oAuth2User, registration); } }
-
GithubOAuth2ClientMapper 父类 AbstractOAuth2ClientMapper
@Slf4j public abstract class AbstractOAuth2ClientMapper { @Autowired private UserService userService; protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2Registration registration) { OAuth2MapperConfig config = registration.getMapperConfig(); UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, oauth2User.getEmail()); // 根据Email查找User User user = userService.findUserByEmail(TenantId.SYS_TENANT_ID, oauth2User.getEmail()); if (user == null && !config.isAllowUserCreation()) { throw new UsernameNotFoundException("User not found: " + oauth2User.getEmail()); } // 查无对象则创建对象 if (user == null) { userCreationLock.lock(); try { user = userService.findUserByEmail(TenantId.SYS_TENANT_ID, oauth2User.getEmail()); // 查无对象则创建对象,然后设置一系列参数 if (user == null) { user = new User(); // 若customerId =null 且 customerName = null 则创建tenant,否则创建customer。这就是为什么Github登录创建的是Tenant if (oauth2User.getCustomerId() == null && StringUtils.isEmpty(oauth2User.getCustomerName())) { user.setAuthority(Authority.TENANT_ADMIN); } else { user.setAuthority(Authority.CUSTOMER_USER); } TenantId tenantId = oauth2User.getTenantId() != null ? oauth2User.getTenantId() : getTenantId(oauth2User.getTenantName()); user.setTenantId(tenantId); CustomerId customerId = oauth2User.getCustomerId() != null ? oauth2User.getCustomerId() : getCustomerId(user.getTenantId(), oauth2User.getCustomerName()); user.setCustomerId(customerId); user.setEmail(oauth2User.getEmail()); user.setFirstName(oauth2User.getFirstName()); user.setLastName(oauth2User.getLastName()); ObjectNode additionalInfo = objectMapper.createObjectNode(); if (!StringUtils.isEmpty(oauth2User.getDefaultDashboardName())) { Optional<DashboardId> dashboardIdOpt = user.getAuthority() == Authority.TENANT_ADMIN ? getDashboardId(tenantId, oauth2User.getDefaultDashboardName()) : getDashboardId(tenantId, customerId, oauth2User.getDefaultDashboardName()); if (dashboardIdOpt.isPresent()) { additionalInfo.put("defaultDashboardFullscreen", oauth2User.isAlwaysFullScreen()); additionalInfo.put("defaultDashboardId", dashboardIdOpt.get().getId().toString()); } } if (registration.getAdditionalInfo() != null && registration.getAdditionalInfo().has("providerName")) { additionalInfo.put("authProviderName", registration.getAdditionalInfo().get("providerName").asText()); } user.setAdditionalInfo(additionalInfo); // 将创建的用户保存起来 user = userService.saveUser(user); // 设置密码为空 if (config.isActivateUser()) { UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId()); userService.activateUserCredentials(user.getTenantId(), userCredentials.getActivateToken(), passwordEncoder.encode("")); } } } catch (Exception e) { log.error("Can't get or create security user from oauth2 user", e); throw new RuntimeException("Can't get or create security user from oauth2 user", e); } finally { userCreationLock.unlock(); } } try { SecurityUser securityUser = new SecurityUser(user, true, principal); return (SecurityUser) new UsernamePasswordAuthenticationToken(securityUser, null, securityUser.getAuthorities()).getPrincipal(); } catch (Exception e) { log.error("Can't get or create security user from oauth2 user", e); throw new RuntimeException("Can't get or create security user from oauth2 user", e); } }
Thingsboard源码分析(二)OAuth2--SpringSecurity
最新推荐文章于 2024-09-12 07:58:53 发布