All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.bestwu.security.oauth2.social.provider.SocialAdapter Maven / Gradle / Ivy

package cn.bestwu.security.oauth2.social.provider;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import javax.annotation.PostConstruct;
import java.util.*;

/**
 * 社交账号登录适配
 * 默认已适配:QQ/微信/微博/腾讯微博
 *
 * @param  系统用户
 */
public abstract class SocialAdapter {
	/**
	 * 微信 provider id grantType
	 */
	public static final String WEIXIN = "weixin";
	/**
	 * QQ provider id grantType
	 */
	public static final String QQ = "qq";
	/**
	 * 腾讯微博 provider id grantType
	 */
	public static final String TQQ = "tqq";
	/**
	 * 微博 provider id grantType
	 */
	public static final String WEIBO = "weibo";

	protected final Log logger = LogFactory.getLog(getClass());

	/**
	 * 支持的社交类型
	 */
	private final Set supportProviders;
	private final RestTemplate restTemplate;
	/**
	 * 是否自动注册
	 */
	private boolean autoLogon;
	/**
	 * QQ appid
	 */
	private String qqAppId;
	/**
	 * 是否自动刷新用户资料
	 */
	private boolean refreshUserProfile;
	//--------------------------------------------

	@Autowired
	private SocialProperties socialProperties;

	public SocialAdapter() {
		this.restTemplate = createRestTemplate();
		this.supportProviders = new HashSet<>(4);
		this.supportProviders.add(WEIXIN);
		this.supportProviders.add(QQ);
		this.supportProviders.add(TQQ);
		this.supportProviders.add(WEIBO);
	}

	@PostConstruct
	public void init() {
		this.qqAppId = socialProperties.getQqAppKey();
		this.autoLogon = socialProperties.isAutoLogon();
		this.refreshUserProfile = socialProperties.isRefreshUserProfile();
	}

	//--------------------------------------------

	/**
	 * @return 是否自动刷新用户资料
	 */
	public boolean isRefreshUserProfile() {
		return refreshUserProfile;
	}

	/**
	 * @return 是否自动注册
	 */
	public boolean isAutoLogon() {
		return autoLogon;
	}

	//--------------------------------------------

	/**
	 * 查找系统中是否存在注册的社交账号
	 *
	 * @param pid  社交账号类型
	 * @param puid 社交账号用户ID
	 * @return 注册的账号
	 */
	public abstract SocialId findByPidAndPuid(String pid, String puid);

	/**
	 * 刷新用户资料
	 *
	 * @param user        系统用户
	 * @param userProfile 社交用户
	 */
	public abstract void saveUserProfile(T user, UserProfile userProfile);

	/**
	 * @param userProfile 用户资料,注意处理imageHref
	 * @return 注册到系统的社交账号
	 */
	public abstract SocialId signup(UserProfile userProfile);

	/**
	 * @param user user
	 * @return UserDetails
	 */
	public abstract UserDetails loadUserDetailsByUser(T user);

	/**
	 * 解析扩展资料
	 *
	 * @param pid                 社交账号提供方
	 * @param originalUserProfile 原始资料
	 * @return 扩展资料
	 */
	public abstract Map findUserProfileExtra(String pid, Map originalUserProfile);

	//--------------------------------------------

	protected RestTemplate createRestTemplate() {
		RestTemplate restTemplate = new RestTemplate();
		MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
		List supportedMediaTypes = new ArrayList<>(jackson2HttpMessageConverter.getSupportedMediaTypes());
		supportedMediaTypes.add(MediaType.TEXT_PLAIN);
		supportedMediaTypes.add(MediaType.TEXT_HTML);
		jackson2HttpMessageConverter.setSupportedMediaTypes(supportedMediaTypes);
		restTemplate.setMessageConverters(Collections.singletonList(jackson2HttpMessageConverter));
		return restTemplate;
	}

	/**
	 * @param user     系统用户
	 * @param clientId clientId
	 * @param scope    scope
	 * @return OAuth2Authentication
	 */
	public OAuth2Authentication createOAuth2Authentication(T user, String clientId, Set scope) {
		OAuth2Request request = new OAuth2Request(null, clientId, null, true, scope,
				null, null, null, null);

		UserDetails userDetails = loadUserDetailsByUser(user);
		return new OAuth2Authentication(request, new UsernamePasswordAuthenticationToken(userDetails, "N/A", userDetails.getAuthorities()));
	}

	/**
	 * 验证社交账号social_token
	 *
	 * @param pid          社交账号提供方
	 * @param puid         社交账号用户id
	 * @param social_token 社交账号access_token
	 * @return 社交账号用户资料
	 * @throws SocialException social_token不正确抛出异常
	 */
	public Map validateSocialTokenAndGetUserOriginalProfile(String pid, String puid, String social_token) throws SocialException {
		Map originalProfile = null;
		switch (pid) {
		case WEIXIN:
			originalProfile = restTemplate.getForObject("https://api.weixin.qq.com/sns/userinfo?access_token={0}&openid={1}&lang=zh_CN", Map.class, social_token, puid);
			if (logger.isDebugEnabled()) {
				logger.debug(social_token);
				logger.debug(originalProfile);
			}
			if (originalProfile.get("errcode") != null) {
				throw new SocialException((String) originalProfile.get("errmsg"));
			}
			break;
		case TQQ://腾讯微博资料 scope:get_info
			originalProfile = restTemplate.getForObject("https://graph.qq.com/user/get_info?access_token={0}&oauth_consumer_key={1}&openid={2}&format=json", Map.class, social_token, qqAppId, puid);
			if (logger.isDebugEnabled()) {
				logger.debug(social_token);
				logger.debug(originalProfile);
			}
			if (0 != (int) originalProfile.get("ret")) {
				throw new SocialException((String) originalProfile.get("msg"));
			} else {
				originalProfile = (Map) originalProfile.get("data");
			}
			break;
		case QQ://腾讯QQ资料 scope:get_user_info
			originalProfile = restTemplate.getForObject("https://graph.qq.com/user/get_user_info?access_token={0}&oauth_consumer_key={1}&openid={2}", Map.class, social_token, qqAppId, puid);
			if (logger.isDebugEnabled()) {
				logger.debug(social_token);
				logger.debug(originalProfile);
			}
			if (0 != (int) originalProfile.get("ret")) {
				throw new SocialException((String) originalProfile.get("msg"));
			}
			break;
		case WEIBO:
			originalProfile = restTemplate.getForObject("https://api.weibo.com/2/users/show.json?access_token={0}&uid={1}", Map.class, social_token, puid);
			if (logger.isDebugEnabled()) {
				logger.debug(social_token);
				logger.debug(originalProfile);
			}
			if (originalProfile.get("error_code") != null) {
				throw new SocialException((String) originalProfile.get("error"));
			}
			break;
		}
		return originalProfile;
	}

	/**
	 * @param pid                 社交账号提供方
	 * @param puid                社交账号用户id
	 * @param originalUserProfile 原始社交账号资料
	 * @return UserProfile
	 */
	public UserProfile originalProfile2UserProfile(String pid, String puid, Map originalUserProfile) {
		UserProfile userProfile = new UserProfile(pid, puid);
		switch (pid) {
		case WEIXIN:
			userProfile.setNickname((String) originalUserProfile.get("nickname"));
			userProfile.setSex(getSex(originalUserProfile));
			userProfile.setImageHref((String) originalUserProfile.get("headimgurl"));
			break;
		case TQQ:
			userProfile.setNickname((String) originalUserProfile.get("nick"));
			userProfile.setSex(getSex(originalUserProfile));
			String head = (String) originalUserProfile.get("head");
			if (StringUtils.hasText(head)) {
				userProfile.setImageHref(head + "/100");
			}
			break;
		case QQ:
			userProfile.setNickname((String) originalUserProfile.get("nickname"));
			String qqgender = (String) originalUserProfile.get("gender");
			Sex qqsex;
			if (qqgender == null) {
				qqsex = Sex.UNKNOWN;
			} else {
				switch (qqgender) {
				case "男":
					qqsex = Sex.MALE;
					break;
				case "女":
					qqsex = Sex.FEMALE;
					break;
				default:
					qqsex = Sex.UNKNOWN;
					break;
				}
			}
			userProfile.setSex(qqsex);
			String figureurl_qq = (String) originalUserProfile.get("figureurl_qq_2");
			if (!StringUtils.hasText(figureurl_qq)) {
				figureurl_qq = (String) originalUserProfile.get("figureurl_qq_1");
			}
			userProfile.setImageHref(figureurl_qq);
			break;
		case WEIBO:
			userProfile.setNickname((String) originalUserProfile.get("screen_name"));
			String gender = (String) originalUserProfile.get("gender");
			Sex sex;
			if (gender == null) {
				sex = Sex.UNKNOWN;
			} else {
				switch (gender) {
				case "m":
					sex = Sex.MALE;
					break;
				case "f":
					sex = Sex.FEMALE;
					break;
				default:
				case "n":
					sex = Sex.UNKNOWN;
					break;
				}
			}
			userProfile.setSex(sex);
			userProfile.setImageHref((String) originalUserProfile.get("avatar_large"));
			break;
		}

		Map userProfileExtra = findUserProfileExtra(pid, originalUserProfile);
		userProfile.setExtra(userProfileExtra);
		return userProfile;
	}

	/**
	 * 解析性别
	 *
	 * @param originalUserProfile 原始用户资料
	 * @return 性别
	 */
	private Sex getSex(Map originalUserProfile) {
		Integer originalSex = (Integer) originalUserProfile.get("sex");
		Sex sex;
		if (originalSex == null) {
			sex = Sex.UNKNOWN;
		} else {
			switch (originalSex) {
			case 1:
				sex = Sex.MALE;
				break;
			case 2:
				sex = Sex.FEMALE;
				break;
			default:
			case 0:
				sex = Sex.UNKNOWN;
				break;
			}
		}
		return sex;
	}

	//--------------------------------------------

	/**
	 * @param grantType grantType
	 * @return 是否支持指定 grantType的登录
	 */
	public boolean support(String grantType) {
		return supportProviders.contains(grantType.toLowerCase());
	}

}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy