SSM实现微信登录

    大致流程,点击登录,获取二维码,扫描之后,将微信用户的信息传到后台,判断t_wxuser表中是否已经存在该用户,没有就新保存,有就继续判断是否绑定了t_user账户,没有就绑定账户,有就直接登录。

1、准备工作

1、官网

微信开放平台:https://open.weixin.qq.com/
微信开放文档:https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html

1.2、注册认证

    要想接入微信的登录功能,首先需要在微信开发平台进行用户的注册,同时需要认证为开发者,再创建网站应用,等待微信审批,审批过后,就可以使用相关功能。

1.2.1、注册

在开放平台注册:
    使用邮箱进行注册(最好使用企业邮箱,避免使用私人邮箱,后续需要企业的资质认证等,避免你离职后的交接麻烦):

1.2.2、开发者认证

    注册完成后,需要进行开发者的认证,登录后,点击登录名,进入,大致需要个人身份证等真实信息,还需要企业签字盖章等流程,认证一次300人民币。

1.2.3、创建网站应用

    认证成功后,创建网站应用,也需要企业签字盖章,还需要备案的域名,作为微信的回调。
    创建完成后,获取到appidappsecret,配置好回调域名。

1.2.4、回调域名

    在开发阶段要让回调指向本地,本地改了才生效,否则要把代码编译存在网上服务器才可以。
修改本地hosts
路径:C:\Windows\System32\drivers\etc\hosts
SSM实现微信登录_第1张图片

2、后台代码

2.1、导包

SSM实现微信登录_第2张图片

  
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>1.2.47version>
        dependency>

SSM实现微信登录_第3张图片


        <dependency>
            <groupId>org.apache.httpcomponentsgroupId>
            <artifactId>httpclientartifactId>
            <version>4.5.7version>
        dependency>

2.2、base-util

HttpClientUtil
SSM实现微信登录_第4张图片

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;

    }
}

2.3、crm-common
  • WxConstant:微信所用常量
  • WechatUser:储存微信登录的用户,需要crud

WxConstant
SSM实现微信登录_第5张图片

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";
}

WechatUser
SSM实现微信登录_第6张图片

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 +
                '}';
    }
}

2.4、WechatUserMapper

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>
2.5、IWechatUserService

IWechatUserService和WechatUserServiceImpl省略

2.6、IWechatUserService
2.7、crm-shiro

SSM实现微信登录_第7张图片
LoginType

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>
2.7、crm-web

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;
    }
}

3、前台代码

登录对应方法

  //微信登录
            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
};

你可能感兴趣的:(Java,shiro,spring,mybatis)