理論:
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>
