Springboot+微信小程序(1)——微信授权登录

在实际小程序开发中,大部分的前后台交互业务操作与前后端分离的webapp没什么区别,都是调用后台接口,使用JSON数据传递,只有在一些特殊的环节才会感觉到微信的存在,比如微信授权登录、微信支付、微信模板消息推送等场景;

这些场景如果理解了原理,其实也都不复杂,本节博文主要讲解,如何使用springboot后台处理微信授权登录:

在处理微信小程序授权登录之前,最后可以先了解一下Oauth2.0授权协议,如果确实不清楚,那么也没事,直接看微信小程序开发官方文档相关章节:小程序登录时序图

api-login.2fcc9f35.jpg

说明:

  1. 调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器;

  2. 调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 和 会话密钥 session_key;

    之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份;

注意:

  1. 会话密钥 session_key 是对用户数据进行 加密签名 的密钥。为了应用自身的数据安全,开发者服务器不应该把会话密钥下发到小程序,也不应该对外提供这个密钥;

  2. 临时登录凭证 code 只能使用一次;

1561208469103749.png


上面把流程理清了,那么下面就开始用代码实现吧:springboot部分:为了减少博文的篇幅,我就不将所有的代码都贴在这里了,只挑核心的贴:

 需要用到的核心技术,HttpClient和jwtToken,需要在原有依赖的基础上增加这两个maven依赖:

		<!-- httpclient -->
		<dependency>
		    <groupId>org.apache.httpcomponents</groupId>
		    <artifactId>httpclient</artifactId>
		</dependency>		
		<!-- jwt-token -->
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt</artifactId>
			<version>0.7.0</version>
		</dependency>

为HttpClient和jwtToken封装工具类:

HttpClientUtil.java

package com.wx.video.utils;

import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

public class HttpClientUtil {
	public static String doGet(String url, Map<String, String> param) {

        // 创建Httpclient对象
        CloseableHttpClient httpclient = HttpClients.createDefault();

        String resultString = "";
        CloseableHttpResponse response = null;
        try {
            // 创建uri
            URIBuilder builder = new URIBuilder(url);
            if (param != null) {
                for (String key : param.keySet()) {
                    builder.addParameter(key, param.get(key));
                }
            }
            URI uri = builder.build();

            // 创建http GET请求
            HttpGet httpGet = new HttpGet(uri);

            // 执行请求
            response = httpclient.execute(httpGet);
            // 判断返回状态是否为200
            if (response.getStatusLine().getStatusCode() == 200) {
                resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (response != null) {
                    response.close();
                }
                httpclient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return resultString;
    }

    public static String doGet(String url) {
        return doGet(url, null);
    }

    public static String doPost(String url, Map<String, String> param) {
        // 创建Httpclient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse response = null;
        String resultString = "";
        try {
            // 创建Http Post请求
            HttpPost httpPost = new HttpPost(url);
            // 创建参数列表
            if (param != null) {
                List<BasicNameValuePair> paramList = new ArrayList<>();
                for (String key : param.keySet()) {
                    paramList.add(new BasicNameValuePair(key, param.get(key)));
                }
                // 模拟表单
                UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);
                httpPost.setEntity(entity);
            }
            // 执行http请求
            response = httpClient.execute(httpPost);
            resultString = EntityUtils.toString(response.getEntity(), "utf-8");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return resultString;
    }

    public static String doPost(String url) {
        return doPost(url, null);
    }

    public static String doPostJson(String url, String json) {
        // 创建Httpclient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse response = null;
        String resultString = "";
        try {
            // 创建Http Post请求
            HttpPost httpPost = new HttpPost(url);
            // 创建请求内容
            StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
            httpPost.setEntity(entity);
            // 执行http请求
            response = httpClient.execute(httpPost);
            resultString = EntityUtils.toString(response.getEntity(), "utf-8");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return resultString;
    }
}

UserClaims.java

package com.wx.video.utils;

import java.util.Date;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.RequiredTypeException;
import io.jsonwebtoken.impl.JwtMap;

public class UserClaims extends JwtMap implements Claims {
    private String grantType = "password";

    private Integer uid;
    
    private String openid;
    
	public Integer getUid() {
		return uid;
	}

	public void setUid(Integer uid) {
		this.uid = uid;
		setValue("uid", uid);
	}
	
	public String getOpenid() {
		return openid;
	}

	public void setOpenid(String openid) {
		this.openid = openid;
		setValue("openid", openid);
	}

    public String getGrantType() {
        return grantType;
    }

    public void setGrantType(String grantType) {
        this.grantType = grantType;
        setValue("grantType", this.grantType);
    }

    @Override
    public String getIssuer() {
        return getString(ISSUER);
    }

    @Override
    public Claims setIssuer(String iss) {
        setValue(ISSUER, iss);
        return this;
    }

    @Override
    public String getSubject() {
        return getString(SUBJECT);
    }

    @Override
    public Claims setSubject(String sub) {
        setValue(SUBJECT, sub);
        return this;
    }

    @Override
    public String getAudience() {
        return getString(AUDIENCE);
    }

    @Override
    public Claims setAudience(String aud) {
        setValue(AUDIENCE, aud);
        return this;
    }

    @Override
    public Date getExpiration() {
        return get(Claims.EXPIRATION, Date.class);
    }

    @Override
    public Claims setExpiration(Date exp) {
        setDate(Claims.EXPIRATION, exp);
        return this;
    }

    @Override
    public Date getNotBefore() {
        return get(Claims.NOT_BEFORE, Date.class);
    }

    @Override
    public Claims setNotBefore(Date nbf) {
        setDate(Claims.NOT_BEFORE, nbf);
        return this;
    }

    @Override
    public Date getIssuedAt() {
        return get(Claims.ISSUED_AT, Date.class);
    }

    @Override
    public Claims setIssuedAt(Date iat) {
        setDate(Claims.ISSUED_AT, iat);
        return this;
    }

    @Override
    public String getId() {
        return getString(ID);
    }

    @Override
    public Claims setId(String jti) {
        setValue(Claims.ID, jti);
        return this;
    }

    @Override
    public <T> T get(String claimName, Class<T> requiredType) {
        Object value = get(claimName);
        if (value == null) {
            return null;
        }

        if (Claims.EXPIRATION.equals(claimName)
                || Claims.ISSUED_AT.equals(claimName)
                || Claims.NOT_BEFORE.equals(claimName)) {
            value = getDate(claimName);
        }

        if (requiredType == Date.class && value instanceof Long) {
            value = new Date((Long) value);
        }

        if (!requiredType.isInstance(value)) {
            throw new RequiredTypeException("Expected value to be of type: "
                    + requiredType + ", but was " + value.getClass());
        }

        return requiredType.cast(value);
    }
}

JwtTokenProvider.java

package com.wx.video.utils;

import javax.crypto.spec.SecretKeySpec;
import com.alibaba.fastjson.JSONObject;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.CompressionCodecs;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

public class JwtTokenProvider {
	private SecretKeySpec key;
	
    public JwtTokenProvider(String key) {
        SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), SignatureAlgorithm.HS512.getJcaName());
        this.key = secretKeySpec;
    }
    
    public String createToken(Claims claims) {
        String compactJws = Jwts.builder().setPayload(JSONObject.toJSONString(claims))
                .compressWith(CompressionCodecs.DEFLATE).signWith(SignatureAlgorithm.HS512, key).compact();
        return compactJws;
    }

    public Claims parseToken(String token) {
        try {
            return Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

 JwtUtils.java

package com.wx.video.utils;

import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.Claims;

@Component
public class JwtUtils {
	@Value("${auth.jwtKey}")
	private String jwtKey;
	
	/**
	 ** 根据UserClaims创建JwtToken 
	 * @param userClaims
	 * @return
	 */
	public String createToken(UserClaims userClaims) {
		JwtTokenProvider tokenProvider = new JwtTokenProvider(jwtKey);
		return tokenProvider.createToken(userClaims);
	}
	
	/**
	 ** 根据token解析出Claim 
	 * @param token
	 * @return
	 */
	public Claims parseToken(String token) {
		JwtTokenProvider tokenProvider = new JwtTokenProvider(jwtKey);
		return tokenProvider.parseToken(token);
	}
	
	/**
	 **  根据request请求直接解析出Claim 
	 * @param request
	 * @return
	 */
	public Claims getUserClaim(HttpServletRequest request) {
		String token = request.getHeader("Authorization");
      	JwtTokenProvider tokenProvider = new JwtTokenProvider(jwtKey);
		return tokenProvider.parseToken(token);
	}
}

有了上面的工具类,正在的授权登录代码就很简单啦,代码如下(为了方便直接阅读,少贴一些代码,里面需要用到的常量,我就不提取不封装啦,实际工作工程中,可以专门做一个类,存放所有的常量,如appid,secret等,统一管理):

@Controller
@RequestMapping("/api")
public class UserController {
	@Autowired
        private UserService userService;
	@Autowired
	private VorderService vorderService;
	@Autowired
	private JwtUtils jwtUtils;
	@Autowired
	private RedisOperator redis;
	
    @PostMapping("/wxlogin")
    @ResponseBody
    public JsonResult wxlogin(@RequestBody Map<String, Object> map){
        // 配置请求参数
        Map<String, String> param = new HashMap<>();
        param.put("appid", "wx0fb11123456897544");
        param.put("secret", "5c5e5efae856768878yb53976b");
        param.put("js_code", map.get("code").toString());
        param.put("grant_type", "authorization_code");

        // 发送请求
        String url = "https://api.weixin.qq.com/sns/jscode2session";
        String wxResult = HttpClientUtil.doGet(url, param);
        
        JSONObject jsonObject = JSONObject.parseObject(wxResult);
        // 获取参数返回的
        String sessionkey = jsonObject.get("session_key").toString();
        String openid = jsonObject.get("openid").toString();
        
        // 根据返回的user实体类,判断用户是否是新用户,不是的话,更新最新登录时间,是的话,将用户信息存到数据库
        User user = userService.selectByOpenId(openid);
        System.out.println("查询用户结果"+user);
        if(user != null){
            user.setUname(map.get("uname").toString());
            user.setUavatar(map.get("uavatar").toString());
            user.setUgender(map.get("ugender").toString());
            user.setUaddress(map.get("address").toString());
            user.setSessionkey(sessionkey);  //修改sessionKey
            user.setUpdateTime(new Date());  //修改更新时间
            //更新数据库
            userService.update(user);
        }else{
            User newUser = new User();
            newUser.setOpenid(openid);
            newUser.setSessionkey(sessionkey);
            newUser.setUname(map.get("uname").toString());
            newUser.setUavatar(map.get("uavatar").toString());
            newUser.setUgender(map.get("ugender").toString());
            newUser.setUaddress(map.get("address").toString());  
            newUser.setCreateTime(new Date());
            // 添加到数据库
            int count = userService.insert(newUser);
            if(count < 0){
                return JsonResult.error("插入数据失败");
            }
        }

        //获取到当前用户的数据库uid
        User user1 = userService.selectByOpenId(openid);
        
        //生成JWTtoken 
        UserClaims userClaims = new UserClaims();
        userClaims.setUid(user1.getUid());
        userClaims.setOpenid(openid);
        String jwttoken = jwtUtils.createToken(userClaims);
        System.out.println(jwttoken);
        //将token存入Redis
        redis.set(jwttoken, sessionkey);
        
        // 封装返回小程序
        Map<String, String> result = new HashMap<>();
        result.put("token", jwttoken);

        return JsonResult.successs(result);
    }
}

注意:

1、上文中用到的appid和secret都是我写的假值,需要替换为自己的;

2、统一返回结果数据结构,这种实现方式很多,就是我里面出现的JsonResult类;

3、需要用到的Redis工具类也自己封装一下,这个实现方法也不麻烦,不影响我们的授权登录流程;

上面的代码就足以完成微信小程序授权登陆啦,小程序获取到token后,记得将token存入本地storage,后面进行登录态校验的请求,在Header头中设置Authorization值为token;后台使用封装好的JwtUtils工具类中的getUserClaim方法,就可以从request中直接解析出Claims对象,里面存放着我们封装进去的uid和openid,然后开始处理我们的正常业务啦;

此致敬礼 

jiguiquan@163.com

文章作者信息...

1 Comment

留下你的评论

*评论支持代码高亮<pre class="prettyprint linenums">代码</pre>

相关推荐