系统现要求登录界面可以使用手机微信扫一扫授权登录,网上大多数样例是在微信开放平台上完成的,这里使用微信测试公众号(相当于服务号,另外企业微信做出来的必须使用企业微信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