1. 程式人生 > >微信掃碼授權

微信掃碼授權

系統現要求登入介面可以使用手機微信掃一掃授權登入,網上大多數樣例是在微信開放平臺上完成的,這裡使用微信測試公眾號(相當於服務號,另外企業微信做出來的必須使用企業微信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;  
	    }

	

}