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"

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

相关推荐
Scott9999HH3 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔3 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社4 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海4 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖5 小时前
JDK 26 新特性详解
java·开发语言
马优晨6 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区8 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大8 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai8 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说8 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json