微信掃碼授權
阿新 • • 發佈:2018-12-12
系統現要求登入介面可以使用手機微信掃一掃授權登入,網上大多數樣例是在微信開放平臺上完成的,這裡使用微信測試公眾號(相當於服務號,另外企業微信做出來的必須使用企業微信app掃碼才可授權)先記錄一下實現過程
1.註冊一個自己的測試號,在下圖所示位置,點選修改配置回撥域名(如:45477g.natappfree.cc,可使用內網穿透工具)
2.使用qrcode生成一個二維碼,迴圈訪問後臺,看是否進行掃碼授權(可採其他長連線推送方式如WebSocket),授權,即呼叫登入方法,不然一直迴圈,當然二維碼過期就需要重新重新整理頁面了。
//生成二維碼 !function(){ //這是微信掃碼認證後臺入口,將該連結寫在二維碼中 var content ="${qrcodeUrl}"+"${uuid}"; console.dir("掃碼url: "+content); var contextRootPath = "${ctx}"; console.log("專案根路徑: "+"${pageContext.request}"); $('.pc_qr_code').qrcode({ render:"canvas", width:200, height:200, correctLevel:0, text:content, background:"#ffffff", foreground:"black", src:"/cugItsm/images/icon1.png" }); keepPool();//自動迴圈呼叫 }(); //輪詢 function keepPool(){ $.get("${ctx}/auth/pool",{uuid:"${uuid}"},function(object){ obj=$.parseJSON(object); if(obj.successFlag == '1'){ console.log("掃碼成功....."); $("#result").html("<font color='red'>掃碼成功</font>"); console.log("${ctx}/adminDB/indexDB?stuEmpno="+obj.stuEmpno+"&empName="+obj.empName); stuEmpno = obj.stuEmpno; empName = obj.empName; login();//login為正常輸入賬號密碼登入的方法 }else if(obj.successFlag == '0'){ $("#result").html(obj.msg); $("#result").css({ "color":"red" }) } else{ keepPool(); } }); }
手機掃碼二維碼看到的頁面,auth.jsp,可能是測試號授權一次,以後授權頁面不再每次出現,這裡需要手動點一下授權,不然可以在頁面載入完後使用js點選這個授權,對使用者透明(裡面註釋的程式碼實現了這部分)
<!DOCTYPE html> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <c:set var="ctx" value="${pageContext.request.contextPath}"/> <html lang="en"> <head> <title>auth</title> <script type="text/javascript" src="${ctx}/js/jquery/1.8.0/jquery-1.8.0.min.js"></script> </head> <body> <div> <a id="auth" href="${authUrl}">登入授權</a> </div> <%-- <div style="display:none;"> <a id="auth" href="${authUrl}">登入授權</a> </div> --%> </body> <!-- <script > !function(){ //不能用$(“#id”).click(),因為是a中的文字點選事件 $("#auth")[0].click(); }(); </script> --> </html>
3.後臺方法
@Controller @RequestMapping("/auth") public class AuthController { static Logger logger = Logger.getLogger(AuthController.class); private AuthBean authBean=new AuthBean(); @Autowired private AuthServiceI authServiceImpl; @Autowired private UserTableService userTableServiceImpl; @RequestMapping("/index2") public String index2(){ return "auth/index2"; } /** * 微信掃碼登入的頁面 * @param model * @return model裡面加上二維碼裡面的連結qrcodeUrl,uuid頁面生成攜帶的uuid,判斷二維碼過期 */ @RequestMapping("/test") public String test(Model model){ String uuid = UUID.randomUUID().toString(); AuthPool.cacheMap.put(uuid, new AuthBean()); model.addAttribute("uuid",uuid); model.addAttribute("qrcodeUrl", authServiceImpl.getQrcode()); return "auth/test"; } /** * 手機掃描二維碼後請求的地址,即qrcodeUrl定義的地址 * @param model * @return auth頁面,授權頁面 */ @RequestMapping("/scanLogin") public String scanLogin(Model model){ //將微信網頁認證返回給前臺 model.addAttribute("authUrl", authServiceImpl.getAuthUrl()); return "auth/auth"; } /** * 手機掃描二維碼後進行授權,在手機端看到的頁面 * @param request * @return */ @RequestMapping("/welcome") public String welcome(HttpServletRequest request){ String state = request.getParameter("state"); String code = request.getParameter("code"); //System.out.println(state+" "+code); if(state.equals(authServiceImpl.getState())){ String openId=authServiceImpl.getAccesstokenForOpenid(code); UserTable userTable=userTableServiceImpl.queryUserTableByOpenid(openId); if(userTable==null){ request.setAttribute("msg", "請先關注微信公眾號!"); }else{ authBean.setStuEmpno(userTable.getStuEmpno()); authBean.setEmpName(userTable.getEmpName()); request.setAttribute("msg", "登入授權成功!"); } authBean.scanSuccess(); }else{ logger.error("微信授權失敗!"); } return "auth/welcome"; } /** * pc端頁面迴圈的方法,判斷uuid是否過時,判斷手機端是否進行了授權 * @param uuid * @param request * @return */ @RequestMapping("/pool") @ResponseBody public JSONObject pool(String uuid,HttpServletRequest request){ System.out.println("檢查是否授權..."); JSONObject obj = new JSONObject(); AuthBean pool=null; SessionInfo sessionInfo = (SessionInfo) request.getSession().getAttribute(GlobalConstant.SESSION_INFO); //登出後,將授權狀態設為false,這時需要重新使用手機微信授權 if ((sessionInfo == null) || (sessionInfo.getId() == null)) { authBean.afterAuth(); } if( !( AuthPool.cacheMap == null || AuthPool.cacheMap.isEmpty()) ) { pool = AuthPool.cacheMap.get(uuid); } try { if (pool == null) { // 掃碼超時,進執行緒休眠 Thread.sleep(10 * 1000L); obj.put("successFlag","0"); obj.put("msg","該二維碼已經失效,請重新獲取"); }else{ // 使用計時器,固定時間後不再等待掃描結果--防止頁面訪問超時 new Thread(new ScanCounter(uuid, pool)).start(); if(authBean.getScanStatus()){ obj.put("successFlag","1"); obj.put("stuEmpno", authBean.getStuEmpno()); obj.put("empName", authBean.getEmpName()); }else{ obj.put("successFlag","0"); obj.put("msg","請使用手機微信進行授權!"); } } } catch (InterruptedException e) { e.printStackTrace(); } return obj; } } /** *計數器類 */ class ScanCounter implements Runnable { public Long timeout = 10 *60 * 1000L; // 傳入的物件 private String uuid; private AuthBean authBean ; public ScanCounter(String p, AuthBean authBean) { uuid = p; this.authBean = authBean; } @Override public void run() { try { Thread.sleep(timeout); } catch (InterruptedException e) { e.printStackTrace(); } notifyPool(uuid, authBean); } public synchronized void notifyPool(String uuid, AuthBean authBean) { if (authBean != null) authBean.notifyPool(); } public String getUuid() { return uuid; } public void setUuid(String uuid) { this.uuid = uuid; } public AuthBean getAuthBean() { return authBean; } public void setAuthBean(AuthBean authBean) { this.authBean = authBean; } }
uuid的儲存與清除可使用其他快取替代,如redis
public class AuthPool {
static Logger logger = Logger.getLogger(AuthPool.class);
// 快取超時時間 10分鐘
private static Long timeOutSecond = 10 * 60 * 1000L;
// 每半小時清理一次快取
private static Long cleanIntervalSecond = 30 * 60 * 1000L;
//專用於高併發的map類-----Map的併發處理(ConcurrentHashMap)
public static ConcurrentHashMap<String, AuthBean> cacheMap = new ConcurrentHashMap<String, AuthBean>();
static {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(cleanIntervalSecond);
} catch (InterruptedException e) {
e.printStackTrace();
}
clean();
}
}
public void clean() {
try {
if (cacheMap.keySet().size() > 0) {
Iterator<String> iterator = cacheMap.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
AuthBean pool = cacheMap.get(key);
if (System.currentTimeMillis() - pool.getCreateTime() > timeOutSecond ) {
cacheMap.remove(key);
}
}
}
} catch (Exception e) {
logger.error("定時清理uuid異常", e);
}
}
}).start();
}
}
記錄手機是否掃碼的類,裡面使用執行緒同步,保證掃碼的實時性
public class AuthBean implements java.io.Serializable{
private static final long serialVersionUID = 1L;
private boolean isScan=false;
private String stuEmpno;
private String empName;
//建立時間
private Long createTime = System.currentTimeMillis();
public boolean isScan() {
return isScan;
}
public void setScan(boolean isScan) {
this.isScan = isScan;
}
/**
* 獲取掃描的狀態
* @return
*/
public synchronized boolean getScanStatus(){
try
{
if(!isScan()){ //如果還未掃描,則等待
this.wait();
}
if (isScan())
{ //System.err.println("手機掃描完成設定getScanStatus..true...........");
return true;
}
} catch (InterruptedException e)
{
e.printStackTrace();
}
return false;
}
/**
* 掃碼之後設定掃碼狀態
*/
public synchronized void scanSuccess(){
try
{ //System.err.println("手機掃描完成setScan(true)....同時釋放notifyAll");
setScan(true);
this.notifyAll();
} catch (Exception e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 授權初始化,將掃碼狀態設為false,這時候需要重新進行掃碼
*/
public synchronized void afterAuth(){
try{
//System.err.println("授權初始化setScan(false)....同時釋放notifyAll");
setScan(false);
//this.notify();
}catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
public synchronized void notifyPool(){
try
{
this.notifyAll();
} catch (Exception e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public String getStuEmpno() {
return stuEmpno;
}
public void setStuEmpno(String stuEmpno) {
this.stuEmpno = stuEmpno;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public Long getCreateTime()
{
return createTime;
}
}