企业微信单点登录集成

news/2025/3/21 20:30:35/

1.进入企业微信后台应用管理配置应用信息

2.配置网页授权及JS-SDK (设置可信域名需要和企业信息相关)

通过配置IIS设置可快速解决,在自己的域名管理平台配置域名解析到自己的服务器上;

window搜索IIS 

配置web信息,将下载的文件放到F盘下

企业微信认证成功

配置应用主页回调URL(参考信息开始开发 - 文档 - 企业微信开发者中心)

corpid==》 企业账号(我的企业里面企业ID)

agentid==>应用ID

secret==> 应用secret

redirect_url==》单点登录页面URL(域名:端口/login_sso之类的登录页)

3.前端构造页面(基于ruoyi集成参考)

构造首页

<template><div></div>
</template><script>
import { getInfo } from "@/api/login";export default {name: "Loginsso",data() {return {loginRules: {},loading: false,//验证码开关captchaOnOff: true,//注册开关register: false,//重定向redirect: undefined};},watch: {},created() {//页面初始化时调单点登录方法this.loginSso();},methods: {loginSso() {//获取地址栏中的codeconst code = this.$route.query.code;console.log("code=" + code)//调用登录的接口if (code == '' || code == undefined || code == null) {//请求中不带code,拦截为正常登录// this.$message.error("请通企业微信客户端打开,登录失败,请重新登录");this.$router.push({ path: "/login" }).catch(() => { });} else {//有code,走单点登录流程//    Pg4YaTLuPL5f5DA4IGuAGkztU3gj1G3JpQe5XeIjq5E// this.$message.error(code);this.loading = true;//开启过渡动画const loginIfo = {code: code,}//执行另一套登录操作//不是本系统的用户,去J平台登陆去this.$store.dispatch("LoginSso", loginIfo).then(() => {this.$message.success("登录成功");this.loading = false;//判断当前角色this.$router.push({ path: this.redirect || "/" }).catch(() => { });}).catch(err => {console.log("单点登录异常", err);this.$router.push({ path: "/login" }).catch(() => { });this.loading = false;});}},}
};
</script><style rel="stylesheet/scss" lang="scss"></style>

配置路由

{path: '/loginSso',component: () => import('@/views/login_sso'),hidden: true},

配置登录方法

import {login, logout, getInfo, refreshToken,loginSso} from '@/api/login'
import {getToken, setToken, setExpiresIn, removeToken} from '@/utils/auth'const user = {state: {token: getToken(),name: '',avatar: '',roles: [],permissions: []},mutations: {SET_TOKEN: (state, token) => {state.token = token},SET_EXPIRES_IN: (state, time) => {state.expires_in = time},SET_USERID: (state, userId) => {state.userId = userId},SET_NAME: (state, name) => {state.name = name},SET_NICK_NAME: (state, nickName) => {state.nickName = nickName},SET_AVATAR: (state, avatar) => {state.avatar = avatar},SET_ROLES: (state, roles) => {state.roles = roles},SET_PERMISSIONS: (state, permissions) => {state.permissions = permissions}},actions: {// 登录Login({commit}, userInfo) {const username = userInfo.username.trim()const password = userInfo.passwordconst code = userInfo.codeconst uuid = userInfo.uuidreturn new Promise((resolve, reject) => {login(username, password, code, uuid).then(res => {let data = res.datasetToken(data.access_token)commit('SET_TOKEN', data.access_token)setExpiresIn(data.expires_in)commit('SET_EXPIRES_IN', data.expires_in)resolve()}).catch(error => {reject(error)})})},LoginSso({commit}, userInfo){const code = userInfo.codereturn new Promise((resolve, reject) => {loginSso(code).then(res => {let data = res.datasetToken(data.access_token)commit('SET_TOKEN', data.access_token)setExpiresIn(data.expires_in)commit('SET_EXPIRES_IN', data.expires_in)// setToken(res.token)// commit('SET_TOKEN', res.token)resolve()}).catch(error => {reject(error)})})},// 获取用户信息GetInfo({commit, state}) {return new Promise((resolve, reject) => {getInfo().then(res => {const user = res.userconst avatar = (user.avatar == "" || user.avatar == null) ? require("@/assets/images/profile.jpg") : user.avatar;if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组commit('SET_ROLES', res.roles)commit('SET_PERMISSIONS', res.permissions)} else {commit('SET_ROLES', ['ROLE_DEFAULT'])}commit('SET_USERID', user.userId)commit('SET_NAME', user.userName)commit('SET_NICK_NAME', user.nickName)commit('SET_AVATAR', avatar)resolve(res)}).catch(error => {reject(error)})})},// 刷新tokenRefreshToken({commit, state}) {return new Promise((resolve, reject) => {refreshToken(state.token).then(res => {setExpiresIn(res.data)commit('SET_EXPIRES_IN', res.data)resolve()}).catch(error => {reject(error)})})},// 退出系统LogOut({commit, state}) {return new Promise((resolve, reject) => {logout(state.token).then(() => {commit('SET_TOKEN', '')commit('SET_ROLES', [])commit('SET_PERMISSIONS', [])removeToken()resolve()}).catch(error => {reject(error)})})},// 前端 登出FedLogOut({commit}) {return new Promise(resolve => {commit('SET_TOKEN', '')removeToken()resolve()})}}
}export default user

配置白名单

4.后端构造页面

网关配置白名单

拦截器放行

5.登录接口

 @Value("${qywx.corp-id}")private String qwcorpid;//参数从配置文件中获取可配置到nacos@Value("${qywx.application-list[0].secret}")private String qwcorpsecret;@PostMapping("loginSso")public R<?> loginSso(@RequestBody LoginBody form) {// 企业idString corpid =qwcorpid ;// 应用内 SecretString corpsecret =qwcorpsecret ;String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={corpid}&corpsecret={corpsecret}";RestTemplate restTemplate = new RestTemplate();QywxToken forObject = restTemplate.getForObject(url, QywxToken.class, corpid, corpsecret);String accessToken = null;if (forObject != null) {accessToken = forObject.getAccess_token();}else{return  R.fail("登录异常");}String url2 = "https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token={ACCESS_TOKEN}&code={CODE}";QywxLoginInfo qywxLoginInfo = restTemplate.getForObject(url2, QywxLoginInfo.class, accessToken, form.getCode());/*获取人员信息账号*/String url3 = "https://qyapi.weixin.qq.com/cgi-bin/auth/getuserdetail?access_token=" + accessToken;String requestBody = String.format("{\"user_ticket\": \"%s\"}", qywxLoginInfo.getUser_ticket());HttpHeaders headers = new HttpHeaders();HttpEntity<String> request = new HttpEntity<>(requestBody, headers);headers.setContentType(MediaType.APPLICATION_JSON); // 设置 Content-Type 为 application/jsonQywxUser qywxUser  = restTemplate.postForObject(url3, request, QywxUser.class);/*设置登录信息*/// 用户登录LoginUser userInfo = sysLoginService.loginNoCaptcha(qywxUser.getUserid(),qywxUser.getMobile());// 获取登录tokenreturn R.ok(tokenService.createToken(userInfo));}

6.实体类

package com.ruoyi.auth.domain;public class Acess {private static final long serialVersionUID = 1L;private  String corpid;private  String corpsecret;public String getCorpid() {return corpid;}public void setCorpid(String corpid) {this.corpid = corpid;}public String getCorpsecret() {return corpsecret;}public void setCorpsecret(String corpsecret) {this.corpsecret = corpsecret;}public Acess(String corpid, String corpsecret) {this.corpid = corpid;this.corpsecret = corpsecret;}
}
package com.ruoyi.auth.domain;public class QywxLoginInfo {private Integer errcode;private String errmsg;private String userid;private String user_ticket;public QywxLoginInfo(Integer errcode, String errmsg, String userid, String user_ticket) {this.errcode = errcode;this.errmsg = errmsg;this.userid = userid;this.user_ticket = user_ticket;}public QywxLoginInfo() {}public Integer getErrcode() {return errcode;}public void setErrcode(Integer errcode) {this.errcode = errcode;}public String getErrmsg() {return errmsg;}public void setErrmsg(String errmsg) {this.errmsg = errmsg;}public String getUserid() {return userid;}public void setUserid(String userid) {this.userid = userid;}public String getUser_ticket() {return user_ticket;}public void setUser_ticket(String user_ticket) {this.user_ticket = user_ticket;}@Overridepublic String toString() {return "QywxLoginInfo{" +"errcode=" + errcode +", errmsg='" + errmsg + '\'' +", userid='" + userid + '\'' +", user_ticket='" + user_ticket + '\'' +'}';}
}
package com.ruoyi.auth.domain;public class QywxToken {private String access_token;private Integer errcode;private String errmsg;private Integer expires_in;public QywxToken(String access_token, Integer errcode, String errmsg, Integer expires_in) {this.access_token = access_token;this.errcode = errcode;this.errmsg = errmsg;this.expires_in = expires_in;}public String getAccess_token() {return access_token;}public void setAccess_token(String access_token) {this.access_token = access_token;}public Integer getErrcode() {return errcode;}public void setErrcode(Integer errcode) {this.errcode = errcode;}public String getErrmsg() {return errmsg;}public void setErrmsg(String errmsg) {this.errmsg = errmsg;}public Integer getExpires_in() {return expires_in;}public void setExpires_in(Integer expires_in) {this.expires_in = expires_in;}public QywxToken() {}@Overridepublic String toString() {return "QywxToken{" +"access_token='" + access_token + '\'' +", errcode=" + errcode +", errmsg='" + errmsg + '\'' +", expires_in=" + expires_in +'}';}
}
package com.ruoyi.auth.domain;import java.util.List;public class QywxUser {/** 用户id */private String errcode;private String errmsg;/** 用户id */private String userid;/** 性别 */private String gender;/** 头像url */private String avatar;private String qr_code;/** 手机号码 */private String mobile;/** 邮箱 */private String email;private String biz_mail;private String address;public QywxUser(String errcode, String errmsg, String userid, String gender, String avatar, String qr_code, String mobile, String email, String biz_mail, String address) {this.errcode = errcode;this.errmsg = errmsg;this.userid = userid;this.gender = gender;this.avatar = avatar;this.qr_code = qr_code;this.mobile = mobile;this.email = email;this.biz_mail = biz_mail;this.address = address;}public QywxUser() {}public String getErrcode() {return errcode;}public void setErrcode(String errcode) {this.errcode = errcode;}public String getErrmsg() {return errmsg;}public void setErrmsg(String errmsg) {this.errmsg = errmsg;}public String getUserid() {return userid;}public void setUserid(String userid) {this.userid = userid;}public String getGender() {return gender;}public void setGender(String gender) {this.gender = gender;}public String getAvatar() {return avatar;}public void setAvatar(String avatar) {this.avatar = avatar;}public String getQr_code() {return qr_code;}public void setQr_code(String qr_code) {this.qr_code = qr_code;}public String getMobile() {return mobile;}public void setMobile(String mobile) {this.mobile = mobile;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}public String getBiz_mail() {return biz_mail;}public void setBiz_mail(String biz_mail) {this.biz_mail = biz_mail;}public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}@Overridepublic String toString() {return "QywxUser{" +"errcode='" + errcode + '\'' +", errmsg='" + errmsg + '\'' +", userid='" + userid + '\'' +", gender='" + gender + '\'' +", avatar='" + avatar + '\'' +", qr_code='" + qr_code + '\'' +", mobile='" + mobile + '\'' +", email='" + email + '\'' +", biz_mail='" + biz_mail + '\'' +", address='" + address + '\'' +'}';}
}
package com.ruoyi.auth.domain;public class UserCode {private static final long serialVersionUID = 1L;private  String access_token;private String code;public String getAccess_token() {return access_token;}public void setAccess_token(String access_token) {this.access_token = access_token;}public String getCode() {return code;}public void setCode(String code) {this.code = code;}public UserCode(String access_token, String code) {this.access_token = access_token;this.code = code;}}

7.无验证码登录方法

 public LoginUser loginNoCaptcha(String  username,String mobaile){// IP黑名单校验String blackStr = Convert.toStr(redisService.getCacheObject(CacheConstants.SYS_LOGIN_BLACKIPLIST));if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) {recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "很遗憾,访问IP已被列入系统黑名单");throw new ServiceException("很遗憾,访问IP已被列入系统黑名单");}// 查询用户信息R<LoginUser> userResult = remoteUserService.getByMobile(mobaile, SecurityConstants.INNER);if (StringUtils.isNull(userResult)) {recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "登录用户手机号为绑定");throw new ServiceException("登录用户:" + username + " 手机号为绑定");}if (StringUtils.isNull(userResult.getData())) {recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "登录用户未获取,请重新登录");throw new ServiceException("登录用户:" + username + "未获取,请重新登录");}if (R.FAIL == userResult.getCode()) {throw new ServiceException(userResult.getMsg());}LoginUser userInfo = userResult.getData();SysUser user = userResult.getData().getSysUser();if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "对不起,您的账号已被删除");throw new ServiceException("对不起,您的账号:" + username + " 已被删除");}if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户已停用,请联系管理员");throw new ServiceException("对不起,您的账号:" + username + " 已停用");}recordLogService.recordLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功");return userInfo;}

openfeign调用方法 在api-system包内

 @PostMapping("/user/register")public R<Boolean> registerUserInfo(@RequestBody SysUser sysUser, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);

8.找到对应服务下的接口编写接口和方法

  @InnerAuth@GetMapping("/info/getByMobile/{mobile}")public R<LoginUser> getByMobile(@PathVariable("mobile") String mobile) {System.out.println("------------------");SysUser sysUser = userService.selectUserByMobile(mobile);if (StringUtils.isNull(sysUser)) {return R.fail("未查询到指定账户,请联系管理员绑定手机号");}// 角色集合Set<String> roles = permissionService.getRolePermission(sysUser);// 权限集合Set<String> permissions = permissionService.getMenuPermission(sysUser);permissions.add("ROLE_ACTIVITI_USER");LoginUser sysUserVo = new LoginUser();sysUserVo.setSysUser(sysUser);System.out.println("==================");sysUserVo.setRoles(roles);System.out.println(roles);sysUserVo.setPermissions(permissions);System.out.println(sysUserVo);return R.ok(sysUserVo);}
  <select id="selectUserByMobile"   parameterType="String" resultMap="SysUserResult"><include refid="selectUserVo"/>where u.phonenumber = #{phonenumber} and u.del_flag = '0'</select>


http://www.ppmy.cn/news/1580963.html

相关文章

如何启用 HTTPS 并配置免费的 SSL 证书

引言 HTTPS 已成为现代网站安全性的基础要求。通过 SSL/TLS 证书对数据进行加密&#xff0c;不仅可以保护用户隐私&#xff0c;还能提升搜索引擎排名并增强用户信任。本指南将详细介绍如何通过 Lets Encrypt&#xff08;免费、自动化的证书颁发机构&#xff09;为您的网站启用…

C++内存分配方式

文章目录 1、静态内存分配2、栈内存分配3、堆内存分配4、内存池分配5、placement new语法工作原理示例 placement new应用场景 在C 中&#xff0c;内存分配主要有以下几种方式&#xff1a; 1、静态内存分配 特点&#xff1a;在编译时就确定了内存的分配和释放&#xff0c;内存…

qml中ComboBox组件onCurrentIndexChanged与onActivated的使用

在qml页面中使用ComboBox时&#xff0c;一般会有以下用法&#xff1a; ComboBox{id: boxmodel: yourBindingModelonCurrentIndexChanged: { //业务代码} } 通常不会有什么问题&#xff0c;切换下拉列表时触发onCurrentIndexChanged&#xff0c;然后执行业务代码。 但是&am…

MATLAB语法速成-对照C语言学习

一、变量与数据类型 变量声明&#xff1a;MATLAB 是动态类型语言&#xff0c;无需提前声明变量类型。例如&#xff1a; a 5; % 定义一个整数变量 b 3.14; % 定义一个浮点数变量 c Hello; % 定义一个字符串变量数据类型&#xff1a;常见的数据类型有数值类型&#xff08;整…

jvm中每个类的Class对象是唯一的吗

jvm中每个类的Class对象是唯一的吗 在 Java 中&#xff0c;同一个类的 Class 对象在由同一个类加载器加载时是唯一的。析&#xff1a; 1. 同一类加载器的唯一性 规则&#xff1a;若一个类被同一个类加载器加载&#xff0c;无论创建多少实例&#xff0c;其 Class 对象始终唯一…

Redis Sentinel(哨兵模式)高可用性解决方案

一、概述 Redis Sentinel&#xff08;哨兵模式&#xff09;是Redis的高可用性&#xff08;High Availability, HA&#xff09;解决方案&#xff0c;它通过哨兵系统和Redis实例的协同工作&#xff0c;确保了Redis服务的高可用性和数据的持久性。哨兵系统由一个或多个哨兵进程组…

当前企业使用VPN面临的不足和挑战

VPN的防护理念无法满足数字化转型的需求 古人云&#xff1a;知己知彼&#xff0c;百战不殆&#xff0c;既然要替换VPN&#xff0c;就要先了解VPN。VPN于1996年起源&#xff0c;98年首次在我国出现&#xff0c;历经25年的持续演进&#xff0c;直到现在依然广泛流行。VPN的起源背…

Pot-App 本地deepseek-r1 翻译开源插件,支持本地ollama deepseek-r1系列模型,同时在POT翻译窗口不显示模型思考过程

一、软件介绍 文末提供插件及源码下载 此开源插件作為支持本地ollama deepseek-r1系列模型&#xff0c;並在POT输出窗口中不显示模型思考过程。 模型安装&#xff08;根据自己的电脑配置安装相应版本&#xff0c;支持官方1.5b~8b&#xff09; Ollama模型网址&#xff1a;deep…