java后端对接飞书登陆

java后端对接飞书登陆

项目要求对接第三方登陆,飞书登陆,次笔记仅针对java后端,在看本笔记前,默认已在飞书开发方已建立了应用,并获取到了appid和appsecret。后端要做的其实很简单,基本都是前端做的,后端要做的就是接收前端请求飞书获取到一个code后传到后端,后端拿到code后请求登陆人在飞书的token信息,同时根据token请求飞书接口获取登陆人信息返回给前端就好了,官网开发指导有很详细的教程,可以看下官方文档。
飞书官方文档

废话不多书,直接上代码:

首先controller:

复制代码
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

/**
 * @author xx
 */
@RestController
@Api(tags = "飞书登陆认证")
public class FeiShuController {

    @Resource
    private FeiShuService feiShuService;

    @GetMapping("/getUserByCode/{code}")
    @ApiOperation(value = "根据code获取飞书用户信息")
    public Response<FeishuUserResp> login(@PathVariable String code) {
        //获取飞书用户token
        FeiShuTokenResp feiShuTokenVo = feiShuService.getTokenByCode(code);
        //根据token获取用户信息
        FeishuUserResp userInfo = feiShuService.getUserInfoByToken(feiShuTokenVo);
        // 刷新token
        return Response.ok(userInfo);
    }

    @GetMapping("/refreshToken/{refreshToken}")
    @ApiModelProperty(value = "刷新token")
    public Response<FeishuUserResp> refreshToken(@PathVariable String refreshToken){
        return Response.ok(feiShuService.refreshToken(refreshToken));
    }

    @PostMapping("/login-out/{userId}")
    @ApiOperation(value = "飞书退出登陆")
    public Response loginOut(HttpServletRequest request, @PathVariable String userId){
        return feiShuService.loginOut(request,userId);
    }
}

service层:

复制代码
import cn.hutool.http.Header;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class FeiShuServiceImpl implements FeiShuService {

    // 应用appid
    @Value("${feishu.app.id}")
    private String appid;
    // 应用appsecret
    @Value("${feishu.app.secret}")
    private String appsecret;
    // 飞书获取token地址
    @Value("${feishu.userAccessTokenUrl}")
    private String userAccessTokenUrl;
    // 回调地址(需前端提供)
    @Value("${feishu.redirectUrl}")
    private String redirectUrl;
    // 获取用户详情
    @Value("${feishu.userInfoUrl}")
    private String userInfoUrl;
	// 登出飞书地址
    @Value("${feishu.logOutUrl}")
    private String loginOutUrl;
	//刷新飞书token地址
    @Value("${feishu.refreshTokenUrl}")
    private String refreshTokenUrl;

    @Resource
    private RedisService redisService;

    /**
     * 根据code获取token
     *
     * @param code code
     * @return token
     */
    @Override
    public FeiShuTokenResp getTokenByCode(String code) {
        Map<String, Object> params = new HashMap<>(4);
        params.put("grant_type", "authorization_code");
        params.put("client_id", appid);
        params.put("client_secret", appsecret);
        params.put("code", code);
        params.put("redirect_uri", redirectUrl);

        String result = HttpUtil.createPost(userAccessTokenUrl)
                .header(Header.CONTENT_TYPE, "application/json")
                .charset("utf-8")
                .body(JSON.toJSONString(params))
                .execute()
                .body();

        if (StringUtils.isEmpty(result)) {
            throw new RuntimeException("获取飞书token失败!");
        }

        JSONObject parseObj = JSONUtil.parseObj(result);
        if ((int) parseObj.get("code") != 0) {
            throw new RuntimeException("请求飞书token失败:" + parseObj.get("error_description"));
        }
        //token
        String userAccessToken = (String) parseObj.get("access_token");
        //token过期时间
        long expiresIn = (int) parseObj.get("expires_in");
        // refresh_token
        String refreshToken = (String) parseObj.get("refresh_token");
        // 刷新token的过期时间
        long refreshTokenExpiresIn = (int) parseObj.get("refresh_token_expires_in");
        // token_type
        String tokenType = (String) parseObj.get("token_type");

        return FeiShuTokenResp.builder()
                .tokenType(tokenType)
                .accessToken(userAccessToken)
                .expiresIn(expiresIn)
                .refreshToken(refreshToken)
                .refreshTokenExpiresIn(refreshTokenExpiresIn)
                .build();
    }

    /**
     * 获取用户详情
     *
     * @return 用户信息
     */
    @Override
    public FeishuUserResp getUserInfoByToken(FeiShuTokenResp feiShuTokenVo) {
        String token = feiShuTokenVo.getTokenType() + " " + feiShuTokenVo.getAccessToken();

        String result = HttpUtil.createGet(userInfoUrl)
                .header("Authorization", token)
                .header(Header.CONTENT_TYPE, "application/json")
                .charset("utf-8")
                .execute()
                .body();

        if (StringUtils.isEmpty(result)) {
            throw new RuntimeException("请求飞书用户详情接口失败!");
        }

        JSONObject parseObj = JSONUtil.parseObj(result);
        if ((int) parseObj.get("code") != 0) {
            throw new RuntimeException("获取飞书用户信息失败:" + parseObj.get("msg"));
        }
        Object data = parseObj.get("data");

        FeishuUserResp userResp = JSONUtil.toBean(JSONUtil.toJsonStr(data), FeishuUserResp.class);
        userResp.setAccessToken(feiShuTokenVo.getAccessToken());
        userResp.setRefreshToken(feiShuTokenVo.getRefreshToken());

        // 缓存token
        String tokenKey = GlobalConstant.TOKEN + ":" + feiShuTokenVo.getAccessToken();
        redisService.setCacheObject(tokenKey, feiShuTokenVo.getAccessToken(), feiShuTokenVo.getExpiresIn(), TimeUnit.SECONDS);
        //缓存用户信息
        redisService.setCacheObject(feiShuTokenVo.getAccessToken(), userResp, feiShuTokenVo.getExpiresIn(), TimeUnit.SECONDS);
        //缓存刷新token
        if (Objects.nonNull(feiShuTokenVo.getRefreshToken())) {
            String refreshTokenKey = GlobalConstant.REFRESH_TOKEN + ":" + feiShuTokenVo.getRefreshToken();
            redisService.setCacheObject(refreshTokenKey, feiShuTokenVo.getRefreshToken(), feiShuTokenVo.getRefreshTokenExpiresIn(), TimeUnit.SECONDS);
            //缓存
            redisService.setCacheObject(feiShuTokenVo.getRefreshToken(), userResp, feiShuTokenVo.getRefreshTokenExpiresIn(), TimeUnit.SECONDS);
        }
        return userResp;
    }

    /**
     * 飞书退出登陆
     *
     * @param userId user_id
     * @return 返回
     */
    @Override
    public Response loginOut(HttpServletRequest request, String userId) {
        String token = request.getHeader(GlobalConstant.TOKEN);

        Map<String, Object> params = new HashMap<>(2);
        params.put("logout_type", 1);
        params.put("user_id", userId);

        String result = HttpUtil.createPost(loginOutUrl)
                .header("Authorization", "")
                .header(Header.CONTENT_TYPE, "application/json")
                .charset("utf-8")
                .body(JSONUtil.toJsonStr(params))
                .execute()
                .body();
        if (result == null) {
            throw new RuntimeException("飞书退出登陆失败!");
        }
        JSONObject parseObj = JSONUtil.parseObj(result);

        //删除缓存token
        FeishuUserResp object = redisService.getCacheObject(token);
        redisService.deleteObject(object.getAccessToken());
        redisService.deleteObject(object.getRefreshToken());
        redisService.deleteObject(GlobalConstant.TOKEN + ":" + token);
        redisService.deleteObject(GlobalConstant.REFRESH_TOKEN + ":" + object.getRefreshToken());

        if ((int) parseObj.get("code") == 0) {
            return Response.ok();
        }
        return Response.failed(parseObj.get("msg").toString());
    }

    /**
     * 刷新token
     *
     * @param refreshToken 刷新token
     * @return 返回
     */
    @Override
    public FeishuUserResp refreshToken(String refreshToken) {

        Map<String, String> params = new HashMap<>(4);
        params.put("grant_type", "refresh_token");
        params.put("client_id", appid);
        params.put("client_secret", appsecret);
        params.put("refresh_token", refreshToken);

        String result = HttpUtil.createPost(refreshTokenUrl)
                .header(Header.CONTENT_TYPE, "application/json")
                .charset("utf-8")
                .body(JSONUtil.toJsonStr(params))
                .execute()
                .body();

        if (Objects.isNull(result)) {
            throw new RuntimeException("刷新token失败!");
        }

        JSONObject parseObj = JSONUtil.parseObj(result);
        if (20037 == (int) parseObj.get("code")) {
            throw new RuntimeException("refresh_token已过期");
        }
        if (0 != (int) parseObj.get("code")) {
            throw new RuntimeException("请求飞书刷新token接口失败:" + parseObj.get("error_description"));
        }
        ;
        //token
        String token = (String) parseObj.get("access_token");
        //token过期时间
        long expiresIn = (long) parseObj.get("expires_in");
        //刷新token
        String refreshToken1 = (String) parseObj.get("refresh_token");
        //刷新token 过期时间
        long refreshTokenExpiresIn = (long) parseObj.get("refresh_token_expires_in");

        FeishuUserResp resp = redisService.getCacheObject(refreshToken);
        resp.setAccessToken(token);
        resp.setRefreshToken(refreshToken1);
        //缓存token
        redisService.setCacheObject(GlobalConstant.TOKEN + ":" + token, token, expiresIn, TimeUnit.MINUTES);
        redisService.setCacheObject(token, resp, expiresIn, TimeUnit.MINUTES);

        Object object = redisService.getCacheObject(GlobalConstant.REFRESH_TOKEN + ":" + refreshToken1);
        if (Objects.isNull(object)) {
            redisService.setCacheObject(GlobalConstant.REFRESH_TOKEN + ":" + refreshToken1, refreshToken1, refreshTokenExpiresIn, TimeUnit.MINUTES);
            redisService.setCacheObject(refreshToken1, resp, refreshTokenExpiresIn, TimeUnit.MINUTES);
        }
        return resp;
    }
}

yml文件飞书相关配置

复制代码
#  飞书相关配置
feishu:
  app:
    id: "xxxxxx"
    secret: "xxxxxx"
    # 获取token地址
  userAccessTokenUrl: "https://open.feishu.cn/open-apis/authen/v2/oauth/token"
  # 回调地址(可更改)
  redirectUrl: "http://xxxxxx/upgrade/login"
  # 获取用户信息地址
  userInfoUrl: "https://open.feishu.cn/open-apis/authen/v1/user_info"
  # 登出
  logOutUrl: "https://open.feishu.cn/open-apis/passport/v1/sessions/logout"
  # 刷新token地址
  refreshTokenUrl: "https://open.feishu.cn/open-apis/authen/v2/oauth/token"

学习笔记仅供学习使用,更详细步骤请移步官方文档。

相关推荐
system_error_x16 分钟前
SpringBoot 集成 Thymeleaf 简洁入门
java·spring boot·后端
酷炫码神28 分钟前
Day 10|Pinia 状态管理:把散落在各组件的数据,收进一个全局仓库
android·开发语言·javascript
驭渊的小故事1 小时前
Spring AI Alibaba 实战:从maven依赖治理到声明式工具调用
java·人工智能·spring
Elastic 中国社区官方博客1 小时前
我们如何通过 70 多个拉取请求,将 OpenTelemetry Java agent 迁移到 invokedynamic
java·大数据·开发语言·elasticsearch·搜索引擎·全文检索·es
tyn1881 小时前
记录本地安装nacos服务的方法及异常
java·开发语言
步行cgn2 小时前
Spring MVC 视图机制的核心接口与类
java·开发语言·后端·spring·mvc
y1su2 小时前
Leetcode 字符串模板
java·数据结构·算法·leetcode·职场和发展·动态规划
传奇开心果编程3 小时前
【Rust入门练中学】第5课:引用与借用
开发语言·rust·引用·编程语言理论·通用编程语言·借用
qetfw3 小时前
Bash 变量:export、位置参数与默认值展开验证
linux·开发语言·chrome·bash
电子云与长程纠缠3 小时前
UE5魂系游戏开发教程05
开发语言·学习·游戏·ui·ue5