博客之QQ登录功能(一)

流程图
博客之QQ登录功能(一)_第1张图片
博客之QQ登录功能(一)_第2张图片
博客之QQ登录功能(一)_第3张图片
上图spring social 封装了1-8步需要的工作
博客之QQ登录功能(一)_第4张图片

1、新建包和书写配置文件

博客之QQ登录功能(一)_第5张图片
在这里插入图片描述

public class QQProperties {

	//App唯一标 识
	private String appId = "100550231";
	private String appSecret = "69b6ab57b22f3c2fe6a6149274e3295e";
	
	//QQ供应商
	private String providerId = "callback.do";
	//拦截器拦截的请求
	private String filterProcessesUrl = "/qqLogin";
	
	//get set 方法
	//...
}
@ConfigurationProperties(prefix = "blog.security")
public class BlogSecurityProperties {
	
	private QQProperties qqProperties = new QQProperties();

	//get set...
}
@Configuration
//让我们的配置生效
@EnableConfigurationProperties (BlogSecurityProperties.class)
public class BlogSecurityConfig {

}

2、获取QQ用户信息

package com.zzz.blog.social.qq.api;

public interface QQ {

	//返回一个QQ的用户信息
	QQUserInfo getUserInfo();
}
package com.zzz.blog.social.qq.api;

import org.springframework.social.oauth2.AbstractOAuth2ApiBinding;

public class QQImpl extends AbstractOAuth2ApiBinding implements QQ{

	private static final String URL_GET_USERINFO = "https://graph.qq.com/user/get_user_info?oauth_consumer_key=%s&openid=%s";
	
	//外界赋值
	private String appId;
	//用户的唯一 标识,url
	private String openId;
	
	private ObjectMapper objectMapper = new ObjectMapper();

	@Override
	public QQUserInfo getUserInfo() {
		// TODO Auto-generated method stub
		return null;
	}

}
package com.zzz.blog.social.qq.api;

public class QQUserInfo {

	private String is_lost;
	private String province;
	private String city;
	private String year;
	private String constellation;
	
	private String ret;
	private String msg;
	private String nickname;
	
	private String figureurl;
	private String figureurl_1;
	private String figureurl_2;
	private String figureurl_qq_1;
	private String figureurl_qq_2;

	private String figureurl_qq;
	private String figureurl_type;
	private String gender_type;
	
	private String gender;
	private String is_yellow_vip;
	private String vip;
	private String yellow_vip_level;
	private String level;
	private String is_yellow_year_vip;
	
	private String openId;
	
	//get/set...
}

修改代码:

	//获取用户信息
	@Override
	public QQUserInfo getUserInfo() {
		
		//拼接参数
		String url = String.format(URL_GET_USERINFO, appId, openId);
		//发送请求
		String result = getRestTemplate().getForObject(url, String.class);
		
		//处理返回值
		QQUserInfo userInfo = null;

			try {
				userInfo = objectMapper.readValue(result, QQUserInfo.class);
				userInfo. setOpenId(openId);
			} catch (JsonProcessingException e) {
				throw new RuntimeException(" 获取用户信息失败!");
			}

		return userInfo;
	}

3、如何获得OpenId以及AppId

博客之QQ登录功能(一)_第6张图片

public class QQImpl extends AbstractOAuth2ApiBinding implements QQ{
	
	private static final String URL_GET_USERINFO = "https://graph.qq.com/user/get_user_info?pauth_consumer_key=%s&openid=%s";
	
	private static final String URL_GET_OPENID = "https://graph.qq.com/oauth2.0/me?access_token=%s";
	
	//外界赋值
	private String appId;
	//用户的唯一 标识,url
	private String openId;

	private ObjectMapper objectMapper = new ObjectMapper();

	public QQImpl(String accessToken, String appId) {
		//自动拼接一个参数
		super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER) ;
		//赋值appid 
		this.appId = appId;
		//赋值openid
		//通过url获得openid
		//拼接参数
		String url = String.format(URL_GET_OPENID, accessToken);
		
		//发送请求
		String result = getRestTemplate().getForObject(url, String.class);
		//处理返回值
		//callback( {"client_ id":"100550231", ”openid":"CDF1A28F8698E326D173DE17437FB098"} );
		result = StringUtils.replace(result, "callback( ","");
		result = StringUtils.replace(result, " );","");
		//{"client_ id": "100550231","openid": "CDF1A28F8698E326D173DE17437FB098"}
		OpenId id = null;
		
		try {
			id = objectMapper.readValue(result, OpenId.class);
		} catch (JsonProcessingException e) {
			throw new RuntimeException( "获取OpenId失败! ! ");
		}
		
		//赋值openid
		this.openId = id.getOpenid();
	}
	
	//获取用户信息
	@Override
	public QQUserInfo getUserInfo() {
		...
	}
	
}
package com.zzz.blog.social.qq.api;

public class OpenId {

	private String client_id;
	private String openid;

	//get/set
}

4、完成QQOAuth2Template

package com.zzz.blog.social.qq.template;

import ...

public class QQOAuth2Template extends OAuth2Template{

	public QQOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {
		super(clientId, clientSecret, authorizeUrl, accessTokenUrl);
		//使clientId、clientSecret可以拼接到一起
		setUseParametersForClientAuthentication(true);
	}

	//添加text/html
	@Override
	protected RestTemplate createRestTemplate() {
		RestTemplate template = super.createRestTemplate();
		template.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
		
		return template;
	}

	//把请求的格式按照qq的标准,做了一些自定义信息 自己处理请求 按&分割字符,分割后如下
	//access_token=FE04***** *****************CCE2  items[0]
	//expires_in=7776000 item[0]
	//refresh_token=88E4********* **************BE14 item[1]
	@Override
	protected AccessGrant postForAccessGrant(String accessTokenUrl, MultiValueMap<String, String> parameters) {
		
		String responseStr = getRestTemplate().postForObject(accessTokenUrl, parameters, String.class);
		
		//StringUtils.split只切割了一次,坑
		String[] items = StringUtils.split(responseStr, "&");
		String[] item = StringUtils.split(items[1], "&");
		
		String access_token =StringUtils.replace(items[0], "access_token=", "");
		Long expires_in = new Long(StringUtils.replace(item[0], "expires_in=", ""));
		String refresh_token = StringUtils.replace(item[1], "refresh_token=", "");
		
		
		return new AccessGrant(access_token, null, refresh_token, expires_in);
	}
	
}

5、完成QQAdapter与ServiceProvider

package com.zzz.blog.social.qq.connection;

import ...

public class QQAdapter implements ApiAdapter<QQ>{

	@Override
	public boolean test(QQ api) {
		// 始终为true
		return true;
	}

	@Override
	public void setConnectionValues(QQ api, ConnectionValues values) {
		//获取userinfo
		QQUserInfo userInfo = api. getUserInfo();
		
		//获取用户名称
		values.setDisplayName(userInfo.getNickname());
		//获取头像
		values.setImageUrl(userInfo.getFigureurl_qq_1());
		//获取个人主页
		values.setProfileUrl(null);
		//openid,用户在服务商中的唯一标识
		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) {
		// TODO Auto-generated method stub
		
	}

}
package com.zzz.blog.social.qq.connection;

import ...

public class QQServiceProvider extends AbstractOAuth2ServiceProvider<QQ>{

	//将用户导向认证服务器中的ur1地址,用户在该地址上进行授权
	private static final String URL_AUTHORIZE = "https://graph.qq.com/oauth2.0/authorize";
	//在获取令牌的时候,需要访问的url
	private static final String URL_ACCESSTOEKN = "https://graph.qq.com/oauth2.0/token";
	
	private String appId;
	
	//1-6
	public QQServiceProvider(String appId,String appSecret) {
		super(new QQOAuth2Template(appId, appSecret, URL_AUTHORIZE, URL_ACCESSTOEKN));
		this.appId = appId;
	}

	//7-8
	@Override
	public QQ getApi(String accessToken) {
		// TODO Auto-generated method stub
		return new QQImpl(accessToken, appId);
	}
	
}

6、完成QQConfig与ConnectionFactory

package com.zzz.blog.social.qq.connection;

import ...

public class QQConnectionFactory extends OAuth2ConnectionFactory<QQ>{

	public QQConnectionFactory(String providerId, String appId,String appSecret) {
		super(providerId, new QQServiceProvider(appId, appSecret), new QQAdapter());
	}

}
package com.zzz.blog.social.qq.config;

import ...

@Configuration
@EnableSocial
@Order(2)
public class QQConfig extends SocialConfigurerAdapter{

	@Autowired
	private BlogSecurityProperties blogSecurityProperties;

	//添加qq创建connection的工厂
	@Override
	public void addConnectionFactories(ConnectionFactoryConfigurer connectionFactoryConfigurer,
			Environment environment) {
		
		QQProperties qqConfig = blogSecurityProperties.getQqProperties();

		QQConnectionFactory qqConnectionFactory = new QQConnectionFactory(qqConfig.getProviderId(), qqConfig.getAppId(), qqConfig.getAppSecret());
		
		connectionFactoryConfigurer.addConnectionFactory(qqConnectionFactory);
	}
	
	//获取登陆人
	@Override
	public UserIdSource getUserIdSource() {
		return new AuthenticationNameUserIdSource();
	}
	
}

7、创建表以及创建操作表的类JdbcUsersConnectionRepository

package com.zzz.blog.social.qq.config;

import ...

@Configuration
@EnableSocial
@Order(1)
public class SocialConfig extends SocialConfigurerAdapter{
	
	@Autowired
	private DataSource dataSource;
	
	//登录之后,直接将QQ的数据保存在数据库
	@Override
	public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
		JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
		return repository;
	}	
	
	//改变拦截的请求
	
	//在注册的过程中,拿到了这个SpringSocial中的信息
	//业务完成之后,把用户的id传给了SpringSocial
	
	//打开ConnectController
}

找到socailJDBC表格式,在数据库中执行sql
博客之QQ登录功能(一)_第7张图片

8、改变拦截的请求

package com.zzz.blog.social.qq.config;

import ...

public class ZZZSpringSocialConfigurer extends SpringSocialConfigurer{

	private String filterProcessesUrl;

	public ZZZSpringSocialConfigurer(String filterProcessesUrl) {
		this.filterProcessesUrl = filterProcessesUrl;
	}

	
	//将默认的拦截改为qqLogin
	@Override
	protected <T> T postProcess(T object) {
		//获得filter
		SocialAuthenticationFilter filter = (SocialAuthenticationFilter)super.postProcess(object);
		//设置字段
		filter.setFilterProcessesUrl(filterProcessesUrl);
		return (T) filter;
	}
	
}
	//改变拦截的请求 /auth -> /qqLogin
	@Bean
	public SpringSocialConfigurer zzzSocialSecurityConfig() {
		String filterProcessesUrl = blogSecurityProperties.getQqProperties().getFilterProcessesUrl();
		ZZZSpringSocialConfigurer zzzSpringSocialConfigurer = new ZZZSpringSocialConfigurer(filterProcessesUrl);
		return zzzSpringSocialConfigurer;
	}

9、将Social中的配置生效到SpringSecurity中

在SocialConfig类中添加代码

	//在注册的过程中,拿到了这个SpringSocial中的信息
	//业务完成之后,把用户的id传给了SpringSocial
	@Bean
	public ProviderSignInUtils providerSignInUtils() {
		return new ProviderSignInUtils(connectionFactoryLocator, getUsersConnectionRepository(connectionFactoryLocator));
	}
	
	//打开ConnectController
	@Bean
	public ConnectController connectController(ConnectionFactoryLocator connectionFactoryLocator,ConnectionRepository connectionRepository) {
		return new ConnectController(connectionFactoryLocator, connectionRepository);
	}

添加apply配置socialconfig

package com.zzz.blog.config;

import ...

//安全配置类
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{

	//SpringSecurity加密方法返回值
	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
	
	@Autowired
	private SpringSocialConfigurer zzzSocialSecurityConfig;
	
	//做拦截
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		// 请求授权
		http.formLogin().and().authorizeRequests()
		//授权放行
		.antMatchers("/*.html").permitAll()
		//所有请求
		.anyRequest()
		//都需要身份认证
		.authenticated().and()
		//43、使用Layer打开select-mood子页面并配置SpringSecurity允许Iframe嵌入页面 
		.headers().frameOptions().disable().and()
		//跨站请求伪造的防护
		.csrf().disable()
		//添加我们所写的spring social配置
		.apply(zzzSocialSecurityConfig);
	}
	
}

10、创建Visitor实体并实现SocialUserDetailsService接口查找Visitor

package com.zzz.blog.domain;

import ...

@Entity
public class Visitor {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	private String username;
	private String password;
	private String image;
	
	protected Visitor() {
		
	}

	public Visitor(Long id, String username, String password, String image) {
		super();
		this.id = id;
		this.username = username;
		this.password = password;
		this.image = image;
	}

	//get/set
}
package com.zzz.blog.repository;

import ...

public interface VisitorRepository extends CrudRepository<Visitor, Long>{

}
@Component
public class SocialVisitorServiceImpl implements SocialUserDetailsService{

	@Autowired
	private VisitorRepository visitorRepository;
	
	@Autowired
	private PasswordEncoder passwordEncoder;
	
	@Override
	public SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException {

		//根据userId查找访客
		Optional<Visitor> optional = visitorRepository.findById(new Long(userId));
		Visitor visitor = optional.get();
		if (visitor == null) {
			throw new UsernameNotFoundException(userId);
		}
		
		return new SocialUser(visitor.getUsername(), passwordEncoder.encode(visitor.getPassword()), true, true, true, true, AuthorityUtils.commaSeparatedStringToAuthorityList("VISITOR"));
	}

}

11、实现ConnectionSignUp接口添加Visitor

package com.zzz.blog.social.qq.signup;

import ...

@Component
public class DemoConnectionSignUp implements ConnectionSignUp{

	@Autowired
	private VisitorService visitorService;
	
	//根据社交用户的信息,创建一个Visitor并返回唯一标识
	@Override
	public String execute(Connection<?> connection) {
		
		Visitor visitor = new Visitor(null, connection.getDisplayName(), "123456", connection.getImageUrl());
		
		visitor = visitorService.saveVisitory(visitor);
		
		return visitor.getId().toString();
	}

}
package com.zzz.blog.service;

import ...

@Service
public interface VisitorService {

	Visitor saveVisitory(Visitor visitor);

}
package com.zzz.blog.service;

import ...

@Service
public interface VisitorService {

	Visitor saveVisitory(Visitor visitor);

}
package com.zzz.blog.service;

import ...

@Component
public class VisitorServiceImpl implements VisitorService{

	@Autowired
	private VisitorRepository visitorRepository;
	
	@Override
	public Visitor saveVisitory(Visitor visitor) {
		return visitorRepository.save(visitor);
	}

}

SocialConfig添加setConnectionSignUp执行方法

	@Autowired
	private ConnectionSignUp connectionSignUp;
	
	//登录之后,直接将QQ的数据保存在数据库
	@Override
	public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
		JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
		repository.setConnectionSignUp(connectionSignUp);
		return repository;
	}

12、测试QQ登录

application.properties添加代码如下(修改端口):

server.port=80

login.html修改超链接代码如下:

						<a href="/qqLogin/callback.do" class="login100-social-item bg2">
							<i class="fa fa-qq">i>
						a>

修改C:\Windows\System32\drivers\etc\hosts文件

127.0.0.1        www.pinzhi365.com

这是别人提供的测试地址。我们也可以到QQ互联官网https://connect.qq.com/上注册用户,创建应用。
博客之QQ登录功能(一)_第8张图片
其中回调地址的写法:网站地址/拦截器拦截的路径/服务提供商。
创建完修改QQProperties类上的对应配置即可。
测试通过,控制台打印了添加visitor数据的sql。

你可能感兴趣的:(java,spring,boot)