1、单点登录(SSO)
Single Sign-On。指用户仅需一次登录,即可访问全部应用的实现,在历史中根据应用变化,SSO 也有多种实现形态。在这次接入中,是基于oauth2.0协议进行实现的。
授权协议,虽不是为了 SSO 设计,但也经常用于实现 SSO。由于 OIDC 协议基于 OAuth 2.0 协议实现,两者很多支持的模式是互通的。而对于全总的接入中心,我们是采用了Oauth2.0中的授权码模式对外开放openApi来让各个地方服务平台进行接入;Authorization Code 模式,被授权方是用户,应用通过授权码模式,可获取三方系统身份信息,并以该身份进行登录。常见的钉钉登录、微信登录等均采用授权码模式。
1.1 流程解释
一般来说,是先要在对应平台申请服务,然后我们的服务就会出现在对方app上,在点击该服务的时候就将对方app的登录信息自动带到我们的服务中,一般会是一个agentId参数(应用的唯一ID)和一个authorizationCode(授权码),我们够通过授权码拿到accessToken(第三方应用的授权登陆凭证/登陆令牌),再通过这个accessToken拿到用户信息,此时再进行登录把拿到的用户信息存入我们自己的数据库即可,之间肯定也会用到appKey(appKey和appSecret用于第三方应用调用接入中心接口时,对接口参数加签验签时使用)和appSecret(对接口参数加签验签时使用)
1.2 前端示例代码
//主代码
<script>
import fw from "@/api/fw/fw";
import {linkToClient,getBntConfigCache,getPageFilter} from "@/utils/api";
import {setUserId,setSign,getUserId,setIdCard,getIdCard} from "@/utils/common";
import {jsToCallApp} from "@/pages/quanZong/uniapp-callApp-1.0.0";
export default{
data(){
return{
name:'',
idCard:''
}
},
methods:{
openAppFunction(type,e){
// uni.clearStorageSync();
// alert(".................getIdCard():"+getIdCard("idCard"));
if(getIdCard("idCard")!=null && getIdCard("idCard")!='') {
this.toPage(e);
}else{
jsToCallApp(
{
type: type,
},
(data) => {
if('-1'!==data && data){
// alert(".................data2:"+data);
data = JSON.parse(data);
this.name = data.name;
this.idCard = data.idCard;
this.toPage(e);
showContentDom.innerHTML = data;
}
},
)
}
},
authLogin(authorizationCode){
this.showLoding();
return new Promise((resolve, reject) => {
this.$http.get('/third/client/quanZong/accessToken', {
params:{
authorizationCode:authorizationCode
}
}).then(res=>{
this.hideLoding();
if(res.code == 0){
let accessToken = res.data.accessToken;
let expire = res.data.expire;
let refreshToken = res.data.refreshToken;
this.$http.get('/third/client/quanZong/userInfo', {
params:{
accessToken:accessToken
}
}).then(res=>{
if(res.code == 0){
this.$http.post('/third/client/quanZong/authLogin', {
mobile:res.data.mobile,
nickname:res.data.name,
username:this.name,
idCard:this.idCard,
gender:res.data.gender,
avatarUrl:res.data.avatarUrl,
userType:res.data.userType,
did:res.data.did,
userId:res.data.userId
}).then(res=>{
this.hideLoding();
if(res.code == 200){
let r = res.data;
setUserId(r.id);
setSign(r.signStr);
setIdCard(r.idCard);
resolve(true);
} else{
this.toast(res.msg);
resolve(false);
}
})
} else{
this.toast(res.msg);
resolve(false);
}
})
} else{
this.toast(res.msg);
resolve(false);
}
})
})
},
async toPage(e){
if(!e.authorizationCode || !e.state){
uni.showToast({
title:"授权失败,请重新进入",
icon:"none"
})
/* setTimeout(function(){
window.history.back();
},2000) */
return;
}
let res= await this.authLogin(e.authorizationCode);
if(res){
let url="";
//政策咨询
if(e.state==1){
// url="/pages/jingtong/policy-advisory?id=1";
url="/pages/want-help/seek-help?id=1";
}else if(e.state==2){
//法律援助
// url="/pages/jingtong/policy-advisory?id=2";
linkToClient("https://ff.bjzgh12351.org/ff/#/","法律服务",'client');
return;
}
else if(e.state==3){
//互助保障
// url="/pages/jingtong/policy-advisory?id=3";
url="/pages/mutual-guarantee/mutual-guarantee";
}
else if(e.state==4){
//困难帮扶
// url="/pages/jingtong/policy-advisory?id=4";
url="/pages/want-help/seek-help?id=4";
}
else if(e.state==5){
//职业介绍
// url="/pages/jingtong/policy-advisory?id=5";
url="/pages/want-help/seek-help?id=5";
}
else if(e.state==6){
//我要入会
url="/pages/jingtong/application-form";
}
else if(e.state==7){
//就业招聘
url="/pages/jingtong/professional-integrity";
}
else if(e.state==8){
//参保活动查询
url="/pages/jingtong/activity-process";
}
else if(e.state==9){
//保障给付查询
url="/pages/jingtong/guarantee-payment";
}
else if(e.state==10){
//个人理赔申报
url="/pages/jingtong/personal-claims";
}
else if(e.state==11){
//创业贷款申请
url="/pages/jingtong/petty-loan";
}
else if(e.state==12){
//保障活动资讯
url="/pages/jingtong/mutual-guarantee";
}
else if(e.state==13){
//驿站
url="/pages/jingtong/site-map";
}
else if(e.state==14){
//法律服务
linkToClient("https://ff.bjzgh12351.org/ff/#/","法律服务",'client');
return;
}
else if(e.state==15){
//心理关爱
linkToClient("https://bjzgxl.pemcloud.cn/index.php/M3djk/member/getCode","心理关爱",'client');
return;
}
else if(e.state==16){
//电子书屋
linkToClient("https://mo.591adb.cn/bjszgh.html","电子书屋",'client');
return;
}else if(e.state==17){
//我要求助
url="/pages/want-help/seek-help";
}
uni.reLaunch({
url: url
})
}
}
},
onLoad(e) {
var that = this;
setTimeout(function(){
that.openAppFunction('openKbToAuth',e);
},300);
// this.toPage(e);
},
}
</script>
//JS
// #ifdef H5
function initUserPlugin() {
// 这里根据移动端原生的 userAgent 来判断当前是 Android 还是 ios
// alert('>>>>>>>>>进来1>>>>>>>>')
const u = navigator.userAgent
// Android终端
const isAndroid = u.startsWith("QzApp") && u.indexOf('Android') > -1 || u.indexOf('Adr') > -1
// IOS 终端
const isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)
/**
* 配合 IOS 使用时的初始化方法
*/
const iosFunction = (callback) => {
if (window.WebViewJavascriptBridge) { return callback(window.WebViewJavascriptBridge) }
if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback) }
window.WVJBCallbacks = [callback]
var WVJBIframe = document.createElement('iframe')
WVJBIframe.style.display = 'none'
WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__'
document.documentElement.appendChild(WVJBIframe)
setTimeout(function () {
document.documentElement.removeChild(WVJBIframe)
}, 0)
}
/**
* 配合 Android 使用时的初始化方法
*/
const androidFunction = (callback) => {
if (window.WebViewJavascriptBridge) {
callback(window.WebViewJavascriptBridge)
} else {
document.addEventListener('WebViewJavascriptBridgeReady', function () {
callback(window.WebViewJavascriptBridge)
}, false)
}
}
window.setupWebViewJavascriptBridge = isAndroid ? androidFunction : isIOS ? iosFunction : androidFunction
window.setupWebViewJavascriptBridge(function (bridge) {
})
}
initUserPlugin()
// #endif
// js注册方法
export function appCallJsRegister(name, callBack) {
// initUserPlugin()
window.setupWebViewJavascriptBridge(
bridge => {
bridge.registerHandler(name, callBack)
}
)
}
// js传递数据给java
export function jsToCallApp(data, callBack) {
// initUserPlugin()
if (window.WebViewJavascriptBridge) {
window.WebViewJavascriptBridge.callHandler(
'h5CallApp',
data,
function (responseData) {
// 回传的数据
callBack && callBack(responseData)
}
)
} else {
callBack && callBack('-1')
}
}
export function toShowBack(isShow) {
jsToCallApp({
type: 'showLeftBack', // 需要操作的事件类型
isShow: isShow,
},
(backData) => {
console.log(backData)
},)
}
1.3 后端示例代码
①Controller层
package com.yuqiaotech.third.feign.controller.quanzong;
import com.alibaba.fastjson.JSONObject;
import com.gbs.saas.sdk.oauth2.authentication.client.ISsoAuthenClient;
import com.gbs.saas.sdk.oauth2.authentication.client.dto.request.AccessTokenRefreshRequest;
import com.gbs.saas.sdk.oauth2.authentication.client.dto.request.AccessTokenRequest;
import com.gbs.saas.sdk.oauth2.authentication.client.dto.request.UserInfoQueryRequest;
import com.gbs.saas.sdk.oauth2.authentication.client.dto.response.AccessTokenResponse;
import com.gbs.saas.sdk.oauth2.authentication.client.dto.response.UserInfoQueryResponse;
import com.gbs.saas.sdk.oauth2.authentication.common.model.Result;
import com.yuqiaotech.base.BaseController;
import com.yuqiaotech.domain.UserInfo;
import com.yuqiaotech.estivate.aop.DataSource;
import com.yuqiaotech.feign.third.model.QuanZongUserInfoDto;
import com.yuqiaotech.model.R;
import com.yuqiaotech.third.feign.service.quanzong.QuanZongThirdFeign;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.UUID;
@RestController
@RequestMapping("/client/quanZong")
@DataSource
public class QuanZongController extends BaseController {
@Autowired
private ISsoAuthenClient ssoAuthenClient;
@Autowired
private QuanZongThirdFeign quanZongThirdFeign;
@PostMapping("/authLogin")
public R authLogin(@RequestBody QuanZongUserInfoDto quanZongUserInfoDto){
UserInfo me = getCurrentUserInfox();
R r = new R();
try {
if(me==null){
r= quanZongThirdFeign.authLogin(quanZongUserInfoDto);
}else{
JSONObject json = new JSONObject();
Long userInfoId = me.getId();
json.put("id", userInfoId);
json.put("userType", me.getUserType());
String pwd = me.getPassword();
if(pwd == null || "".equals(pwd))pwd = me.getId()+"";
String signature2 = DigestUtils.md5Hex(pwd);
signature2 = DigestUtils.md5Hex(signature2);
json.put("signStr",signature2);
String idCard = me.getIdCard();
if(idCard == null || "".equals(idCard)){
json.put("idCard",null);
r.setCode(250);
r.setMsg("身份信息未录入,请稍后进入重新授权!");
r.setData(json);
return r;
}else {
String idCard2 = DigestUtils.md5Hex(idCard);
idCard2 = DigestUtils.md5Hex(idCard2);
json.put("idCard",idCard2);
}
r.setCode(200);
r.setMsg("登录成功");
r.setData(json);
}
} catch (Exception e) {
e.printStackTrace();
r.setCode(500);
r.setMsg("服务异常");
}
return r;
}
/**
* 授权码交换accessToken
* @param request
* @param authorizationCode
* @return
*/
@GetMapping("/accessToken")
public Result<AccessTokenResponse> getAccessToken(HttpServletRequest request, String authorizationCode){
AccessTokenRequest accessTokenRequest=new AccessTokenRequest(authorizationCode);
accessTokenRequest.setRequestId(UUID.randomUUID().toString());
Result<AccessTokenResponse> result=this.ssoAuthenClient.accessToken(accessTokenRequest);
return result;
}
/**
* 获取登陆用户信息
* @param request
* @param accessToken
* @return
*/
@GetMapping("/userInfo")
public Result<UserInfoQueryResponse> getUserInfo(HttpServletRequest request, String accessToken){
UserInfoQueryRequest userQuery=new UserInfoQueryRequest();
userQuery.setAccessToken(accessToken);
// userQuery.setIncludeOrganization(false);
// userQuery.setIncludeRole(false);
Result<UserInfoQueryResponse> result=this.ssoAuthenClient.getUserInfo(userQuery);
return result;
}
/**
* 刷新accessToken
* @param request
* @param refreshToken
* @return
*/
@GetMapping("/refreshToken")
public Result<AccessTokenResponse> refreshToken(HttpServletRequest request,String refreshToken){
AccessTokenRefreshRequest accessTokenRefreshRequest=new AccessTokenRefreshRequest(refreshToken);
accessTokenRefreshRequest.setRefreshToken(refreshToken);
Result<AccessTokenResponse> result=this.ssoAuthenClient.refreshToken(accessTokenRefreshRequest);
return result;
}
}
②Service层
package com.yuqiaotech.third.feign.service.quanzong.impl;
import com.alibaba.fastjson.JSONObject;
import com.gbs.saas.sdk.oauth2.authentication.common.utils.SsoEncryptUtil;
import com.yuqiaotech.domain.UserInfo;
import com.yuqiaotech.feign.third.model.QuanZongUserInfoDto;
import com.yuqiaotech.model.R;
import com.yuqiaotech.repository.BaseRepository;
import com.yuqiaotech.service.UserInfoBaseService;
import com.yuqiaotech.third.feign.service.quanzong.QuanZongThirdFeign;
import com.yuqiaotech.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class QuanZongThirdFeignService implements QuanZongThirdFeign {
@Value("${sso.appSecret}")
private String appSecret;
@Autowired
private BaseRepository<UserInfo, Long> userInfoLongBaseRepository;
@Autowired
private UserInfoBaseService userInfoBaseService;
@Autowired
RedisUtils redisUtils;
@Override
public R authLogin(QuanZongUserInfoDto quanZongUserInfoDto) {
log.info("appSecret-------------",appSecret);
JSONObject json = new JSONObject();
//解密手机号
String phone = SsoEncryptUtil.aesDecryptHex(appSecret,quanZongUserInfoDto.getMobile());
//拿到身份证信息
String idCard = quanZongUserInfoDto.getIdCard();
UserInfo userInfo=userInfoLongBaseRepository.findUniqueBy("mobile",phone,UserInfo.class);
//用户可以通过手机号查到且身份证号一致 就说明是同一个用户
if(userInfo!=null && userInfo.getIdCard().equals(quanZongUserInfoDto.getIdCard())){
//身份证号
userInfo.setIdCard(idCard);
String idCard2 = DigestUtils.md5Hex(idCard);
idCard2 = DigestUtils.md5Hex(idCard2);
json.put("idCard",idCard2);
}else {
userInfo = new UserInfo();
//手机号 解密后存入
userInfo.setMobile(phone);
//昵称
userInfo.setNickName(quanZongUserInfoDto.getNickname());
//名称
userInfo.setUsername(quanZongUserInfoDto.getUsername());
//性别
userInfo.setSex(quanZongUserInfoDto.getGender());
//头像
userInfo.setWxLogo(quanZongUserInfoDto.getAvatarUrl());
//用户类型
userInfo.setSubUserType("全总");
// //用IdCard来存放did信息
// userInfo.setIdCard(quanZongUserInfoDto.getDid());
// //用CId来存放userId
// userInfo.setCId(quanZongUserInfoDto.getUserId());
if(idCard == null || "".equals(idCard)){
json.put("idCard",null);
return R.tips(json, "身份信息未录入,请稍后进入重新授权!");
}else {
//身份证号
userInfo.setIdCard(idCard);
String idCard2 = DigestUtils.md5Hex(idCard);
idCard2 = DigestUtils.md5Hex(idCard2);
json.put("idCard",idCard2);
}
userInfoLongBaseRepository.save(userInfo);
}
userInfoBaseService.toRedis(userInfo);
Long userInfoId = userInfo.getId();
json.put("id", userInfoId);
json.put("userType", userInfo.getUserType());
String pwd = userInfo.getPassword();
if(pwd == null || "".equals(pwd))pwd = userInfo.getId()+"";
String signature2 = DigestUtils.md5Hex(pwd);
signature2 = DigestUtils.md5Hex(signature2);
json.put("signStr",signature2);
return R.ok(json, "登录成功");
}
}
1.4 引入第三方jar包
在resources中新建一个lib放入你需要引入的jar包
然后要在pom.xml中进行配置,因为我这边有几个jar包所以要配置多次
最后配置打jar/war包时,打入自己的jar包
如果要打war包需要下面这套代码
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<webResources>
<resource>
<!-- 指向的是包含你所有要用jar包的目录 -->
<directory>${project.basedir}/src/main/resources/lib</directory>
<!-- 编译后要把这些jar包复制到的位置 -->
<targetPath>WEB-INF/lib</targetPath>
</resource>
</webResources>
</configuration>