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