1. 流程图
微信登录流程如下:
- 点击微信登录,新开窗口,跳转到微信扫码页面
- 用户扫码登录,新开窗口跳转到后台微信登录接口
- 后台判断用户是否成功登录,将新开窗口重定向到前台微信登录处理页面
- 微信登录处理页面会关闭新开窗口,判断用户是否成功登录,如果用户成功登录,则写入cookie并且跳转首页,如果用户没有注册用户,则携带后端的token跳转注册页面
2. 前端代码核心逻辑实现
对于前端来说,主要
第一步 编写前端第三方登录处理类
import URL from 'url-parse'
export default class OauthHandler {
constructor(name, thirdBaseUrl, appId) {
// 当前第三方登录名称
this.name = name
// 第三方登录跳转URL
this.thirdBaseUrl = thirdBaseUrl
// 第三方登录APPID
this.appId = appId
this.newPage = null
}
login() {
const referrer = (window.globalPortalVue.$route.query && window.globalPortalVue.$route.query.referrer) || '/'
localStorage.setItem(constant['COMMON|OAUTH'].referrerLocalName, referrer)
localStorage.setItem(constant['COMMON|OAUTH'].handlerLocalName, constant['COMMON|OAUTH_HANDLER'].login)
this.openThirdPage(constant['COMMON|OAUTH_HANDLER'].login)
}
bind() {
localStorage.setItem(constant['COMMON|OAUTH'].handlerLocalName, constant['COMMON|OAUTH_HANDLER'].bind)
this.openThirdPage(constant['COMMON|OAUTH_HANDLER'].bind)
}
unBind() {
localStorage.setItem(constant['COMMON|OAUTH'].handlerLocalName, constant['COMMON|OAUTH_HANDLER'].unbind)
this.openThirdPage(constant['COMMON|OAUTH_HANDLER'].unbind)
}
async genThirdUrl(type) {
const thirdUrl = new URL(this.thirdBaseUrl)
return thirdUrl
}
getThirdPageSize() {
return {
width: 500,
height: 500
}
}
getType() {
throw new Error('请覆写此方法')
}
async openThirdPage(type) {
const thirdUrl = await this.genThirdUrl(type)
const { width, height } = this.getThirdPageSize()
this.unMountWindowOauthFunction()
this.mountWindowOauthFunction()
const windowHeight = window.screen.availHeight
const windowWidth = window.screen.availWidth
console.log(this.newPage)
if (this.newPage != null && !this.newPage.closed) {
return
}
this.newPage = window.open(
thirdUrl.toString(),
'_blank',
`toolbar=no,directories=no,status=no,menubar=no,height=${height},width=${width},top=${(windowHeight - height) / 2},left=${(windowWidth - width) / 2}`
)
if (this.newPage) {
return
}
window.open(
thirdUrl.toString(),
'_self'
)
}
unMountWindowOauthFunction() {
window.globalPortalVue.$off('oauthCallBack')
}
mountWindowOauthFunction() {
console.log('监听事件')
window.globalPortalVue.$on('oauthCallBack', (result) => {
console.log('监听到事件')
if (result.code === constant['COMMON|CODE'].success) {
const name = localStorage.getItem(constant['COMMON|OAUTH'].handlerLocalName)
this.successHandler(name, result)
} else {
this.errorHandler(result)
}
})
}
successHandler(name) {
console.log('成功')
// 登录
if (name === constant['COMMON|OAUTH_HANDLER'].login) {
new Promise((resolve, reject) => {
event.emit('user/oauthLogin', resolve, reject)
}).then(() => {
new Promise((resolve, reject) => {
event.emit('user/setUserEnterpriseList', resolve, reject)
}).then(enterpriseList => {
loginSuccessRedirect(enterpriseList)
}).catch(err => {
console.error(err.message)
window.globalPortalVue.$Message.error(this.name + '登录失败!')
})
}).catch((err) => {
console.error(err.message)
window.globalPortalVue.$Message.error(this.name + '微信登录失败!')
})
return
}
// 绑定
if (name === constant['COMMON|OAUTH_HANDLER'].bind) {
new Promise((resolve, reject) => {
event.emit('user/updateUserInfo', resolve, reject)
}).then(re => {
window.globalPortalVue.$Message.success('绑定' + this.name + '成功!')
}).catch(err => {
console.error(err)
window.globalPortalVue.$Message.error('绑定' + this.name + '失败,请重试!')
})
}
// 解除绑定
if (name === constant['COMMON|OAUTH_HANDLER'].unbind) {
new Promise((resolve, reject) => {
event.emit('user/updateUserInfo', resolve, reject)
}).then(re => {
window.globalPortalVue.$Message.success('解绑' + this.name + '成功!')
}).catch(err => {
console.error(err)
window.globalPortalVue.$Message.error('解绑' + this.name + '失败,请重试!')
})
}
}
errorHandler(result) {
console.log('授权失败', result)
if (result.code === constant['COMMON|CODE'].error) {
window.globalPortalVue.$Message.error(result.message)
return
}
if (result.code === constant['COMMON|CODE'].userNotFound) {
window.globalPortalVue.$router.push({
path: '/oauthLogin',
query: {
token: result.token,
type: this.getType()
}
})
}
}
}
第三步 实现后台微信登录处理接口
实现微信登录Controller
@RequestMapping("/wechatOpenPlatform")
public class WechatOpenPlatformController{
@Autowired
WechatOauthService wechatOauthService;
@GetMapping("login")
@ApiOperation(value = "微信网页登录", notes = "微信网页登录")
public void login(@PathVariable String region, @NotNull(message = "微信登录参数错误") String code, HttpServletResponse response) {
wechatOauthService.wechatLogin(code, response);
}
}
实现微信登录service实现
public interface WechatOauthService {
/**
* 微信网页登录
*
* @param oauth2Handler
* @param callbackUrl
* @param authTokenForm
* @param response
*/
void wechatLogin(String code,
HttpServletResponse response);
}
@Service
public class WechatOauthServiceImpl implements WechatOauthService {
private static String callBackUrl = "http://www.xxxx.com/#/wechat/callback";
private static String openAppId= "xxxxx";
private static String openSecret= "xxxx";
@Autowired
UserService userService;
@Autowired
AutowireCapableBeanFactory autowireCapableBeanFactory;
@Override
public void wechatLogin (String code, HttpServletResponse response) {
WechatHandler wechatHandler= new WechatHandler(openAppId, openSecret);
// 实现容器外bean注入
autowireCapableBeanFactory.autowireBean(wechatHandler);
// 获取用户token
AuthTokenVo authTokenVo = wechatHandler.fetchToken(code);
// 根据token获取用户信息
WechatUserVo wechatUserVo = wechatHandler.fetchUserInfo(authTokenVo);
// 根据unionId判断用户是否存在
UserMo userMo = userService.selectUserByUnionId(wechatUserVo.getUnionId());
if (userMo == null) {
// 登录失败
response.sendRedirect(callBackUrl + "?code=500&token=" + wechatUserVo.getUnionId());
}
// 调用登录方法,写入cookie
userService.login(userMo);
// 登录成功
response.sendRedirect(callBackUrl + "?code=200");
}
}
// 调用微信接口获取微信信息处理类
public class WechatHandler {
/**
* 应用唯一标识
*/
private String appId;
/**
* 应用密钥
*/
private String secret;
/**
* 请求方法
*/
@Resource
RestTemplate restTemplate;
public WechatHandler(String appId, String secret) {
this.appId = appId;
this.secret = secret;
}
private final static String BASE_URL = "https://api.weixin.qq.com";
private final static String FETCH_TOKEN_GRANT_TYPE = "authorization_code";
private final static String TOKEN_URL =
BASE_URL + "/sns/oauth2/access_token?appid={appId}&secret={secret}&code={code}&grant_type={grantType}";
private final static String USER_INFO_URL = BASE_URL + "/sns/userinfo?access_token={accessToken}&openid={openId}";
@Override
public AuthTokenVo fetchToken(AuthTokenForm authTokenForm) {
Map<String, String> query = new HashMap<>();
query.put("appId", getAppId());
query.put("secret", getSecret());
query.put("code", authTokenForm.getCode());
query.put("grantType", FETCH_TOKEN_GRANT_TYPE);
String result = restTemplate.getForObject(TOKEN_URL, String.class, query);
JSONObject jsonObject = JSON.parseObject(result);
if (jsonObject.getInteger("errcode") != null) {
throw new BusinessException("获取TOKEN失败");
}
AuthTokenVo authTokenVo = new AuthTokenVo(jsonObject);
return authTokenVo;
}
@Override
public AuthTokenVo refreshToken(AuthRefreshTokenForm authRefreshTokenForm) {
return null;
}
@Override
public Object fetchUserInfo(AuthTokenVo authTokenVo) {
Map<String, String> query = new HashMap<>();
query.put("accessToken", authTokenVo.getAccessToken());
query.put("openId", authTokenVo.getOpenId());
String result = restTemplate.getForObject(USER_INFO_URL, String.class, query);
JSONObject jsonObject = JSON.parseObject(result);
if (jsonObject.getInteger("errcode") != null) {
throw new BusinessException("获取用户信息失败");
}
WechatUserVo wechatUserVo = new WechatUserVo(jsonObject);
return wechatUserVo;
}
}
微信用户实体类
@Data
@NoArgsConstructor
public class WechatUserVo {
/**
* OPEN_ID
*/
private String openId;
/**
* 唯一标识
*/
private String unionId;
/**
* 昵称
*/
private String nickName;
/**
* 性别
*/
private Integer sex;
/**
* 省份
*/
private String province;
/**
* 城市
*/
private String city;
/**
* 国家
*/
private String country;
/**
* 头像
*/
private String headImgUrl;
/**
* 特权信息
*/
private String privilege;
public WechatUserVo(JSONObject jsonObject) {
this.unionId = jsonObject.getString("unionid");
this.openId = jsonObject.getString("openid");
this.nickName = jsonObject.getString("nickname");
this.sex = jsonObject.getInteger("sex");
this.province = jsonObject.getString("province");
this.city = jsonObject.getString("city");
this.country = jsonObject.getString("country");
this.headImgUrl = jsonObject.getString("headimgurl");
this.privilege = jsonObject.getString("privilege");
}
}
认证token实体类
@Data
@NoArgsConstructor
public class AuthTokenVo {
/**
* 用户接口调用凭证
*/
private String accessToken;
/**
* token过期时间
*/
private String expiresIn;
/**
* 刷新token凭证
*/
private String refreshToken;
/**
* 用户标识
*/
private String openId;
/**
* 授权作用域
*/
private String scope;
public AuthTokenVo(JSONObject jsonObject) {
this.accessToken = jsonObject.getString("access_token");
this.expiresIn = jsonObject.getString("expires_in");
this.refreshToken = jsonObject.getString("refresh_token");
this.openId = jsonObject.getString("openid");
this.scope = jsonObject.getString("scope");
}
}
微信token请求表单类
@Data
public class AuthTokenForm {
/**
* 用户CODE
*/
private String code;
}