我是靠谱客的博主 俭朴毛巾,最近开发中收集的这篇文章主要介绍ThingsBoard源码解析-登录认证用户名密码登录JWT请求用户数据OAuth2授权码登录(以github为例)TIPS,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

配置类: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),并重定向到首页
    在这里插入图片描述

详细解析

自定义ClientRegistrationRepository

public interface ClientRegistrationRepository {
    ClientRegistration findByRegistrationId(String registrationId);
}

ClientRegistrationRepository 是spring security给开发者的扩展点,使得开发者可以根据id选择不同的client(即不同的授权认证方式),在spring security认证filter中会调用该接口获取client注册信息,然后存入认证请求中:
在这里插入图片描述
TB里自定义的ClientRegistrationRepository:

@Component
public class HybridClientRegistrationRepository implements ClientRegistrationRepository {
    private static final String defaultRedirectUriTemplate = "{baseUrl}/login/oauth2/code/{registrationId}";

    @Autowired
    private OAuth2Service oAuth2Service;

    @Override
    public ClientRegistration findByRegistrationId(String registrationId) {
    	// 从库中查询client注册信息
        OAuth2Registration registration = oAuth2Service.findRegistration(UUID.fromString(registrationId));
        // 构造ClientRegistration对象
        return registration == null ?
                null : toSpringClientRegistration(registration);
    }

    private ClientRegistration toSpringClientRegistration(OAuth2Registration registration){
        String registrationId = registration.getUuidId().toString();
        return ClientRegistration.withRegistrationId(registrationId)
                .clientName(registration.getName())
                .clientId(registration.getClientId())
                .authorizationUri(registration.getAuthorizationUri())
                .clientSecret(registration.getClientSecret())
                .tokenUri(registration.getAccessTokenUri())
                .scope(registration.getScope())
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .userInfoUri(registration.getUserInfoUri())
                .userNameAttributeName(registration.getUserNameAttributeName())
                .jwkSetUri(registration.getJwkSetUri())
                .clientAuthenticationMethod(new ClientAuthenticationMethod(registration.getClientAuthenticationMethod()))
                .redirectUri(defaultRedirectUriTemplate)
                .build();
    }
}

获取授权URL

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

授权请求解析,重定向到github

接收到授权请求:/oauth2/authorization/529c39e0-1958-11ed-947e-9d3049528ec9

根据授权请求解析器【oAuth2AuthorizationRequestResolver】根据表【oauth2_registration】构造OAuth2AuthorizationRequest对象,其具体实现类【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是外国人开发的,他们认为手机号属于隐私。

最后

以上就是俭朴毛巾为你收集整理的ThingsBoard源码解析-登录认证用户名密码登录JWT请求用户数据OAuth2授权码登录(以github为例)TIPS的全部内容,希望文章能够帮你解决ThingsBoard源码解析-登录认证用户名密码登录JWT请求用户数据OAuth2授权码登录(以github为例)TIPS所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(42)

评论列表共有 0 条评论

立即
投稿
返回
顶部