TingsBoard源码解析-登录认证-OAuth2认证

news2025/8/12 0:08:32

配置类:ThingsboardSecurityConfiguration

用户名密码登录

在这里插入图片描述

用户名密码登录请求URL: /api/auth/login
配置中发现:在默认的用户名密码认证之前添加了认证拦截器【RestLoginProcessingFilter】,而该拦截器拦截将拦截用户名密码登录请求,并传入了认证成功后的处理器(successHandler)。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

RestLoginProcessingFilter定义如下:

// 认证方法
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
        throws AuthenticationException, IOException, ServletException {

    //。。。省略
    
    // 提取用户名
    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);
}

@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
                                        Authentication authResult) throws IOException, ServletException {
    successHandler.onAuthenticationSuccess(request, response, authResult);
}

进入authenticate方法(位于ProviderManager.class)
在这里插入图片描述

通过认证请求的类型从providers中匹配到认证处理器,provider的实现类有:
在这里插入图片描述

其中注意到自定义的类:RestAuthenticationProvider,主要代码如下:


@Override
public boolean supports(Class<?> authentication) {
    // 匹配
    return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}

@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();
        // 校验用户名和密码:1.从用户表查询用户信息 2.校验用户密码是否一致
        return authenticateByUsernameAndPassword(authentication, userPrincipal, username, password);
    } 
    // 省略其他代码
}

如果认证成功,则执行successHandler,从配置类【ThingsboardSecurityConfiguration】得知handler是:

全局搜索defaultAuthenticationSuccessHandler :

@Component(value = "defaultAuthenticationSuccessHandler")
public class RestAwareAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    // 。。。

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

        // 生成access_token 和 refreshToken
        Map<String, String> tokenMap = new HashMap<String, String>();
        tokenMap.put("token", accessToken.getToken());
        tokenMap.put("refreshToken", refreshToken.getToken());

        // 将token写入响应对象
        response.setStatus(HttpStatus.OK.value());
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        mapper.writeValue(response.getWriter(), tokenMap);

        // 。。。
    }
}

至此登录完成,前端可使用jwt请求用户数据。

JWT请求用户数据

登录完成后,使用jwt请求用户数据,例如请求当前用户基础信息:

在这里插入图片描述

使用jwt请求用户数据,首先要认证该jwt是否合法,然后将jwt表示的用户放入上下文中,然后执行具体的查询逻辑;既然是认证,还是从安全配置类【ThingsboardSecurityConfiguration】开始:
在这里插入图片描述
在这里插入图片描述

可知Filter【JwtTokenAuthenticationProcessingFilter】默认处理除了指定skip的url外,定义如下:

public class JwtTokenAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {
    // ...
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException, IOException, ServletException {
        RawAccessJwtToken token = new RawAccessJwtToken(tokenExtractor.extract(request));
        //匹配处理类JwtAuthenticationToken的认证器,然后执行认证
        return getAuthenticationManager().authenticate(new JwtAuthenticationToken(token));
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {
        // 认证成功,将认证结果缓存到上下文中
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        context.setAuthentication(authResult);
        SecurityContextHolder.setContext(context);
        chain.doFilter(request, response);
    }
    // ...
 }

可发现自定义的认证器【JwtAuthenticationProvider 】匹配类【JwtAuthenticationToken】

@Component
@RequiredArgsConstructor
public class JwtAuthenticationProvider implements AuthenticationProvider {
    //...
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials();
        // 解析jwt、校验jwt,返回jwt表示的用户
        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));
    }
}

OAuth2授权码登录(以github为例)

配置信息

http.oauth2Login()
        .authorizationEndpoint()
        // cookie存储请求
        .authorizationRequestRepository(httpCookieOAuth2AuthorizationRequestRepository)
        // 请求解析器
        .authorizationRequestResolver(oAuth2AuthorizationRequestResolver)
        .and()
        .loginPage("/oauth2Login")
        .loginProcessingUrl(oauth2Configuration.getLoginProcessingUrl())
        // 授权成功处理器
        .successHandler(oauth2AuthenticationSuccessHandler)
        // 授权失败处理器
        .failureHandler(oauth2AuthenticationFailureHandler);

配置GitHub授权登录

当部署TB后,默认是不支持GitHub授权登录的,看不到GitHub登录按钮,请参考oauth2配置教程。

整体流程

为了流程更清晰,请先登录github

  1. 进入登录页面,先获取已支持的授权登录方式,用来渲染登录按钮和按钮的链接
    在这里插入图片描述

  2. 点击Github登录按钮,系统根据请求中的id判断本次授权登录是通过github登录,因此响应302使前端重定向到github的授权页面
    在这里插入图片描述

  3. 在github的页面确认授权即可(后续再登录就无需授权了)
    在这里插入图片描述

  4. git授权认证后携带code回调
    在这里插入图片描述

  5. 发起回调,系统用code到github中换access_token,再用access_token到github中查询github用户信息,然后匹配本地用户,如不存在则注册新用户;再生成登录态jwt(access_token和refresh_token),并重定向到首页
    在这里插入图片描述

详细解析

获取授权URL

从表【oauth2_registration】查询到registrationId,然后拼接到授权URl中并返回。

重定向到github

接收到授权请求:/oauth2/authorization/529c39e0-1958-11ed-947e-9d3049528ec9
根据授权请求解析器【oAuth2AuthorizationRequestResolver】解析该请求,其具体实现类【
CustomOAuth2AuthorizationRequestResolver】的核心方法是:

private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId, String redirectUriAction, String appPackage, String appToken) {
    // 根据registrationId查找当前使用的是什么平台的client登记信息,这里是github的客户端信息
    ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
    Map<String, Object> attributes = new HashMap<>();
    attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId());
    String appSecret = this.oAuth2Service.findAppSecret(UUID.fromString(registrationId), appPackage);
    String callbackUrlScheme = this.oAuth2AppTokenFactory.validateTokenAndGetCallbackUrlScheme(appPackage, appToken, appSecret);
    attributes.put(TbOAuth2ParameterNames.CALLBACK_URL_SCHEME, callbackUrlScheme);
    // 构造回调URL
    String redirectUriStr = expandRedirectUri(request, clientRegistration, redirectUriAction);
    return builder
            .clientId(clientRegistration.getClientId())
            .authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
            .redirectUri(redirectUriStr)
            .scopes(clientRegistration.getScopes())
            .state(this.stateGenerator.generateKey())
            .attributes(attributes)
            .build();
}

code换access_token

待github认证后回调时会携带code,配置类中未自定义code转token的实现类,
则使用默认实现类【DefaultAuthorizationCodeTokenResponseClient】
在这里插入图片描述

token查询用户信息

也没有自定义用户信息查询服务,因此使用默认的DefaultOAuth2UserService:

在这里插入图片描述

查询到github用户信息如下:
在这里插入图片描述

认证成功处理器

OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
// 查找cient登记信息
OAuth2Registration registration = oAuth2Service.findRegistration(UUID.fromString(token.getAuthorizedClientRegistrationId()));
OAuth2AuthorizedClient oAuth2AuthorizedClient = oAuth2AuthorizedClientService.loadAuthorizedClient(
        token.getAuthorizedClientRegistrationId(),
        token.getPrincipal().getName());
// 构造本地用户 SecurityUser
OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(registration.getMapperConfig().getType());
SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(request, token, oAuth2AuthorizedClient.getAccessToken().getTokenValue(),
        registration);
// 生成token
JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);

// 重定向到首页
getRedirectStrategy().sendRedirect(request, response, baseUrl + "/?accessToken=" + accessToken.getToken() + "&refreshToken=" + refreshToken.getToken());

在这里插入图片描述

其中Github对应的mapper是githubOAuth2ClientMapper
在这里插入图片描述

不同的mappr构造本地用户的实现不相同,例如github返回的用户信息中没有email,因此需要再次请求github获取email,而苹果用户认证返回的用户信息包含email,则不再需要发起请求。
再次请求github查询用户email:
在这里插入图片描述

然后使用github用户信息和邮箱创建本地用户
在这里插入图片描述

在这里插入图片描述

首次登录成功后,user表中新增用户信息:

在这里插入图片描述

TIPS

TB的用户体系中,邮箱是可作为用户的唯一标识,这和国内通常使用手机号作为用户标识不同,可能因为TB是外国人开发的,他们认为手机号属于隐私。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/33701.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

项目管理证书 PMP 的含金量高吗?

PMP 含金量&#xff0c;PMP有没有用&#xff0c;这类问题一直是大家关注的重点&#xff0c;知乎上几个相关问题热度也一直很高。 作为有 7 年项目经验的 PMP 持证者&#xff0c;我要跟大家说句实话&#xff1a; PMP 最基础的是项目管理领域的一个资格认证证书&#xff0c;相当…

FastDFS分布式文件系统

FastDFS分布式文件系统 FastDFS是由国人开发的针对中小文件存储的轻量级分布式文件系统&#xff0c;使用C语言进行开发&#xff0c;效率高、跨平台&#xff0c;可以在类UNIX系统上很好运行。整体设计以简单高效为原则&#xff0c;具有冗余备份、负载均衡、在线扩容等性能。 F…

开源生态企业反哺GitLink确实开源创新服务--DevOps引擎合作

日前&#xff0c;建木正式入驻到GitLink引擎模块下。 建木是DevOps领域的小能手&#xff0c;而GitLink又致力于提供强大的开源基础设施&#xff0c;双方可谓一拍即合&#xff0c;强强联手为开发者提供更愉悦、更轻松的研发体验&#xff01; GitLink&#xff08;确实开源&#…

Android结构优化 - Java、Kotlin项目结构分包

随着Android中 Java、Kotlin 的混编开发场景越来越多&#xff0c;其中大多人都会将 java文件 和 kt文件 放在同一个资源文件夹下&#xff0c;在项目越来越大的情况下&#xff0c;我们进行代码查询、项目重构、优化都不太便捷&#xff0c;所以本篇主要记录通过 kotlin分包、java…

MySQL集群:双主模式

目录 1、双主模式 1.1、高可用架构 1.2、MMM架构(基于双主模式) 1.2.1、MMM故障处理机制 1.2.2、MMM监控机制 1.3、MHA架构(基于主从模式) 1.3.1、MHA故障处理机制 1.3.2、MHA优点 1.4、主备切换 1.4.1、主备延迟问题 1.4.2、可靠性优先 1.4.3、可用性优先 2、双主…

关于 Laravel Redis 多个进程同时取队列问题详解

最近在工作中遇到了一个问题&#xff0c;开启多个进程处理队列会重复读取 Redis 中队列吗&#xff1f;是否因此导致重复执行任务&#xff1f;下面就来通过示例代码详细介绍下。 使用 Supervisor 监听 Laravel 队列任务&#xff0c;其中 Supervisor 的配置如下&#xff1a; 1 2…

.net----泛型

泛型泛型的基本概念集合类System. Collections. ArrayList泛型集合类System. Collection. Generic. List<T>ArrayListList<T>泛型的定义和类型参数类型参数<T>泛型类和泛型接口泛型类泛型接口泛型结构泛型方法泛型委托和泛型事件default关键字及协变和逆变协…

手动引入jar包,解决Dependency ‘XXX‘ not found的两种方式

目录引言一、使用systemPath导入&#xff08;一&#xff09;将jar包复制到指定文件夹&#xff08;二&#xff09;在pom文件中引入jar包**这里有一个超级大的坑&#xff0c;就是systemPath不支持聚合工程的父子传导&#xff01;&#xff01;&#xff01;****不支持pom工程的继承…

Linux零基础从入门到精通,必学的55个指令合集【上篇】

Linux学习笔记 资料下载&#xff1a; 链接: https://pan.baidu.com/s/1UvwkJaEJO7W3sU5qkCgKzA?pwdfe2f提取码: fe2f 本篇文章主要适用0基础的读者&#xff0c;内容会比较通俗易懂&#xff0c;也会有详细的图解教程&#xff0c;以及运行后的返回结果。我本人在系统性的学习…

G1D22-安装burpsuiteAttacKG

–0724 还有几分钟&#xff0c;把burpsuite安装一下 —0804 hh当然&#xff0c;和室友聊天去啦hhh java目录下找不到jdk&#xff0c;环境变量没法配emm&#xff0c;重新装一下。 emm原来这个文件夹是在安装时自己创建的 啊啊啊&#xff0c;我是猪emm javasuite闪退是因为环境变…

别瞎扯,元宇宙就是没有切实发展?

前言 最近两年&#xff0c;技术圈比较火的话题之一就是&#xff1a;元宇宙&#xff0c;而且2021年被看作是元宇宙元年&#xff0c;直到现在元宇宙话题依然不断&#xff0c;因为元宇宙在过去的一年里太火了。不管是在国内还是国外&#xff0c;元宇宙太火了&#xff0c;而且与元宇…

WPF项目实战布局--通用固件下载 C#

每个作品都是产品 C# WPF版效果&#xff1a; C# winForm版效果: 一.布局设计UI 1.主体&#xff1a;grid 2行 2列 00 下载按钮 20% 01进度条 80% &#xff08;同时显示百分比&#xff09; 10 11都是跨列 显示日志 2.细节&#xff1a;百分比与进度条Value绑定。下载按钮…

java EE初阶 — 计算机工作原理

文章目录1.操作系统2.操作系统的定位3.进程3.1 进程的基本了解3.2 操作系统内核是如何管理软件资源的3.3 PCB里描述了进程的哪些特征3.3.1 三个较为简单的特征3.3.2 进程的调度属性4.内存管理1.操作系统 操作系统是一个搞管理的软件。 对上要给软件提供稳定的运行环境。对下要…

Java面向对象之——继承

文章目录前言一、继承机制二、继承的语法三、父类成员访问&#x1f351;1、子类中访问父类的成员变量&#x1f351;2、子类中访问父类的成员方法四、super关键字五、子类构造方法六、super和this七、继承关系下的代码执行顺序八、访问限定修饰符protected九、Java继承方式十、f…

C#界面里Control.ImeMode 属性的使用

C#界面里Control.ImeMode 属性的使用 Control.ImeMode 属性是获取或设置控件的输入法编辑器 (IME) 模式。 输入法是一种特殊的程序,可以通过某种方式进行激活。 输入法程序总是在别的程序上面,因此它的运行是一种特殊的状态,所以需要特别处理。 因为电脑当时为了输入26个字…

第 46 届国际大学生程序设计竞赛(ICPC)亚洲区域赛(南京),签到题5题

文章目录A.Oops, Its Yesterday Twice MoreM.Windblume FestivalC.Klee in Solitary ConfinementH.CrystalflyD.Paimon Sorting补题链接&#xff1a;https://codeforces.com/gym/103470 A.Oops, It’s Yesterday Twice More Oops, It’s Yesterday Twice More Input file: st…

2020-RKT

2020-RKT&#xff1a;Relation-Aware Self-Attention for Knowledge Tracing 有代码&#xff1a;https://github.com/shalini1194/RKT 摘要 学生在解决练习的过程中获得技能&#xff0c;每一次这样的互动都对学生解决未来练习的能力有明显的影响。 这种影响表现为:1)互动中涉…

Transformer13~目标检测算法汇总

都到了13了 ~~ 还是基于这个的么办法 自从VIT横空出世以来&#xff0c;Transformer在CV界掀起了一场革新&#xff0c;各个上下游任务都得到了长足的进步&#xff0c;然后盘点一下基于Transformer的端到端目标检测算法&#xff01; 原始Tranformer检测器 DETR&#xff08;ECCV…

神经网络架构

神经网络架构 首先我们来看一张图&#xff0c;左边的是生物上的神经网络&#xff0c;右边的是数学版的神经网络 下面我们介绍在深度学习中神经网络的基本架构 整体架构包括层次结构&#xff0c;神经元&#xff0c;全连接&#xff0c;非线性四个部分 我们将针对这四个部分来进…

章节4 Linux操作系统基础知识

4.1-Linux系统结构 Linux系统结构 内核Shell文件系统应用程序 Linux操作系统内核 管理进程管理内存管理驱动管理文件和网络 … Linux Shell 接收用户的命令&#xff0c;经过转换&#xff0c;交给内核去执行 cat —> open() read() 简化操作安全 Linux Shell工具&am…