SpringSocial之微信登录

编写顺序同SpringSocial之QQ登录

创建用户信息类WeiXinUserInfo:
package com.cong.security.core.social.weixin.api;

import lombok.Data;

/**
 * 微信用户信息
 */
@Data
public class WeixinUserInfo {
	//普通用户的标识,对当前开发者帐号唯一
	private String openid;
	//普通用户昵称
	private String nickname;
	//语言
	private String language;
	//普通用户性别,1为男性,2为女性
	private String sex;
	//普通用户个人资料填写的省份
	private String province;
	//普通用户个人资料填写的城市
	private String city;
	//国家,如中国为CN
	private String country;
	//用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空
	private String headimgurl;
	//用户特权信息,json数组,如微信沃卡用户为(chinaunicom)
	private String[] privilege;
	//用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的unionid是唯一的。
	private String unionid;
}
微信获取信息接口:
package com.cong.security.core.social.weixin.api;

/**
 * 微信API调用接口
 */
public interface Weixin {

    /**
     * @return com.cong.security.core.social.weixin.api.WeixinUserInfo
     * @Description 通过用户openId查询用户信息
     **/
    WeixinUserInfo getUserInfo(String openId);
}
微信获取信息接口实现类:
package com.cong.security.core.social.weixin.api;

import java.nio.charset.Charset;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.social.oauth2.AbstractOAuth2ApiBinding;
import org.springframework.social.oauth2.TokenStrategy;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * Weixin API调用模板, scope为Request的Spring bean, 根据当前用户的accessToken创建。
 */
public class WeixinImpl extends AbstractOAuth2ApiBinding implements Weixin {

	private ObjectMapper objectMapper = new ObjectMapper();
	// 获取用户信息的url
	private static final String URL_GET_USER_INFO = "https://api.weixin.qq.com/sns/userinfo?openid=";

	public WeixinImpl(String accessToken) {
		super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER);
	}

	//默认注册的StringHttpMessageConverter字符集为ISO-8859-1,而微信返回的是UTF-8的,所以覆盖了原来的方法。
	protected List<HttpMessageConverter<?>> getMessageConverters() {
		List<HttpMessageConverter<?>> messageConverters = super.getMessageConverters();
		messageConverters.remove(0);
		messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
		return messageConverters;
	}

	/**
	 * 获取微信用户信息。
	 */
	@Override
	public WeixinUserInfo getUserInfo(String openId) {
		String url = URL_GET_USER_INFO + openId;
		String response = getRestTemplate().getForObject(url, String.class);
		if (StringUtils.contains(response, "errcode")) {
			// 报错,返回null
			return null;
		}
		WeixinUserInfo profile = null;
		try {
			profile = objectMapper.readValue(response, WeixinUserInfo.class);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return profile;
	}
}
定义WeixinAccessGrant:

后面在开发APP模块的时候客户端可以直接从第三方获取到从而实现openID登录,QQ登陆的时候也可以获取到openId。

package com.cong.security.core.social.weixin.connect;

import org.springframework.social.oauth2.AccessGrant;

/**
 * 微信的access_token信息。与标准OAuth2协议不同,微信在获取access_token时会同时返回openId,并没有单独的通过accessToken换取openId的服务<br>
 * 所以在这里继承了标准AccessGrant,添加了openId字段,作为对微信access_token信息的封装。<br>
 */
public class WeixinAccessGrant extends AccessGrant {
    private static final long serialVersionUID = -7243374526633186782L;
    // 用以保存多返回的openId字段
    private String openId;
    public WeixinAccessGrant() {
        super("");
    }

    public WeixinAccessGrant(String accessToken, String scope, String refreshToken, Long expiresIn) {
        super(accessToken, scope, refreshToken, expiresIn);
    }
    public String getOpenId() {
        return openId;
    }
    public void setOpenId(String openId) {
        this.openId = openId;
    }
}
WeixinOAuth2Template:
package com.cong.security.core.social.weixin.connect;

import java.nio.charset.Charset;
import java.util.Map;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.social.oauth2.AccessGrant;
import org.springframework.social.oauth2.OAuth2Parameters;
import org.springframework.social.oauth2.OAuth2Template;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;

/**
 * 完成微信的OAuth2认证流程的模板类。国内厂商实现的OAuth2每个都不同,
 * spring默认提供的OAuth2Template适应不了,只能针对每个厂商自己微调。
 */
@Slf4j
public class WeixinOAuth2Template extends OAuth2Template {

	private String clientId;

	private String clientSecret;

	private String accessTokenUrl;

	private static final String REFRESH_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/refresh_token";

	public WeixinOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {
		super(clientId, clientSecret, authorizeUrl, accessTokenUrl);
		setUseParametersForClientAuthentication(true);
		this.clientId = clientId;
		this.clientSecret = clientSecret;
		this.accessTokenUrl = accessTokenUrl;
	}

	@Override
	public AccessGrant exchangeForAccess(String authorizationCode, String redirectUri,
			MultiValueMap<String, String> parameters) {
		// 自己拼请求的串
		StringBuilder accessTokenRequestUrl = new StringBuilder(accessTokenUrl);
		accessTokenRequestUrl.append("?appid=" + clientId);
		accessTokenRequestUrl.append("&secret=" + clientSecret);
		accessTokenRequestUrl.append("&code=" + authorizationCode);
		accessTokenRequestUrl.append("&grant_type=authorization_code");
		accessTokenRequestUrl.append("&redirect_uri=" + redirectUri);
		return getAccessToken(accessTokenRequestUrl);
	}

	public AccessGrant refreshAccess(String refreshToken, MultiValueMap<String, String> additionalParameters) {
		StringBuilder refreshTokenUrl = new StringBuilder(REFRESH_TOKEN_URL);
		refreshTokenUrl.append("?appid=" + clientId);
		refreshTokenUrl.append("&grant_type=refresh_token");
		refreshTokenUrl.append("&refresh_token=" + refreshToken);
		return getAccessToken(refreshTokenUrl);
	}

	private AccessGrant getAccessToken(StringBuilder accessTokenRequestUrl) {
		String response = getRestTemplate().getForObject(accessTokenRequestUrl.toString(), String.class);
		log.info("微信获取access_token请求URL[{}], 响应内容:[{}] ",accessTokenRequestUrl.toString(), response);
		Map<String, Object> result = null;
		try {
			result = new ObjectMapper().readValue(response, Map.class);
		} catch (Exception e) {
			e.printStackTrace();
		}
		// 返回错误码时直接返回空
		if (StringUtils.isNotBlank(MapUtils.getString(result, "errcode"))) {
			String errcode = MapUtils.getString(result, "errcode");
			String errmsg = MapUtils.getString(result, "errmsg");
			throw new RuntimeException("获取access token失败, errcode:" + errcode + ", errmsg:" + errmsg);
		}
		WeixinAccessGrant accessToken = new WeixinAccessGrant(MapUtils.getString(result, "access_token"),
				MapUtils.getString(result, "scope"), MapUtils.getString(result, "refresh_token"),
				MapUtils.getLong(result, "expires_in"));
		// 额外的openId字段
		accessToken.setOpenId(MapUtils.getString(result, "openid"));
		return accessToken;
	}

	/**
	 * 构建获取授权码的请求。也就是引导用户跳转到微信的地址。
	 */
	public String buildAuthenticateUrl(OAuth2Parameters parameters) {
		String url = super.buildAuthenticateUrl(parameters);
		url = url + "&appid=" + clientId + "&scope=snsapi_login";
		return url;
	}

	public String buildAuthorizeUrl(OAuth2Parameters parameters) {
		return buildAuthenticateUrl(parameters);
	}

	/**
	 * 微信返回的contentType是html/text,添加相应的HttpMessageConverter来处理。
	 */
	protected RestTemplate createRestTemplate() {
		RestTemplate restTemplate = super.createRestTemplate();
		restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
		return restTemplate;
	}
}
WeixinAdapter适配器:
package com.cong.security.core.social.weixin.connect;

import com.cong.security.core.social.weixin.api.Weixin;
import com.cong.security.core.social.weixin.api.WeixinUserInfo;
import org.springframework.social.connect.ApiAdapter;
import org.springframework.social.connect.ConnectionValues;
import org.springframework.social.connect.UserProfile;

/**
 * 微信 api适配器,将微信 api的数据模型转为spring social的标准模型。
 */
public class WeixinAdapter implements ApiAdapter<Weixin> {
	
	private String openId;
	
	public WeixinAdapter() {}
	
	public WeixinAdapter(String openId){
		this.openId = openId;
	}

	@Override
	public boolean test(Weixin api) {
		return true;
	}
	@Override
	public void setConnectionValues(Weixin api, ConnectionValues values) {
		WeixinUserInfo profile = api.getUserInfo(openId);
		values.setProviderUserId(profile.getOpenid());
		values.setDisplayName(profile.getNickname());
		values.setImageUrl(profile.getHeadimgurl());
	}

	@Override
	public UserProfile fetchUserProfile(Weixin api) {
		return null;
	}

	@Override
	public void updateStatus(Weixin api, String message) {}
}
WeixinServiceProvider:
package com.cong.security.core.social.weixin.connect;

import com.cong.security.core.social.weixin.api.Weixin;
import com.cong.security.core.social.weixin.api.WeixinImpl;
import org.springframework.social.oauth2.AbstractOAuth2ServiceProvider;

/**
 * 微信的OAuth2流程处理器的提供器,供spring social的connect体系调用
 */
public class WeixinServiceProvider extends AbstractOAuth2ServiceProvider<Weixin> {

	//微信获取授权码的url
	private static final String URL_AUTHORIZE = "https://open.weixin.qq.com/connect/qrconnect";
	//微信获取accessToken的url
	private static final String URL_ACCESS_TOKEN = "https://api.weixin.qq.com/sns/oauth2/access_token";
	public WeixinServiceProvider(String appId, String appSecret) {
		super(new WeixinOAuth2Template(appId, appSecret, URL_AUTHORIZE, URL_ACCESS_TOKEN));
	}
	@Override
	public Weixin getApi(String accessToken) {
		return new WeixinImpl(accessToken);
	}
}
WeixinConnectionFactory
package com.cong.security.core.social.weixin.connect;

import com.cong.security.core.social.weixin.api.Weixin;
import org.springframework.social.connect.ApiAdapter;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionData;
import org.springframework.social.connect.support.OAuth2Connection;
import org.springframework.social.connect.support.OAuth2ConnectionFactory;
import org.springframework.social.oauth2.AccessGrant;
import org.springframework.social.oauth2.OAuth2ServiceProvider;

/**
 * 微信连接工厂
 */
public class WeixinConnectionFactory extends OAuth2ConnectionFactory<Weixin> {

	/**
	 * @param appId
	 * @param appSecret
	 */
	public WeixinConnectionFactory(String providerId, String appId, String appSecret) {
		super(providerId, new WeixinServiceProvider(appId, appSecret), new WeixinAdapter());
	}

	/**
	 * 由于微信的openId是和accessToken一起返回的,所以在这里直接根据accessToken设置providerUserId即可,不用像QQ那样通过QQAdapter来获取
	 */
	@Override
	protected String extractProviderUserId(AccessGrant accessGrant) {
		if (accessGrant instanceof WeixinAccessGrant) {
			return ((WeixinAccessGrant) accessGrant).getOpenId();
		}
		return null;
	}

	public Connection<Weixin> createConnection(AccessGrant accessGrant) {
		return new OAuth2Connection<Weixin>(getProviderId(), extractProviderUserId(accessGrant),
				accessGrant.getAccessToken(), accessGrant.getRefreshToken(), accessGrant.getExpireTime(),
				getOAuth2ServiceProvider(), getApiAdapter(extractProviderUserId(accessGrant)));
	}

	public Connection<Weixin> createConnection(ConnectionData data) {
		return new OAuth2Connection<Weixin>(data, getOAuth2ServiceProvider(), getApiAdapter(data.getProviderUserId()));
	}

	private ApiAdapter<Weixin> getApiAdapter(String providerUserId) {
		return new WeixinAdapter(providerUserId);
	}

	private OAuth2ServiceProvider<Weixin> getOAuth2ServiceProvider() {
		return (OAuth2ServiceProvider<Weixin>) getServiceProvider();
	}
}
WeixinAutoConfig:
package com.cong.security.core.social;

import javax.sql.DataSource;
import com.cong.security.core.properties.SecurityProperties;
import com.cong.security.core.properties.WeixinProperties;
import com.cong.security.core.social.weixin.connect.WeixinConnectionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.social.config.annotation.ConnectionFactoryConfigurer;
import org.springframework.social.config.annotation.SocialConfigurerAdapter;
import org.springframework.social.connect.ConnectionFactory;
import org.springframework.social.connect.ConnectionFactoryLocator;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.connect.jdbc.JdbcUsersConnectionRepository;

/**
 * 微信登录配置
 * 
 * @author single-聪
 *
 */
@Configuration
@ConditionalOnProperty(prefix = "my.security.social.weixin", name = "app-id")
public class WeixinAutoConfig extends SocialConfigurerAdapter {

	@Autowired
	private DataSource dataSource;

	@Autowired
	private SecurityProperties securityProperties;

	@Override
	public void addConnectionFactories(ConnectionFactoryConfigurer connectionFactoryConfigurer,
			Environment environment) {
		connectionFactoryConfigurer.addConnectionFactory(createConnectionFactory());
	}

	protected ConnectionFactory<?> createConnectionFactory() {
		WeixinProperties weixinConfig = securityProperties.getSocial().getWeixin();
		return new WeixinConnectionFactory(weixinConfig.getProviderId(), weixinConfig.getAppId(),
				weixinConfig.getAppSecret());
	}
	@Override
	public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
		// 将微信登陆的实现类设置为jdbc实现
		JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource,
				connectionFactoryLocator, Encryptors.noOpText());
		return repository;
	}
}

登录页面添加微信登录链接

<a href="/login/weixin">微信登录</a>

微信账号为申请到,无法测试代码逻辑无问题,个人一般不开发网站应用,多数为APP,所以此处只作为笔记。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值