理論:
1.獲得微信官方的網址
2.使用OAuth2.0
3.登陸需要三步
獲得驗證
返回一個網站
獲得授權
對象
獲得用戶
對象
然后登陸的時候會和我們數據庫中的袁勇綁定,如果沒有就創建,有就關聯
操作:
1.更改項目配置
在最后一行添加
127.0.0.1 bugtracker.itsource.cn
這是你去官網注冊獲得的域名這樣才能把二維碼返回過來,使用回調函數
2.引入依賴
<!--httpclient的依賴:--> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.7</version> </dependency> <!--處理json的包 https://mvnrepository.com/artifact/com.alibaba/fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency>
3.登陸步驟
寫一個封裝好的
WxConstants把那些固定的常量封裝成一個類,方便我們修改等等
package cn.jiedada.crm.web.wechart; /*這是微信提供的必要字段 只有通過這些方式才能獲得我們所有的 * */ public class WxConstants { public final static String APPID = "wxd853562a0548a7d0"; //用戶授權后微信的回調域名,當我們獲得該值的時候會調用該方法 public final static String CALLBACK="http://bugtracker.itsource.cn/callback"; public final static String SCOPE = "snsapi_login"; public final static String APPSECRET = "4a5d5615f93f24bdba2ba8534642dbb6"; //微信上獲取code的地址(這里的 // appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect) //需要我們前台在調用的時候經行拼接REDIRECT_URI= public final static String CODEURL = "https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect"; //微信上獲取at的地址 public final static String ACCESSTOKEURL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code"; //微信上獲取用戶信息的地址 public final static String USERINFOURL = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID"; }
因為這些都是我們必須要的字段而且不能夠修改所以這樣操作
因為我們獲取code,at,用戶信息是在微信官方獲得的所以我們需要發送請求,所以我們封裝一個類來發送請求
HttpClientUtils

package cn.jiedada; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.util.EntityUtils; import java.io.IOException; import java.util.Iterator; import java.util.List; import java.util.Map; /*因為我們使用微信登陸的時候會發送請求所以要使用 * */ public class HttpClientUtils { /** * http請求工具類,post請求 * * @param url url * @param params json字符串的參數 * @return * @throws Exception */ public static String httpPost(String url, String params) throws Exception { // 創建httpClient對象 DefaultHttpClient defaultHttpClient = null; try { defaultHttpClient = new DefaultHttpClient(); HttpPost httpPost = new HttpPost(url); httpPost.setHeader("Content-Type", "application/json;charset=ut-8"); if (params != null) { System.out.println("請求參數:" + params); // 設置請求參數 HttpEntity httpEntity = new StringEntity(params, "utf-8"); httpPost.setEntity(httpEntity); } // 執行post請求,並得到相應結果 HttpResponse httpResponse = defaultHttpClient.execute(httpPost); if (httpResponse.getStatusLine().getStatusCode() != 200) { String errorLog = "請求失敗,errorCode:" + httpResponse.getStatusLine().getStatusCode(); throw new Exception(url + errorLog); } // 解析結果 HttpEntity responseEntity = httpResponse.getEntity(); String responseStr = EntityUtils.toString(responseEntity, "utf-8"); System.out.println("請求結果:" + responseStr); return responseStr; } catch (ClientProtocolException e) { e.printStackTrace(); throw e; } catch (IOException e) { e.printStackTrace(); throw e; } finally { if (defaultHttpClient != null) defaultHttpClient.getConnectionManager().shutdown(); } } /** * http請求工具類,get請求 * * @param url 請求地址:可以已經帶參數(?),也可以沒有帶參數,在params中傳過來 * @param params 參數:值支持字符串和list * @return * @throws Exception */ public static String httpGet(String url, Map<String, Object> params) throws Exception { DefaultHttpClient defaultHttpClient = null; try { defaultHttpClient = new DefaultHttpClient(); if (params != null) { // 參數的拼接 StringBuilder stringBuilder = new StringBuilder(); Iterator<String> iterator = params.keySet().iterator(); String key; while (iterator.hasNext()) { key = iterator.next(); Object val = params.get(key); if (val instanceof List) { // 如果是list,則遍歷拼接 List v = (List) val; for (Object o : v) { stringBuilder.append(key).append("=").append(o.toString()).append("&"); } } else { // 字符串:直接拼接 stringBuilder.append(key).append("=").append(val.toString()).append("&"); } } // 刪除最后一個& stringBuilder.deleteCharAt(stringBuilder.length() - 1); if (url.indexOf("?") > 0) { // url地址本身包含? url = url + "&" + stringBuilder.toString(); } else { url = url + "?" + stringBuilder.toString(); } } System.out.println("請求地址:" + url); HttpGet httpGet = new HttpGet(url); httpGet.setHeader("Content-Type", "application/json;charset=ut-8"); // 執行 HttpResponse httpResponse = defaultHttpClient.execute(httpGet); if (httpResponse.getStatusLine().getStatusCode() != 200) { String errorLog = "請求失敗,errorCode:" + httpResponse.getStatusLine().getStatusCode(); throw new Exception(url + errorLog); } // 解析結果 HttpEntity responseEntity = httpResponse.getEntity(); String responseStr = EntityUtils.toString(responseEntity, "utf-8"); System.out.println("請求結果:" + responseStr); return responseStr; } catch (ClientProtocolException e) { e.printStackTrace(); throw e; } catch (IOException e) { e.printStackTrace(); throw e; } finally { if (defaultHttpClient != null) defaultHttpClient.getConnectionManager().shutdown(); } } }
下面我們來獲取值
獲得登陸是的二維碼在登陸頁面中這是后台中登陸的代碼

@RequestMapping(value = "/login",method = RequestMethod.GET) @ResponseBody public AjaxResoult wecharLogin(Model model){ String wxLoginUrl = WxConstants.CODEURL.replaceAll("APPID", WxConstants.APPID) .replaceAll("REDIRECT_URI", WxConstants.CALLBACK) .replaceAll("SCOPE", WxConstants.SCOPE); AjaxResoult ajaxResoult = new AjaxResoult(); ajaxResoult.setWxLoginUrl(wxLoginUrl); return ajaxResoult; }
前台登陸時的代碼
點擊登陸
<el-form-item> <el-button type="primary" @click="getWxLoginUrl">微信登陸</el-button> </el-form-item>
getWxLoginUrl(event){ this.$http.get('/login').then((res)=>{ console.debug(res) ; let { msg, success,wxLoginUrl, resultObj } = res.data; if (!success) { this.$message({ message: msg, type: 'error' }); } else { // this.$router.push({ path: '/wxLoginUrl' }); window.location.href=wxLoginUrl } }) },
點擊的時候發送請求
會出現圖片
當使用手機掃碼的時候會調到一個叫callback的方法中去
這是你去官網注冊獲得的域名這樣才能把二維碼返回過來,使用回調函數
callback方法(這里面我們需要處理的為獲得at,和用戶信息,判斷用戶是否掃過碼,如果掃過就判斷是否關聯上了一個,如果沒有就添加一個微信表的數據,並且調轉到綁定頁面)

/* * 回調處理 * 需要獲得at * 和userinfo * */ @RequestMapping(value = "/callback",method = RequestMethod.GET) public String wecharLogin(String code,String state) throws Exception { //獲得at的url String atUrl = WxConstants.ACCESSTOKEURL.replaceAll("APPID", WxConstants.APPID) .replaceAll("SECRET", WxConstants.APPSECRET) .replaceAll("CODE", code); //獲得at的url然后發送請求 String atRespone = HttpClientUtils.httpGet(atUrl, null); System.out.println("atRespone" + atRespone); //因為獲得user info的條件是 // USERINFOURL = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID"; //需要access_token和openid JSONObject atParse = (JSONObject) JSON.parse(atRespone); System.out.println("atParse" + atParse); String access_token = atParse.getString("access_token"); String openid = atParse.getString("openid"); //獲得用戶信息 String userUrl = WxConstants.USERINFOURL.replaceAll("ACCESS_TOKEN", access_token) .replaceAll("OPENID", openid); //發送請求獲得用戶信息 String userInfo = HttpClientUtils.httpGet(userUrl, null); System.out.println("userInfo" + userInfo); //轉化為JSONObject對象 JSONObject userInfoObj = (JSONObject) JSON.parse(userInfo); //取出其中的值 String useropenid = userInfoObj.getString("openid"); String nickname = userInfoObj.getString("nickname"); String unionid = userInfoObj.getString("unionid"); WeChart weChart = weChartService.findByopenid(useropenid); if (weChart == null) { //如果不存在就設置值進去 WeChart weChart1 = new WeChart(); weChart1.setOpenid(useropenid); weChart1.setNickname(nickname); weChart1.setUnionid(unionid); weChartService.save(weChart1); //跳轉注冊頁面 /*AjaxResoult ajaxResoult = new AjaxResoult(); ajaxResoult.setBindUrl("redirect:/bind.jsp?openid="+useropenid);*/ return "redirect:http://localhost:8080/#/Register?openid="+useropenid; } else { //存在直接登陸並且把數據綁定過去 WeChart weChart2 = weChartService.findByopenid(useropenid); if (weChart2.getEmployee_id() != null) { //通過員工id找到員工的username設置login Employee employee = employeeService.findOne(weChart2.getEmployee_id()); //登陸當前用戶 Subject subject = SecurityUtils.getSubject(); //使用自己的token Map<String,Object> result = new HashMap<>(); //除了返回登錄成功與否,還要把登錄的用戶返回前端 AjaxResoult ajaxResoult = new AjaxResoult(); //登錄用戶 MyUsernamePasswordToken token = new MyUsernamePasswordToken(employee.getUsername()); subject.login(token); Serializable tokenid = subject.getSession().getId(); //跳轉登陸頁面 //還是返回ajaxResoult上面的自己在前台拼裝就可以了 return "redirect:http://localhost:8080/#/echarts?tokenid="+tokenid; } else { //跳轉注冊頁面 /*AjaxResoult ajaxResoult = new AjaxResoult(); ajaxResoult.setBindUrl("redirect:/bind.jsp?openid="+useropenid);*/ return "redirect:http://localhost:8080/#/Register?openid="+useropenid; } } }
當第一次沒登陸的時候我們需要跳轉到登陸頁面
因為我們做了登陸權限,只能讓登錄頁面訪問,所以我們在main.js中添加了一個鈎子函數
router.beforeEach((to, from, next) => { if (to.path == '/login') { sessionStorage.removeItem('token'); } //如果session沒有token信息,跳轉到登陸頁面 let token = sessionStorage.getItem('token'); //獲得請求欄上的地址 let url = window.location.href; //獲得后台傳過來的openid的值(獲取http://localhost:8080/#/echarts=?tokenid如這里的tokenid) let openid = url.split("=")[1]; //xxx //判斷是否放行 if (!token && to.path != '/login') { //判斷是否是后台穿過來的,放行注冊頁面 if(openid && to.path == '/Register'){ next(); } //放行我們已經注冊了的用戶登陸問題 else if(openid && to.path == '/echarts'){ sessionStorage.setItem("token",openid); next(); } else { next({ path: '/login' }) } } else { next() } });
這是這個頁面的值這個頁面

<template> <div> <!--:model="tenant" 數據雙向綁定--> <!--ref="tenantForm" id="tenantForm",給form去一個名字--> <!--:rules="formRules" 校驗規則--> <el-form :model="employee" ref="tenantForm" :rules="formRules" label-position="left" label-width="100px" class="demo-ruleForm login-container"> <h3 class="title">用戶關聯</h3> <el-form-item prop="companyName"label="用戶名稱"> <el-input type="text" v-model="employee.username" auto-complete="off" placeholder="請輸入公司名稱!"></el-input> </el-form-item> <el-form-item prop="companyNum" label="用戶密碼"> <el-input type="text" v-model="employee.password" 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="settledIn" >入駐</el-button> </el-form-item> </el-form> </div> </template> <script> export default { data() { //elementui提供自定義驗證 value 當前這個框 var validatePass2 = (rule, value, callback) => { console.log(value); //確認密碼 底層提供給我們 if (value === '') { callback(new Error('請再次輸入密碼')); } else if (value !== this.employee.password) { callback(new Error('兩次輸入密碼不一致!')) } else { callback();//表示通過 } } return { keyword:'', mapDialogVisibale:false, //employee:tenant 為了做數據表單校驗不要嵌套對象 employee: { username:'', password:'', openid:'', }, formRules: { username: [ { required: true, message: '請輸入用戶名稱!', trigger: 'blur' } ], password: [ { required: true, message: '請輸入用戶密碼!', trigger: 'blur' } ], comfirmPassword: [ {required: true,validator: validatePass2, trigger: 'blur' } //自定義校驗規則 ] } }; }, methods: { selectAdrressConfirm(){ //把地圖里面的值放入到表單地址里面,通過這種方式獲得值 this.employee.address = document.getElementById("searchInput").value; //把對話框關閉 this.mapDialogVisibale = false; }, settledIn(){ //驗證表單數據 this.$refs.tenantForm.validate((valid) => { //校驗表單成功后才做一下操作 if (valid) { this.$confirm('確認關聯嗎?', '提示', {}).then(() => { //拷貝后面對象的值到新對象,防止后面代碼改動引起模型變化 let url = window.location.href; let openid = url.split("=")[1]; //xxx let para = Object.assign({}, this.employee); //employee //tenant? para.openid=openid; //判斷是否有id有就是修改,否則就是添加 this.$http.post("/binder",para).then((res) =>{ this.logining = false; //NProgress.done(); let { msg, success, resultObj } = res.data; if (!success) { this.$message({ message: msg, type: 'error' }); } else { sessionStorage.setItem('user', JSON.stringify(resultObj.user.username)); sessionStorage.setItem('token',resultObj.token); this.$router.push({ path: '/echarts' }); } }); }); } }) } }, } </script> <style lang="scss" scoped> .login-container { -webkit-border-radius: 5px; border-radius: 5px; -moz-border-radius: 5px; background-clip: padding-box; margin: 180px auto; width: 500px; 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; } } .bmap{ width: 100%; height: 600px; } .searchinput{ width: 300px; box-sizing: border-box; padding: 9px; border: 1px solid #dddee1; line-height: 20px; font-size: 16px; height: 38px; color: #333; position: relative; border-radius: 4px; } </style>
binder方法

/*綁定微信用戶和我們的員工用戶 * */ @RequestMapping(value = "/binder",method = RequestMethod.POST) @ResponseBody public AjaxResoult binder(@RequestBody Map<String,String> map){ String username = map.get("username"); System.out.println("username"+username); String password = map.get("password"); System.out.println("password"+password); String openid = map.get("openid"); System.out.println("openid"+openid); //在數據庫中查找是否有該員工 Employee employee = employeeService.findEmployeeByUsername(username); if(employee==null){ return new AjaxResoult().setMsg("用戶或者密碼錯誤").setSuccess(false); }else { //判斷密碼是否正確 String encrypt = MD5Util.encrypt(password); if(encrypt.equals(employee.getPassword())){ WeChart weChart = weChartService.findByopenid(openid); //添加用戶 Long employee_id = employee.getId(); weChart.setEmployee_id(employee_id); weChartService.update(weChart); AjaxResoult ajaxResoult = new AjaxResoult(); //登陸當前用戶 Subject subject = SecurityUtils.getSubject(); //使用自己的token MyUsernamePasswordToken token = new MyUsernamePasswordToken(username); //登錄用戶 subject.login(token); Map<String,Object> result = new HashMap<>(); //除了返回登錄成功與否,還要把登錄的用戶返回前端 result.put("user",employee); result.put("token",subject.getSession().getId()); ajaxResoult.setResultObj(result); return ajaxResoult; }else { return new AjaxResoult().setMsg("用戶或者密碼錯誤").setSuccess(false); } } }
這里就完成了
但是因為我們需要使用免密登陸
需要覆寫身份認證過濾器
FormAuthenticationFilter

package cn.jiedada.crm.web.shiro; import cn.jiedada.crm.web.wechart.LoginType; import cn.jiedada.crm.web.wechart.MyUsernamePasswordToken; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.web.filter.authc.FormAuthenticationFilter; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; /** * 自定義身份認證過濾器 */ public class MyAuthenticationFilter extends FormAuthenticationFilter { @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { //如果是OPTIONS請求,直接放行 HttpServletRequest httpServletRequest = (HttpServletRequest) request; String method = httpServletRequest.getMethod(); //判斷是否是OPTIONS請求 if("OPTIONS".equalsIgnoreCase(method)){ return true; } return super.isAccessAllowed(request, response, mappedValue); } //薪增方法 @Override protected AuthenticationToken createToken(String username, String password, ServletRequest request, ServletResponse response) { boolean rememberMe = isRememberMe(request); String host = getHost(request); String loginType = LoginType.PASSWORD;//需要密碼 if(request.getParameter("loginType")!=null && !"".equals(request.getParameter("loginType").trim())){ loginType = request.getParameter("loginType"); } return new MyUsernamePasswordToken(username, password,loginType,rememberMe,host); } }
所以需要覆寫他的加密驗證方法
HashedCredentialsMatcher

package cn.jiedada.crm.web.wechart; import com.sun.org.apache.xml.internal.resolver.readers.DOMCatalogReader; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; public class MyHashedCredentialsMatcher extends HashedCredentialsMatcher { //doCredentialsMatch @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { //使用我們自定義的MyUsernamePasswordToken MyUsernamePasswordToken mupt = (MyUsernamePasswordToken) token; //是否是免密登陸,最后修改配置 if (mupt.getLoginType().equals(LoginType.NOPASSWD)) { //免密登錄 return true; } return super.doCredentialsMatch(token, info); } }
而其中需要使用覆寫
UsernamePasswordToken

package cn.jiedada.crm.web.wechart; import com.sun.org.apache.xml.internal.resolver.readers.DOMCatalogReader; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; public class MyHashedCredentialsMatcher extends HashedCredentialsMatcher { //doCredentialsMatch @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { //使用我們自定義的MyUsernamePasswordToken MyUsernamePasswordToken mupt = (MyUsernamePasswordToken) token; //是否是免密登陸,最后修改配置 if (mupt.getLoginType().equals(LoginType.NOPASSWD)) { //免密登錄 return true; } return super.doCredentialsMatch(token, info); } }
而UsernamePasswordToken需要一個常量類

package cn.jiedada.crm.web.wechart; public class LoginType { public static final String NOPASSWD = "NoPassword"; public static final String PASSWORD = "Password"; }
並且配置到shiro中
在application-shiro.xml中配置這里是所有的配置

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd"> <!--session管理器通過繼承DefaultWebSecurityManager來自定義我們的session--> <bean id="crmSessionManager" class="cn.jiedada.crm.web.shiro.CrmSessionManager"></bean> <!--shiro的核心對象--> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!--配置realm--> <property name="sessionManager" ref="crmSessionManager"/> <property name="realm" ref="myRealm"/> </bean> <!--Realms--> <bean id="myRealm" class="cn.jiedada.crm.web.shiro.MyRealm"> <property name="credentialsMatcher"> <bean class="cn.jiedada.crm.web.wechart.MyHashedCredentialsMatcher"> <property name="hashAlgorithmName" value="MD5"/> <property name="hashIterations" value="10"/> </bean> </property> </bean> <!--自定義過濾器--> <bean id="myAuthenticationFilter" class="cn.jiedada.crm.web.shiro.MyAuthenticationFilter"></bean> <bean id="aisellPermissionsAuthorizationFilter" class="cn.jiedada.crm.web.shiro.AisellPermissionsAuthorizationFilter"></bean> <!--shiro的過濾器配置--> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login"/> <property name="successUrl" value="/s/index"/> <property name="unauthorizedUrl" value="/s/unauthorized"/> <!--通過key在下面找到我們需要的東西,需要使用value-ref關聯--> <property name="filters"> <map> <entry key="myFilter" value-ref="myAuthenticationFilter"></entry> <entry key="aisellPers" value-ref="aisellPermissionsAuthorizationFilter"></entry> </map> </property> <!--在這下面使用我們的myFilter--> <!--<property name="filterChainDefinitions"> <value> /* = anon /js/** = anon /** = myFilter </value> </property>--> </bean> <bean id="filterChainDefinitionMap" factory-bean="shiroFilterMapFactory" factory-method="createMap" /> <!--配置返回shiro權限攔截的bean--> <bean id="shiroFilterMapFactory" class="cn.jiedada.crm.web.shiro.ShiroFilterMapFactory"/> </beans>