ThingsBoard
的权限管理基于Spring Security
,使用其过滤器处理
从配置入手
//org.thingsboard.server.config.ThingsboardSecurityConfiguration
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().cacheControl().and().frameOptions().disable()
.and()
.cors()
.and()
.csrf().disable()
.exceptionHandling()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(WEBJARS_ENTRY_POINT).permitAll() // Webjars
.antMatchers(DEVICE_API_ENTRY_POINT).permitAll() // Device HTTP Transport API
.antMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll() // Login end-point
.antMatchers(PUBLIC_LOGIN_ENTRY_POINT).permitAll() // Public login end-point
.antMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll() // Token refresh end-point
.antMatchers(NON_TOKEN_BASED_AUTH_ENTRY_POINTS).permitAll() // static resources, user activation and password reset end-points
.and().authorizeRequests()
.antMatchers(WS_TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() // Protected WebSocket API End-points
.antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() // Protected API End-points
.and().exceptionHandling().accessDeniedHandler(restAccessDeniedHandler)
.and()
.addFilterBefore(buildRestLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildRestPublicLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildRefreshTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildWsJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(rateLimitProcessingFilter, UsernamePasswordAuthenticationFilter.class);
if (oauth2Configuration != null) {
http.oauth2Login()
.authorizationEndpoint()
.authorizationRequestRepository(httpCookieOAuth2AuthorizationRequestRepository)
.authorizationRequestResolver(oAuth2AuthorizationRequestResolver)
.and().loginPage("/oauth2Login")
.loginProcessingUrl(oauth2Configuration.getLoginProcessingUrl())
.successHandler(oauth2AuthenticationSuccessHandler)
.failureHandler(oauth2AuthenticationFailureHandler);
}
}
增加了六种过滤器
addFilterBefore(buildRestLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
addFilterBefore(buildRestPublicLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
JWT
令牌权限处理器addFilterBefore(buildRefreshTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
addFilterBefore(buildWsJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
WebSocket
的JWT
令牌处理器addFilterAfter(rateLimitProcessingFilter, UsernamePasswordAuthenticationFilter.class)
接着我们重点看 登录处理器 和 JWT
令牌处理器
//org.thingsboard.server.config.ThingsboardSecurityConfiguration
public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login";
@Autowired
@Qualifier("defaultAuthenticationSuccessHandler")
private AuthenticationSuccessHandler successHandler;
@Autowired
@Qualifier("defaultAuthenticationFailureHandler")
private AuthenticationFailureHandler failureHandler;
@Autowired private ObjectMapper objectMapper;
@Bean
protected RestLoginProcessingFilter buildRestLoginProcessingFilter() throws Exception {
RestLoginProcessingFilter filter = new RestLoginProcessingFilter(FORM_BASED_LOGIN_ENTRY_POINT, successHandler, failureHandler, objectMapper);
filter.setAuthenticationManager(this.authenticationManager);
return filter;
}
successHandler为成功处理器,创建JWT
令牌写并响应
failureHandler为失败处理器,构建失败信息并响应
RestLoginProcessingFilter继承自org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter,接下来看其处理方法public abstract Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException;
//org.thingsboard.server.service.security.auth.rest.RestLoginProcessingFilter
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
if (!HttpMethod.POST.name().equals(request.getMethod())) {
if(log.isDebugEnabled()) {
log.debug("Authentication method not supported. Request method: " + request.getMethod());
}
throw new AuthMethodNotSupportedException("Authentication method not supported");
}
LoginRequest loginRequest;
try {
//获取请求数据并反序列化
loginRequest = objectMapper.readValue(request.getReader(), LoginRequest.class);
} catch (Exception e) {
throw new AuthenticationServiceException("Invalid login request payload");
}
if (StringUtils.isBlank(loginRequest.getUsername()) || StringUtils.isEmpty(loginRequest.getPassword())) {
throw new AuthenticationServiceException("Username or Password not provided");
}
UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, loginRequest.getUsername());
//创建鉴权令牌
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(principal, loginRequest.getPassword());
//设置细节(客户端地址,用户代理等)
token.setDetails(authenticationDetailsSource.buildDetails(request));
//验证并返回
return this.getAuthenticationManager().authenticate(token);
}
认证管理使用的是org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.AuthenticationManagerDelegator
AuthenticationManagerDelegator是一个代理类,代理目标为org.springframework.security.authentication.ProviderManager
ProviderManager的Authentication authenticate(Authentication authentication) throws AuthenticationException
方法将遍历管理的AuthenticationProvider,根据其boolean supports(Class> authentication)
方法判断是否支持当前认证,调用其Authentication authenticate(Authentication authentication) throws AuthenticationException
方法进行认证
综上,我们找到org.thingsboard.server.service.security.auth.rest.RestAuthenticationProvider
//org.thingsboard.server.service.security.auth.rest.RestAuthenticationProvider
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.notNull(authentication, "No authentication data provided");
Object principal = authentication.getPrincipal();
if (!(principal instanceof UserPrincipal)) {
throw new BadCredentialsException("Authentication Failed. Bad user principal.");
}
UserPrincipal userPrincipal = (UserPrincipal) principal;
//判断类型
if (userPrincipal.getType() == UserPrincipal.Type.USER_NAME) {
String username = userPrincipal.getValue();
String password = (String) authentication.getCredentials();
//根据用户名和密码认证
return authenticateByUsernameAndPassword(authentication, userPrincipal, username, password);
} else {
String publicId = userPrincipal.getValue();
//根据公开Id认证
return authenticateByPublicId(userPrincipal, publicId);
}
}
//org.thingsboard.server.service.security.auth.rest.RestAuthenticationProvider
private Authentication authenticateByUsernameAndPassword(Authentication authentication, 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) {
logLoginAction(user, authentication, ActionType.LOCKOUT, null);
throw e;
}
if (user.getAuthority() == null)
throw new InsufficientAuthenticationException("User has no authority assigned");
//创建用户
SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled(), userPrincipal);
logLoginAction(user, authentication, ActionType.LOGIN, null);
//创建认证令牌并返回
return new UsernamePasswordAuthenticationToken(securityUser, null, securityUser.getAuthorities());
} catch (Exception e) {
logLoginAction(user, authentication, ActionType.LOGIN, e);
throw e;
}
}
最后,调用RestLoginProcessingFilter的成功方法
//org.thingsboard.server.service.security.auth.rest.RestLoginProcessingFilter
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
successHandler.onAuthenticationSuccess(request, response, authResult);
}
调用成功处理器根据用户信息和权限创建令牌并返回
//org.thingsboard.server.service.security.auth.rest.RestAwareAuthenticationSuccessHandler
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
//获取用户
SecurityUser securityUser = (SecurityUser) authentication.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);
//清除认真属性
clearAuthenticationAttributes(request);
}
JWT
令牌处理器//org.thingsboard.server.config.ThingsboardSecurityConfiguration
public static final String WEBJARS_ENTRY_POINT = "/webjars/**";
public static final String DEVICE_API_ENTRY_POINT = "/api/v1/**";
public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login";
public static final String PUBLIC_LOGIN_ENTRY_POINT = "/api/auth/login/public";
public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token";
protected static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[] {"/index.html", "/assets/**", "/static/**", "/api/noauth/**", "/webjars/**", "/api/license/**"};
public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**";
public static final String WS_TOKEN_BASED_AUTH_ENTRY_POINT = "/api/ws/**";
@Autowired
@Qualifier("jwtHeaderTokenExtractor")
private TokenExtractor jwtHeaderTokenExtractor;
protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception {
//跳过的路径列表
List<String> pathsToSkip = new ArrayList<>(Arrays.asList(NON_TOKEN_BASED_AUTH_ENTRY_POINTS));
pathsToSkip.addAll(Arrays.asList(WS_TOKEN_BASED_AUTH_ENTRY_POINT, TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT,
PUBLIC_LOGIN_ENTRY_POINT, DEVICE_API_ENTRY_POINT, WEBJARS_ENTRY_POINT));
//创建包含跳过路径的匹配器
SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_AUTH_ENTRY_POINT);
//创建过滤器
JwtTokenAuthenticationProcessingFilter filter
= new JwtTokenAuthenticationProcessingFilter(failureHandler, jwtHeaderTokenExtractor, matcher);
filter.setAuthenticationManager(this.authenticationManager);
return filter;
}
jwtHeaderTokenExtractor 为头部JWT
令牌提取器
接着看JwtTokenAuthenticationProcessingFilter的处理方法
//org.thingsboard.server.service.security.auth.jwt.JwtTokenAuthenticationProcessingFilter
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
//从请求头提取JWT令牌
RawAccessJwtToken token = new RawAccessJwtToken(tokenExtractor.extract(request));
//认证并返回
return getAuthenticationManager().authenticate(new JwtAuthenticationToken(token));
}
和前面一样,根据令牌类型org.thingsboard.server.service.security.auth.JwtAuthenticationToken找到org.thingsboard.server.service.security.auth.jwt.JwtAuthenticationProvider
package org.thingsboard.server.service.security.auth.jwt;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import org.thingsboard.server.service.security.auth.TokenOutdatingService;
import org.thingsboard.server.service.security.auth.JwtAuthenticationToken;
import org.thingsboard.server.service.security.exception.JwtExpiredTokenException;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import org.thingsboard.server.service.security.model.token.RawAccessJwtToken;
@Component
@RequiredArgsConstructor
public class JwtAuthenticationProvider implements AuthenticationProvider {
private final JwtTokenFactory tokenFactory;
private final TokenOutdatingService tokenOutdatingService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials();
//解析用户
SecurityUser securityUser = tokenFactory.parseAccessJwtToken(rawAccessToken);
//判断过期
if (tokenOutdatingService.isOutdated(rawAccessToken, securityUser.getId())) {
throw new JwtExpiredTokenException("Token is outdated");
}
//创建并返回认证令牌
return new JwtAuthenticationToken(securityUser);
}
@Override
public boolean supports(Class<?> authentication) {
return (JwtAuthenticationToken.class.isAssignableFrom(authentication));
}
}
接着,调用JwtTokenAuthenticationProcessingFilter的成功方法
//org.thingsboard.server.service.security.auth.jwt.JwtTokenAuthenticationProcessingFilter
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
//创建空白上下文
SecurityContext context = SecurityContextHolder.createEmptyContext();
//设置认证对象,即前面的JwtAuthenticationToken
context.setAuthentication(authResult);
//设置上下文
SecurityContextHolder.setContext(context);
//继续过滤链处理
chain.doFilter(request, response);
}
至此,用户信息被写入上下文中,后续使用org.springframework.security.access.prepost.PreAuthorize注解的方法将判断上下文中的用户权限列表是否包含该权限名,实现权限的控制
大致分为基本处理和设备消息处理
通过Actor
模型分发处理,之前我们已经了解了ThingsBoard
中Actor
的基本实现,今天来看一下具体的使用
通过查找org.thingsboard.server.actors.DefaultTbActorSystem的使用,找到切入点org.thingsboard.server.actors.service.DefaultActorService
重点关注其初始化方法
//org.thingsboard.server.actors.service.DefaultActorService
@Autowired
private ActorSystemContext actorContext;
@PostConstruct
public void initActorSystem() {
log.info("Initializing actor system.");
//设置 Actor 服务
actorContext.setActorService(this);
//创建 Actor 系统设置
TbActorSystemSettings settings = new TbActorSystemSettings(actorThroughput, schedulerPoolSize, maxActorInitAttempts);
//创建 Actor 系统
system = new DefaultTbActorSystem(settings);
//创建应用调度器
system.createDispatcher(APP_DISPATCHER_NAME, initDispatcherExecutor(APP_DISPATCHER_NAME, appDispatcherSize));
//创建租户调度器
system.createDispatcher(TENANT_DISPATCHER_NAME, initDispatcherExecutor(TENANT_DISPATCHER_NAME, tenantDispatcherSize));
//创建设备调度器
system.createDispatcher(DEVICE_DISPATCHER_NAME, initDispatcherExecutor(DEVICE_DISPATCHER_NAME, deviceDispatcherSize));
//创建规则调度器
system.createDispatcher(RULE_DISPATCHER_NAME, initDispatcherExecutor(RULE_DISPATCHER_NAME, ruleDispatcherSize));
//设置 Actor 系统
actorContext.setActorSystem(system);
//创建应用 Actor
appActor = system.createRootActor(APP_DISPATCHER_NAME, new AppActor.ActorCreator(actorContext));
//设置应用 Actor
actorContext.setAppActor(appActor);
//创建统计 Actor
TbActorRef statsActor = system.createRootActor(TENANT_DISPATCHER_NAME, new StatsActor.ActorCreator(actorContext, "StatsActor"));
//设置统计 Actor
actorContext.setStatsActor(statsActor);
log.info("Actor system initialized.");
}
接着看ActorSystemContext,代码比较多,重点找消息传递相关的方法
//org.thingsboard.server.actors.ActorSystemContext
public void tell(TbActorMsg tbActorMsg) {
appActor.tell(tbActorMsg);
}
public void tellWithHighPriority(TbActorMsg tbActorMsg) {
appActor.tellWithHighPriority(tbActorMsg);
}
可见,消息统一交给了appActor处理
查看AppActor的doProcess
方法
//org.thingsboard.server.actors.app.AppActor
@Override
protected boolean doProcess(TbActorMsg msg) {
if (!ruleChainsInitialized) {
//初始化租户 Actor
initTenantActors();
ruleChainsInitialized = true;
if (msg.getMsgType() != MsgType.APP_INIT_MSG && msg.getMsgType() != MsgType.PARTITION_CHANGE_MSG) {
log.warn("Rule Chains initialized by unexpected message: {}", msg);
}
}
switch (msg.getMsgType()) {
case APP_INIT_MSG:
break;
case PARTITION_CHANGE_MSG:
ctx.broadcastToChildren(msg);
break;
case COMPONENT_LIFE_CYCLE_MSG:
onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
break;
case QUEUE_TO_RULE_ENGINE_MSG:
onQueueToRuleEngineMsg((QueueToRuleEngineMsg) msg);
break;
case TRANSPORT_TO_DEVICE_ACTOR_MSG:
onToDeviceActorMsg((TenantAwareMsg) msg, false);
break;
case DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG:
case DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG:
case DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG:
case DEVICE_EDGE_UPDATE_TO_DEVICE_ACTOR_MSG:
case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG:
case DEVICE_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG:
case SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG:
case REMOVE_RPC_TO_DEVICE_ACTOR_MSG:
onToDeviceActorMsg((TenantAwareMsg) msg, true);
break;
case EDGE_EVENT_UPDATE_TO_EDGE_SESSION_MSG:
onToTenantActorMsg((EdgeEventUpdateMsg) msg);
break;
case SESSION_TIMEOUT_MSG:
ctx.broadcastToChildrenByType(msg, EntityType.TENANT);
break;
default:
return false;
}
return true;
}
首次会进行租户Actor
初始化
//org.thingsboard.server.actors.app.AppActor
private void initTenantActors() {
log.info("Starting main system actor.");
try {
// This Service may be started for specific tenant only.
//独立的租户标识
Optional<TenantId> isolatedTenantId = systemContext.getServiceInfoProvider().getIsolatedTenant();
if (isolatedTenantId.isPresent()) {
//查询租户
Tenant tenant = systemContext.getTenantService().findTenantById(isolatedTenantId.get());
if (tenant != null) {
log.debug("[{}] Creating tenant actor", tenant.getId());
//获取或创建租户 Actor
getOrCreateTenantActor(tenant.getId());
log.debug("Tenant actor created.");
} else {
log.error("[{}] Tenant with such ID does not exist", isolatedTenantId.get());
}
} else if (systemContext.isTenantComponentsInitEnabled()) {
//统一的租户服务且开启初始化
PageDataIterable<Tenant> tenantIterator = new PageDataIterable<>(tenantService::findTenants, ENTITY_PACK_LIMIT);
//当前是否规则引擎服务
boolean isRuleEngine = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE);
//当前是否核心服务
boolean isCore = systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE);
//遍历租户
for (Tenant tenant : tenantIterator) {
//获取租户配置
TenantProfile tenantProfile = tenantProfileCache.get(tenant.getTenantProfileId());
//判断是否为核心服务或是否为规则引擎服务且租户未使用独立的规则引擎
if (isCore || (isRuleEngine && !tenantProfile.isIsolatedTbRuleEngine())) {
log.debug("[{}] Creating tenant actor", tenant.getId());
//获取或创建租户 Actor
getOrCreateTenantActor(tenant.getId());
log.debug("[{}] Tenant actor created.", tenant.getId());
}
}
}
log.info("Main system actor started.");
} catch (Exception e) {
log.warn("Unknown failure", e);
}
}
根据配置使用getOrCreateTenantActor
方法预先创建租户Actor
,将接收到的消息交由租户Actor
处理
租户Actor
根据消息的类型,再交由设备Actor
或规则链Actor
处理