欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring Social 开发QQ登录

程序员文章站 2022-05-05 15:16:50
...

可先参考
SpringSocial基本原理
Spring Social 认证流程源码详解

授权流程主要接口

Spring Social 开发QQ登录
Spring Social 开发QQ登录

代码结构

Spring Social 开发QQ登录

获取用户信息

Api(个性化第六步),实际上没有一个明确的接口,因为每一个服务提供商对于用户基本信息的调用都是有区别的。SpringSocial其实也提供了一个抽象类叫AbstractOauth2ApiBinding帮助我们快速开发第六步的实现。

public class QQImpl extends AbstractOAuth2ApiBinding implements QQ {

	//获取openId
	private static final String URL_GET_OPENID = "https://graph.qq.com/oauth2.0/me?access_token=%s";

	//获取用户信息
	private static final String URL_GET_USERINFO = "https://graph.qq.com/user/get_user_info?oauth_consumer_key=%s&openid=%s";
	
	private String appId;
	
	private String openId;
	
	private ObjectMapper objectMapper = new ObjectMapper();

	//获取openId
	public QQImpl(String accessToken, String appId) {
		super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER);
		
		this.appId = appId;
		
		String url = String.format(URL_GET_OPENID, accessToken);
		String result = getRestTemplate().getForObject(url, String.class);
		
		System.out.println(result);
		
		this.openId = StringUtils.substringBetween(result, "\"openid\":\"", "\"}");
	}
	
	/**
	 * 获取用户信息
	 * @see QQ#getUserInfo()
	 */
	@Override
	public QQUserInfo getUserInfo() {
		
		String url = String.format(URL_GET_USERINFO, appId, openId);
		String result = getRestTemplate().getForObject(url, String.class);
		
		System.out.println(result);
		
		QQUserInfo userInfo = null;
		try {
			userInfo = objectMapper.readValue(result, QQUserInfo.class);
			userInfo.setOpenId(openId);
			return userInfo;
		} catch (Exception e) {
			throw new RuntimeException("获取用户信息失败", e);
		}
	}

}

实现Oauth2Operation接口

Oauth2Operation封装了授权流程的第一步到第五步,Spring提供了一个默认的实现叫Oauth2Template,这个类会帮助我们去完成Oauth协议的执行流程。这里自定义QQOAuth2Template,复写了OAuth2Template的一些方法,主要是为了适应QQ提供的api接口。

public class QQOAuth2Template extends OAuth2Template {

    private Logger logger = LoggerFactory.getLogger(getClass());

    /**
     *
     * @param clientId
     * @param clientSecret
     * @param authorizeUrl
     * @param accessTokenUrl
     */
    public QQOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {
        super(clientId, clientSecret, authorizeUrl, accessTokenUrl);
        setUseParametersForClientAuthentication(true);  //设置为true,使其携带clientId
    }


    /**
     * 重写postForAccessGrant,因为qq获取令牌的api,返回的时字符串,不是map
     * @param accessTokenUrl
     * @param parameters
     * @return
     */
    @Override
    protected AccessGrant postForAccessGrant(String accessTokenUrl, MultiValueMap<String, String> parameters) {
        String responseStr = getRestTemplate().postForObject(accessTokenUrl, parameters, String.class);

        logger.info("获取accessToke的响应:" + responseStr);

        String[] items = StringUtils.splitByWholeSeparatorPreserveAllTokens(responseStr, "&");

        String accessToken = StringUtils.substringAfterLast(items[0], "=");
        Long expiresIn = new Long(StringUtils.substringAfterLast(items[1], "="));
        String refreshToken = StringUtils.substringAfterLast(items[2], "=");

        return new AccessGrant(accessToken, null, refreshToken, expiresIn);
    }

    /**
     * 添加StringHttpMessageConverter,编码方式为UTF-8
     * @return
     */
    @Override
    protected RestTemplate createRestTemplate() {
        RestTemplate restTemplate = super.createRestTemplate();
        restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
        return restTemplate;
    }

}

实现ServiceProvider接口

ServiceProvider接口,这个接口是服务提供商的一个抽象,针对每一个服务提供商(QQ、微信),都需要一个ServiceProvider接口的一个实现。

public class QQServiceProvider extends AbstractOAuth2ServiceProvider<QQ> {

	private String appId;

	//获取授权码
	private static final String URL_AUTHORIZE = "https://graph.qq.com/oauth2.0/authorize";

	//获取accessToken
	private static final String URL_ACCESS_TOKEN = "https://graph.qq.com/oauth2.0/token";

	/**
	 *
	 * @param appId     clientId
	 * @param appSecret  clientSecret
	 */
	public QQServiceProvider(String appId, String appSecret) {
		super(new QQOAuth2Template(appId, appSecret, URL_AUTHORIZE, URL_ACCESS_TOKEN));
		this.appId = appId;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.springframework.social.oauth2.AbstractOAuth2ServiceProvider#getApi(java.
	 * lang.String)
	 */
	@Override
	public QQ getApi(String accessToken) {
		return new QQImpl(accessToken, appId);
	}

}

实现ApiAdapter

ApiAdapter,将不同格式的用户信息转化为固定格式的Connection对象就是由ApiAdapter接口的实现来完成。转化成功之后就将Connection中封装进去一个用户信息

/**
 * 适配器
 * 把从qqApi获取的个性化信息配置到ConnectionValues中
 * @author zhailiang
 *
 */
public class QQAdapter implements ApiAdapter<QQ> {

	@Override
	public boolean test(QQ api) {
		return true;
	}

	@Override
	public void setConnectionValues(QQ api, ConnectionValues values) {
		QQUserInfo userInfo = api.getUserInfo();
		
		values.setDisplayName(userInfo.getNickname());
		values.setImageUrl(userInfo.getFigureurl_qq_1());
		values.setProfileUrl(null);   //个人主页等
		values.setProviderUserId(userInfo.getOpenId());  //
	}

	@Override
	public UserProfile fetchUserProfile(QQ api) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void updateStatus(QQ api, String message) {
		//do noting
	}

}

实现ConnectionFactory接口

ConnectionFactory工程是为了创建Connection对象,Connection对象封装前六步执行完毕之后获取到的用户信息。

/**
 * 实现QQConnectionFactory,
 * 把QQServiceProvider,QQAdapter放入工厂中
 *
 */
public class QQConnectionFactory extends OAuth2ConnectionFactory<QQ> {

	/**
	 *
	 * @param providerId 提供商的唯一表示
	 * @param appId
	 * @param appSecret
	 */
	public QQConnectionFactory(String providerId, String appId, String appSecret) {
		super(providerId, new QQServiceProvider(appId, appSecret), new QQAdapter());
	}

}

自动配置类

该类的目的是根据配置文件,决定是否需要注入QQConnectionFactory。

/**
 *  注入 QQConnectionFactory
 */
@Configuration
@ConditionalOnProperty(prefix = "imooc.security.social.qq", name = "app-id") //当imooc.security.social.qq.app-id配置了,QQAutoConfig才生效
public class QQAutoConfig extends SocialAutoConfigurerAdapter {

	@Autowired
	private SecurityProperties securityProperties;

	@Override
	protected ConnectionFactory<?> createConnectionFactory() {
		QQProperties qqConfig = securityProperties.getSocial().getQq();
		return new QQConnectionFactory(qqConfig.getProviderId(), qqConfig.getAppId(), qqConfig.getAppSecret());
	}

}

社交登录配置主类

该类中定义了JdbcUsersConnectionRepository 存储器,目的是将传过来的用户信息与我们系统中已经保存的用户信息去进行对应的。
并且配置类社交登录拦截的url,默认是/auth,和注册页的url,默认是/signUp

@Configuration
@EnableSocial
public class SocialConfig extends SocialConfigurerAdapter {

	@Autowired
	private DataSource dataSource;

	@Autowired
	private SecurityProperties securityProperties;
	
	@Autowired(required = false)
	private ConnectionSignUp connectionSignUp;
	
	@Autowired(required = false)
	private SocialAuthenticationFilterPostProcessor socialAuthenticationFilterPostProcessor;

	//把用户信息存到数据库
	@Override
	public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
		JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource,
				connectionFactoryLocator, Encryptors.noOpText());
		repository.setTablePrefix("imooc_"); //存到imooc_userconnection表
		if(connectionSignUp != null) {
			repository.setConnectionSignUp(connectionSignUp);
		}
		return repository;
	}
	
	/**
	 * 社交登录配置类,供浏览器或app模块引入设计登录配置用。
	 * @return
	 */
	@Bean
	public SpringSocialConfigurer imoocSocialSecurityConfig() {
		String filterProcessesUrl = securityProperties.getSocial().getFilterProcessesUrl(); // 过滤的的url,默认为auth
		ImoocSpringSocialConfigurer configurer = new ImoocSpringSocialConfigurer(filterProcessesUrl);
		configurer.signupUrl(securityProperties.getBrowser().getSignUpUrl());  //注册跳转的url
		//?
		configurer.setSocialAuthenticationFilterPostProcessor(socialAuthenticationFilterPostProcessor);
		return configurer;
	}

	/**
	 * 用来处理注册流程的工具类
	 * 
	 * @param connectionFactoryLocator
	 * @return
	 */
	@Bean
	public ProviderSignInUtils providerSignInUtils(ConnectionFactoryLocator connectionFactoryLocator) {
		return new ProviderSignInUtils(connectionFactoryLocator,
				getUsersConnectionRepository(connectionFactoryLocator)) {
		};
	}
}

最后把SocialAuthenticationFilter添加到SpringSecurity过滤器链中

http.apply(validateCodeSecurityConfig)
				.and()
			.apply(smsCodeAuthenticationSecurityConfig)
				.and()
			.apply(imoocSocialSecurityConfig)

源码地址:https://github.com/ChengHaoHappy/imooc-security

相关标签: Spring Security