大致流程,点击登录,获取二维码,扫描之后,将微信用户的信息传到后台,判断t_wxuser表中是否已经存在该用户,没有就新保存,有就继续判断是否绑定了t_user账户,没有就绑定账户,有就直接登录。
微信开放平台:https://open.weixin.qq.com/
微信开放文档:https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
要想接入微信的登录功能,首先需要在微信开发平台进行用户的注册,同时需要认证为开发者,再创建网站应用,等待微信审批,审批过后,就可以使用相关功能。
在开放平台注册:
使用邮箱进行注册(最好使用企业邮箱,避免使用私人邮箱,后续需要企业的资质认证等,避免你离职后的交接麻烦):
注册完成后,需要进行开发者的认证,登录后,点击登录名,进入,大致需要个人身份证等真实信息,还需要企业签字盖章等流程,认证一次300人民币。
认证成功后,创建网站应用,也需要企业签字盖章,还需要备案的域名,作为微信的回调。
创建完成后,获取到appid和appsecret,配置好回调域名。
在开发阶段要让回调指向本地,本地改了才生效,否则要把代码编译存在网上服务器才可以。
修改本地hosts
路径:C:\Windows\System32\drivers\etc\hosts
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.47version>
dependency>
<dependency>
<groupId>org.apache.httpcomponentsgroupId>
<artifactId>httpclientartifactId>
<version>4.5.7version>
dependency>
package com.sevens.base.util;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
public class HttpClientUtil {
public static String get(String url) {
CloseableHttpResponse response = null;
String str = null;
try {
// 获取http客户端
CloseableHttpClient client = HttpClients.createDefault();
// 通过httpget方式来实现我们的get请求
HttpGet httpGet = new HttpGet(url);
// 通过client调用execute方法,得到我们的执行结果就是一个response,所有的数据都封装在response里面了
response = client.execute(httpGet);
// HttpEntity
// 是一个中间的桥梁,在httpClient里面,是连接我们的请求与响应的一个中间桥梁,所有的请求参数都是通过HttpEntity携带过去的
// 所有的响应的数据,也全部都是封装在HttpEntity里面
HttpEntity entity = response.getEntity();
// 通过EntityUtils 来将我们的数据转换成字符串
return EntityUtils.toString(entity, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (response != null) {
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
package com.sevens.crm.constant;
/**
* 微信相关的常量
*/
public class WxConstant {
//申请完账号后的appid
public static final String APPID = "自己的";
//申请完账号后的APPSECRET
public final static String APPSECRET = "自己的";
//用户授权后微信的回调域名
public final static String CALLBACK="http://xxxxx自己的/wechat/callback";
//网站应用scope都填写此值
public static final String SCOPE = "snsapi_login";
//获取授权码对应的url地址
public static final String CODEURL = "https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect";
//获取token的url地址
public static final String TOKENURL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
//获取用户的基本信息
public static final String USERINFOURL = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID";
}
package com.sevens.crm.domain;
import com.sevens.base.domain.BaseDomain;
public class WechatUser extends BaseDomain {
//微信唯一标志
private String openid;
//微信名
private String nickname;
//性别
private Boolean sex;
//地址
private String address;
//头像
private String headimgurl;
//绑定的用户
private User user;
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public Boolean getSex() {
return sex;
}
public void setSex(Boolean sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getHeadimgurl() {
return headimgurl;
}
public void setHeadimgurl(String headimgurl) {
this.headimgurl = headimgurl;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Override
public String toString() {
return "WechatUser{" +
"openid='" + openid + '\'' +
", nickname='" + nickname + '\'' +
", sex=" + sex +
", address='" + address + '\'' +
", headimgurl='" + headimgurl + '\'' +
", user=" + user +
'}';
}
}
WechatUserMapper
package com.sevens.crm.mapper;
import com.sevens.base.mapper.BaseMapper;
import com.sevens.crm.domain.WechatUser;
public interface WechatUserMapper extends BaseMapper<WechatUser, Long> {
/**
* 通过openid去查询WechatUser对象
* @param openid
* @return
*/
WechatUser selectByOpenid(String openid);
}
WechatUserMapper.xml
<mapper namespace="com.sevens.crm.mapper.WechatUserMapper">
<resultMap id="WechatUserResultMap" type="com.sevens.crm.domain.WechatUser">
<id column="id" property="id"/>
<result column="nickname" property="nickname"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/>
<result column="headimgurl" property="headimgurl"/>
<association property="user" javaType="com.sevens.crm.domain.User">
<id column="eid" property="id"/>
<result column="ename" property="name"/>
<result column="epassword" property="password"/>
association>
resultMap>
<select id="selectByOpenid" resultMap="WechatUserResultMap">
SELECT w.*,e.id eid,e.name ename,e.password epassword FROM t_wxuser w
LEFT JOIN t_user e ON w.empid=e.id
WHERE w.openid=#{openid}
select>
<insert id="save" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
INSERT INTO t_wxuser(openid, nickname, sex, address, headimgurl,empid)
VALUES (#{openid},#{nickname},#{sex},#{address},#{headimgurl},#{user.id})
insert>
<update id="update">
UPDATE t_wxuser SET empid=#{user.id} WHERE id=#{id}
update>
mapper>
IWechatUserService和WechatUserServiceImpl省略
package com.sevens.crm.shiro.constant;
public class LoginType {
//通过用户名和密码登录
public static final String AUTH_USER_PASSWORD = "auth_user_password";
//只通过用户名登录
public static final String AUTH_USER = "auth_user";
}
ItsourceHashedCredentialsManager
package com.sevens.crm.shiro.credential;
import com.sevens.crm.shiro.constant.LoginType;
import com.sevens.crm.shiro.token.ItsourceUsernameAndPasswordToken;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
/**
* 自定义密码比较器
*/
public class ItsourceHashedCredentialsManager extends HashedCredentialsMatcher {
/**
* 比较密码
* @param token
* @param info
* @return
*/
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
ItsourceUsernameAndPasswordToken itsourceUsernameAndPasswordToken = (ItsourceUsernameAndPasswordToken) token;
//如果类型是免密的直接通过
if(itsourceUsernameAndPasswordToken.getLoginType().equals(LoginType.AUTH_USER)){
return true;
}
//否则还是走之前的业务流程
return super.doCredentialsMatch(token, info);
}
}
ItsourceUsernameAndPasswordToken
package com.sevens.crm.shiro.token;
import com.sevens.crm.shiro.constant.LoginType;
import org.apache.shiro.authc.UsernamePasswordToken;
public class ItsourceUsernameAndPasswordToken extends UsernamePasswordToken {
private String username;
//登录类型(用户名密码登录,用户名登录)
private String loginType = LoginType.AUTH_USER_PASSWORD;
public ItsourceUsernameAndPasswordToken(){
}
public ItsourceUsernameAndPasswordToken(String username, String password){
super(username, password);
this.username = username;
}
public ItsourceUsernameAndPasswordToken(String username){
super(username,"");
this.username = username;
//设置免密
this.loginType = LoginType.AUTH_USER;
}
@Override
public String getUsername() {
return username;
}
@Override
public void setUsername(String username) {
this.username = username;
}
public String getLoginType() {
return loginType;
}
public void setLoginType(String loginType) {
this.loginType = loginType;
}
}
applicationContext-shiro.xml
<bean id="authRealm" class="com.sevens.crm.shiro.realm.AuthenRealm">
<property name="credentialsMatcher">
<bean class="com.sevens.crm.shiro.credential.ItsourceHashedCredentialsManager">
<property name="hashAlgorithmName" value="MD5"/>
<property name="hashIterations" value="10"/>
bean>
property>
bean>
WechatController
package com.sevens.crm.web.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.annotation.JsonAppend;
import com.sevens.base.util.HttpClientUtil;
import com.sevens.crm.constant.WxConstant;
import com.sevens.crm.domain.Shiro;
import com.sevens.crm.domain.User;
import com.sevens.crm.domain.WechatUser;
import com.sevens.crm.domain.vo.ShiroVO;
import com.sevens.crm.query.ShiroQuery;
import com.sevens.crm.service.IShiroService;
import com.sevens.crm.service.IUserService;
import com.sevens.crm.service.IWechatUserService;
import com.sevens.crm.shiro.token.ItsourceUsernameAndPasswordToken;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Controller
@RequestMapping("/wechat")
@CrossOrigin
public class WechatController {
@Autowired
private IWechatUserService wechatUserService;
@Autowired
private IUserService userService;
@Autowired
private IShiroService shiroService;
@RequestMapping(value = "/toLogin", method = RequestMethod.GET)
@ResponseBody
public String toLogin() {
String codeUrl = WxConstant.CODEURL.replace("APPID", WxConstant.APPID)
.replace("REDIRECT_URI", WxConstant.CALLBACK).replace("SCOPE", WxConstant.SCOPE);
return codeUrl;
}
@RequestMapping("/callback")
public String callback(String code, Model model) throws UnsupportedEncodingException {
//通过授权码获取token
String tokenUrl = WxConstant.TOKENURL.replace("APPID", WxConstant.APPID)
.replace("SECRET", WxConstant.APPSECRET).replace("CODE", code);
//根据授权码获取token值
String tokenJsonStr = HttpClientUtil.get(tokenUrl);
//把json字符串转为JSON对象
JSONObject tokenJsonObject = JSONObject.parseObject(tokenJsonStr);
//获取token值
String access_token = tokenJsonObject.getString("access_token");
//每个微信唯一标志
String openid = tokenJsonObject.getString("openid");
//获取微信用户的基本信息对应的url地址
String userInfoUrl = WxConstant.USERINFOURL.replace("ACCESS_TOKEN", access_token).replace("OPENID", openid);
//获取userInfo的json字符串
String userInfoJsonStr = HttpClientUtil.get(userInfoUrl);
//把json字符串转为json对象
JSONObject userInfoJsonObject = JSONObject.parseObject(userInfoJsonStr);
//根据openid查询WechatUser表中是否存在
WechatUser wechatUser = wechatUserService.selectByOpenid(openid);
if (wechatUser == null) {//代表该微信需要绑定员工
WechatUser user = new WechatUser();
user.setAddress(userInfoJsonObject.getString("province"));
user.setHeadimgurl(userInfoJsonObject.getString("headimgurl"));
user.setNickname(userInfoJsonObject.getString("nickname"));
user.setOpenid(userInfoJsonObject.getString("openid"));
user.setSex(userInfoJsonObject.getInteger("sex") == 1 ? true : false);
//保存WechatUser
System.out.println(user);
wechatUserService.save(user);
//跳到绑定界面
return "redirect:http://localhost:8080/#/callback?openid=" + openid;
} else {
User user = wechatUser.getUser();
if (user == null) {
//跳到绑定界面
return "redirect:http://localhost:8080/#/callback?openid=" + openid;
} else {
//直接认证
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new ItsourceUsernameAndPasswordToken(user.getName());
//shiro认证登录
subject.login(token);
String s = JSON.toJSONString(subject.getPrincipal());
User principal1 = (User) subject.getPrincipal();
User userN = userService.findByName(principal1.getName());
List<ShiroVO> menus = shiroService.getMenusByUsername(principal1.getName());
User principal = (User) subject.getPrincipal();
principal.setMenus(menus);
String u = URLEncoder.encode(JSON.toJSONString(principal), "UTF-8");
String param = "?user=" + u + "&token=" + subject.getSession().getId();
return "redirect:http://localhost:8080/#/callback" + param;
}
}
}
@RequestMapping(value = "/binder", method = RequestMethod.POST)
@ResponseBody
public Map<String, Object> binder(@RequestBody Map<String, String> m) {
Map<String, Object> map = new HashMap<>();
try {
//把对应的员工对象查询出来
User employee = userService.selectByUsernameAndPassword(m.get("username"), m.get("password"));
WechatUser user = wechatUserService.selectByOpenid(m.get("openid"));
//绑定员工
user.setUser(employee);
//修改用户
wechatUserService.update(user);
map.put("success", true);
} catch (Exception e) {
e.printStackTrace();
map.put("success", false);
map.put("msg", "绑定失败!请重新输入用户名和密码");
map.put("openid", m.get("openid"));
}
return map;
}
}
登录对应方法
//微信登录
wechat(){
this.$http.get("/wechat/toLogin").then(res=>{
window.location.href=res.data;
});
},
callback.vue
<template>
</template>
<script>
import {getQueryString} from '../common/js/sss'
export default {
data() {
return {
};
},
methods: {
},
mounted(){
var href = window.location.href;
var queryParam = href.substring( href.indexOf("?"));
var queryObj = getQueryString(queryParam);
if(queryObj.user && !queryObj.openid){//证明登录成功
sessionStorage.setItem('user', queryObj.user);
sessionStorage.setItem("token", queryObj.token);
this.$router.push({ path: '/echarts' });
}else{
this.$router.push({ path: '/binder?openid='+queryObj.openid });
}
}
}
</script>
<style scoped>
</style>
binder.vue
<template>
<el-form :model="ruleForm2" :rules="rules2" ref="ruleForm2" label-position="left" label-width="0px" class="demo-ruleForm login-container">
<h3 class="title">绑定微信</h3>
<el-form-item prop="account">
<el-input type="text" v-model="ruleForm2.account" auto-complete="off" placeholder="账号"></el-input>
</el-form-item>
<el-form-item prop="checkPass">
<el-input type="password" v-model="ruleForm2.checkPass" auto-complete="off" placeholder="密码"></el-input>
</el-form-item>
<el-form-item style="width:100%;">
<el-button type="primary" style="width:100%;" @click.native.prevent="handleSubmit2" :loading="logining">绑定</el-button>
</el-form-item>
</el-form>
</template>
<script>
import {getQueryString} from '../common/js/sss'
//import NProgress from 'nprogress'
export default {
data() {
return {
wxUrl:"",
logining: false,
openid:"",
ruleForm2: {
account: 'superadmin',
checkPass: 'admin'
},
rules2: {
account: [
{ required: true, message: '请输入账号', trigger: 'blur' },
//{ validator: validaePass }
],
checkPass: [
{ required: true, message: '请输入密码', trigger: 'blur' },
//{ validator: validaePass2 }
]
},
checked: true
};
},
methods: {
handleSubmit2(ev) {
var _this = this;
this.$refs.ruleForm2.validate((valid) => {
if (valid) {
//_this.$router.replace('/table');
this.logining = true;
//NProgress.start();
var loginParams = { username: this.ruleForm2.account, password: this.ruleForm2.checkPass,openid:this.openid };
// requestLogin(loginParams).then(data => {
this.$http.post("/wechat/binder",loginParams).then(data=>{
this.logining = false;
//NProgress.done();
let { msg, success, openid } = data.data;
if (!success) {
this.openid = openid;
this.$message({
message: msg,
type: 'error'
});
} else {
this.$router.push({ path: '/login' });
}
});
} else {
return false;
}
});
}
},
mounted(){
var href = window.location.href;
var queryParam = href.substring( href.indexOf("?"));
var queryObj = getQueryString(queryParam);
this.openid = queryObj.openid;
}
}
</script>
<style lang="scss" scoped>
.login-container {
/*box-shadow: 0 0px 8px 0 rgba(0, 0, 0, 0.06), 0 1px 0px 0 rgba(0, 0, 0, 0.02);*/
-webkit-border-radius: 5px;
border-radius: 5px;
-moz-border-radius: 5px;
background-clip: padding-box;
margin: 180px auto;
width: 350px;
padding: 35px 35px 15px 35px;
background: #fff;
border: 1px solid #eaeaea;
box-shadow: 0 0 25px #cac6c6;
.title {
margin: 0px auto 40px auto;
text-align: center;
color: #505458;
}
.remember {
margin: 0px 0px 35px 0px;
}
}
</style>
import {getQueryString} from '../common/js/sss'
sss.js
export const getQueryString = (u) => {
var url = location.search?location.search:u; //获取url中"?"符后的字串
var theRequest = new Object();
if (url.indexOf("?") != -1) {
var str = url.substr(1);
var strs = str.split("&");
for(var i = 0; i < strs.length; i ++) {
theRequest[strs[i].split("=")[0]] = decodeURIComponent(strs[i].split("=")[1]);
}
}
return theRequest
};