微信扫码授权
系統現要求登錄界面可以使用手機微信掃一掃授權登錄,網上大多數樣例是在微信開放平臺上完成的,這里使用微信測試公眾號(相當于服務號,另外企業微信做出來的必須使用企業微信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點擊這個授權,對用戶透明(里面注釋的代碼實現了這部分)
?
3.后臺方法
?微信需要先關注測試號,不然授權的時候,也不提示關注微信測試號
@Controller @RequestMapping("/auth") public class AuthController {static Logger logger = Logger.getLogger(AuthController.class);private AuthBean authBean=new AuthBean();@Autowiredprivate AuthServiceI authServiceImpl;@Autowiredprivate 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);/*這里根據openId從用戶數據表獲取用戶信息,需要先將openId綁定在用戶角色上,openId與用戶綁定代碼沒有給*/UserTable userTable=userTableServiceImpl.queryUserTableByOpenid(openId);if(userTable==null){request.setAttribute("msg", "請先綁定用戶!");/*將openId值傳給前端,然后輸入一個用戶ID,然后后臺一個update罷了*/request.setAttribute("openId", openID);}else{authBean.setStuEmpno(userTable.getStuEmpno());authBean.setEmpName(userTable.getEmpName());request.setAttribute("msg", "登錄授權成功!");//找到用戶才表示掃描授權成功,這里為準,資源給的,是手動從數據庫手動綁定openId的,沒考慮到authBean.scanSuccess();}}else{logger.error("微信授權失敗!");}return "auth/welcome";}/*** pc端頁面循環的方法,判斷uuid是否過時,判斷手機端是否進行了授權* @param uuid* @param request* @return*/@RequestMapping("/pool")@ResponseBodypublic 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;}@Overridepublic 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() {@Overridepublic 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 exceptione.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; }}?
這里給一個簡單的java發送https的工具類,網上查也可以找到
?
#發送https的工具類 public class WebRequestUtil {public static String getRequest4https(String serverAddress){String result = "";try {HttpsURLConnection connection = (HttpsURLConnection)new URL(serverAddress).openConnection();connection.setSSLSocketFactory(new DummySSLSocketFactory());// 設置通用的請求屬性connection.setRequestProperty("accept", "*/*");connection.setRequestProperty("connection", "Keep-Alive");connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");connection.setRequestProperty("Charset", "UTF-8");connection.setRequestProperty("Content-Type", "text/plain;charset=UTF-8");BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));String line = "";while (null != (line = br.readLine())) {result += line;}logger.info("WebRequestUtil getRequest4https result=" + result);} catch (Exception e) {logger.error("WebRequestUtil getRequest4https Exception=" + e.toString());}return result;} }#工具類發送https需要的兩個類 public class DummySSLSocketFactory extends SSLSocketFactory {private SSLSocketFactory factory;public DummySSLSocketFactory() {try {SSLContext sslcontext = SSLContext.getInstance("TLS");sslcontext.init(null,new TrustManager[] { new DummyTrustManager()},null);factory = (SSLSocketFactory)sslcontext.getSocketFactory();} catch(Exception ex) {// ignore}}public static SocketFactory getDefault() {return new DummySSLSocketFactory();}public Socket createSocket() throws IOException {return factory.createSocket();}public Socket createSocket(Socket socket, String s, int i, boolean flag)throws IOException {return factory.createSocket(socket, s, i, flag);}public Socket createSocket(InetAddress inaddr, int i,InetAddress inaddr1, int j) throws IOException {return factory.createSocket(inaddr, i, inaddr1, j);}public Socket createSocket(InetAddress inaddr, int i)throws IOException {return factory.createSocket(inaddr, i);}public Socket createSocket(String s, int i, InetAddress inaddr, int j)throws IOException {return factory.createSocket(s, i, inaddr, j);}public Socket createSocket(String s, int i) throws IOException {return factory.createSocket(s, i);}public String[] getDefaultCipherSuites() {return factory.getDefaultCipherSuites();}public String[] getSupportedCipherSuites() {return factory.getSupportedCipherSuites();}}public class DummyTrustManager implements X509TrustManager {public void checkClientTrusted(X509Certificate[] cert, String authType) {// everything is trusted}public void checkServerTrusted(X509Certificate[] cert, String authType) {// everything is trusted}public X509Certificate[] getAcceptedIssuers() {return new X509Certificate[0];}}最后給一下授權代碼的基本源代碼,需要的朋友可以下載:https://download.csdn.net/download/cacalili/10691193
總結
- 上一篇: Supervised Descent M
- 下一篇: hive 如何将数组转成字符串_教你如何