天天看点

微信扫码授权

系统现要求登录界面可以使用手机微信扫一扫授权登录,网上大多数样例是在微信开放平台上完成的,这里使用微信测试公众号(相当于服务号,另外企业微信做出来的必须使用企业微信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 >
<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}" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow" >登录授权</a>
</div>
<%-- <div style="display:none;">
<a id="auth" href="${authUrl}" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow" >登录授权</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);
            /*这里根据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")
	    @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;  
	    }

	

}
           

这里给一个简单的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

继续阅读