继续上篇文章,来完善下如果使用Facebook注册一个应用 今天通过官网有查到了许多关于spring boot social相关的文章,但是进去之后都只能看到"deprecated" 今天我就来完善下这块知识的短缺
首先在社交平台注册一个应用 (Register your application) 比如注册Facebook Twitter等应用,可以去官网看看教程,这个跟国内的微博之类的是类似的,这里不做简单介绍了。而且每个不同平台的版本升级,界面变化也是蛮快的,建议多去官网看看。
编辑应用的配置添加配置属性 (Edit application.properties) 我这里修改了application.properties添加了下面的配置,其他平台的类似
1
2
3
facebook.appKey= xxx
facebook.appSecret= xxx
Copy 添加Spring Social依赖库 (Add Spring Social Dependencies) 依据上篇文章已经添加的,这里的话,还需要在添加几个如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependency>
<groupId> org.springframework.social</groupId>
<artifactId> spring-social-core</artifactId>
<version> 1.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId> org.springframework.social</groupId>
<artifactId> spring-social-security</artifactId>
<version> 1.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId> org.springframework.security</groupId>
<artifactId> spring-security-core</artifactId>
<version> 5.0.6.RELEASE</version>
</dependency>
Copy 最终总共需要添加的依赖如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<dependency>
<groupId> org.springframework.social</groupId>
<artifactId> spring-social-facebook</artifactId>
<version> 2.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId> com.fasterxml.jackson.core</groupId>
<artifactId> jackson-core</artifactId>
</dependency>
<dependency>
<groupId> org.springframework.security</groupId>
<artifactId> spring-security-crypto</artifactId>
</dependency>
<dependency>
<groupId> org.springframework.social</groupId>
<artifactId> spring-social-core</artifactId>
<version> 1.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId> org.springframework.social</groupId>
<artifactId> spring-social-security</artifactId>
<version> 1.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId> org.springframework.security</groupId>
<artifactId> spring-security-core</artifactId>
<version> 5.0.6.RELEASE</version>
</dependency>
Copy 飙写代码开始
1、创建一个@Configuration类:
1
2
3
4
5
6
@Configuration
@EnableSocial
public class SocialConfig implements SocialConfigurer {
}
Copy 2、在SocialConfig中添加已注册的ConnectionFactoryLocator,这里使用的是Facebook,如果是其他平台的可以继续添加
1
2
3
4
5
6
7
@Override
public void addConnectionFactories ( ConnectionFactoryConfigurer connectionFactoryConfigurer ,
Environment environment ) {
connectionFactoryConfigurer . addConnectionFactory ( new FacebookConnectionFactory (
environment . getProperty ( "facebook.appKey" ), environment . getProperty ( "facebook.appSecret" )));
}
Copy 3、在SocialConfig中添加UsersConnectionRepository以在所有用户之间保留连接数据
1
2
3
4
5
6
7
8
9
@Override
public UsersConnectionRepository getUsersConnectionRepository ( ConnectionFactoryLocator connectionFactoryLocator ) {
JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository ( dataSource ,
connectionFactoryLocator , Encryptors . noOpText ());
repository . setConnectionSignUp ( new SimpleConnectionSignUp ());
return repository ;
}
Copy 4、在SocialConfig中添加getUserIdSource以识别当前用户的连接
1
2
3
4
5
@Override
public UserIdSource getUserIdSource () {
return new AuthenticationNameUserIdSource ();
}
Copy 5、在SocialConfig中添加一个或多个表示当前用户API绑定的请求范围的bean。这里添加的是Facebook:
1
2
3
4
5
6
7
8
9
10
11
@Bean
@Scope ( value = "request" , proxyMode = ScopedProxyMode . INTERFACES )
public Facebook facebook ( ConnectionRepository repository ) {
Connection < Facebook > connection = repository . findPrimaryConnection ( Facebook . class );
if ( connection != null ) {
return connection . getApi ();
}
return null ;
}
Copy 6、在SocialConfig中添加ProviderSignInController,允许用户使用其提供者帐户登录
1
2
3
4
5
6
7
@Bean
public ProviderSignInController providerSignInController ( ConnectionFactoryLocator connectionFactoryLocator ,
UsersConnectionRepository usersConnectionRepository ) {
return new ProviderSignInController ( connectionFactoryLocator , usersConnectionRepository ,
new SpringSecuritySignInAdapter ());
}
Copy 创建视图 创建一个"链接"视图,允许用户使用其提供者帐户登录,视图分别如下 connect/connectFacebook.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
< html lang = "en" xmlns:th = "http://www.thymeleaf.org" >
< head >
< meta charset = "UTF-8" >
< title > 连接到Facebook</ title >
</ head >
< body >
< h3 > 连接到Facebook</ h3 >
< form th:action = "@{/signin/facebook}" method = "POST" >
< input type = "hidden" name = "scope" value = "email,user_friends" />
< input type = "hidden" name = "redirect_uri" value = "http://localhost:8080/connected/facebook" />
< div class = "formInfo" >
< p > 你还没有连接到Facebook,点击连接按钮连接到Facebook账户</ p >
</ div >
< p >
< button type = "submit" > 连接到Facebook</ button >
</ p >
</ form >
</ body >
</ html >
Copy connected/connectFacebook.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
< html lang = "en" xmlns:th = "http://www.thymeleaf.org" >
< head >
< meta charset = "UTF-8" >
< title > 已连接到Facebook</ title >
</ head >
< body >
< h3 > 已连接到Facebook</ h3 >
< div class = "formInfo" >
< p > 你已连接到Facebook,点击连接按钮连接到Facebook账户</ p >
</ div >
< h3 > Facebook好友: < span th:text = "${friendsNum}" ></ span ></ h3 >
< ul >
< li th:each = "friend : ${friends}" >
< img th:src = "'http://graph.facebook.com/' + ${friend.id} + '/picture'" align = "middle" />
< span th:text = "${friend.name}" > name</ span >
</ li >
</ ul >
< h3 > 邮箱信息: < span th:text = "${email}" ></ span ></ h3 >
</ body >
</ html >
Copy 调用API 最后,@Inject引用API绑定到需要它们的对象。检索当前用户的facebook好友的示例@Controller如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Controller
public class SocialController {
private Facebook facebook ;
private ConnectionRepository connectionRepository ;
@Inject
public SocialController ( Facebook facebook , ConnectionRepository connectionRepository ) {
this . facebook = facebook ;
this . connectionRepository = connectionRepository ;
}
@RequestMapping ( value = "/connect/facebook" , method = RequestMethod . GET )
public ModelAndView connectFacebook ( Model model ) {
ModelAndView mv = new ModelAndView ( "connect/connectFacebook" );
mv . addObject ( "email" , "[email protected]" );
return mv ;
}
@RequestMapping ( value = "/connected/facebook" , method = RequestMethod . GET )
public String connectedFacebook ( Model model ) {
Connection < Facebook > connection = this . connectionRepository . findPrimaryConnection ( Facebook . class );
if ( connection == null ) {
return "redirect:/connect/facebook" ;
}
List < Reference > friends = facebook . friendOperations (). getFriends ();
String [] fields = { "id" , "email" , "first_name" , "last_name" };
User userProfile = facebook . fetchObject ( "me" , User . class , fields );
System . out . println ( friends . size ());
model . addAttribute ( "friends" , friends );
model . addAttribute ( "friendsNum" , friends . size ());
model . addAttribute ( "email" , userProfile . getEmail ());
return "connect/connectedFacebook" ;
}
}
Copy 经过以上几个流程最终就完成了相关的配置,可以启动项目进行测试了链接了。
仔细看下面这部分的内容,目前网上使用官网的例子经常遇到的错误及一些疑惑,经过本次的调整,一下问题都会得到解决。可以下载本实例进行测试
1
2
3
git clone https://github.com/durban89/spring-demo.git
git checkout v1.0.5
Copy 第一个疑惑 我们没有添加signin开头的路由为什么能请求的到呢,在项目启动的时候,其实这些已经跟在依赖包内做了处理了,这个在项目启动的时候让我豁然开朗
1
2
3
4
5
6
2018-07-18 11:20:55.878 INFO 66154 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/signin/{providerId}],methods=[POST]}" onto public org.springframework.web.servlet.view.RedirectView org.springframework.social.connect.web.ProviderSignInController.signIn( java.lang.String,org.springframework.web.context.request.NativeWebRequest)
2018-07-18 11:20:55.879 INFO 66154 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/signin/{providerId}],methods=[GET],params=[oauth_token]}" onto public org.springframework.web.servlet.view.RedirectView org.springframework.social.connect.web.ProviderSignInController.oauth1Callback( java.lang.String,org.springframework.web.context.request.NativeWebRequest)
2018-07-18 11:20:55.880 INFO 66154 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/signin/{providerId}],methods=[GET],params=[code]}" onto public org.springframework.web.servlet.view.RedirectView org.springframework.social.connect.web.ProviderSignInController.oauth2Callback( java.lang.String,java.lang.String,org.springframework.web.context.request.NativeWebRequest)
2018-07-18 11:20:55.903 INFO 66154 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/signin/{providerId}],methods=[GET],params=[error]}" onto public org.springframework.web.servlet.view.RedirectView org.springframework.social.connect.web.ProviderSignInController.oauth2ErrorCallback( java.lang.String,java.lang.String,java.lang.String,java.lang.String,org.springframework.web.context.request.NativeWebRequest)
2018-07-18 11:20:55.904 INFO 66154 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/signin/{providerId}],methods=[GET]}" onto public org.springframework.web.servlet.view.RedirectView org.springframework.social.connect.web.ProviderSignInController.canceledAuthorizationCallback()
Copy 第二个疑惑 1
2
org.springframework.web.client.ResourceAccessException: I/O error on POST request for "https://graph.facebook.com/v2.5/oauth/access_token" : Operation timed out ( Connection timed out) ; nested exception is java.net.ConnectException: Operation timed out ( Connection timed out)
Copy 为什么我在国内配置完之后,也跳转到了社交平台,但是返回的时候报下面的错误
这个问题的解决方案如下 pom.xml文件添加类似如下的配置
1
2
3
<jvmArguments> -Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=1087 -Dhttp.proxyHost=127.0.0.1 -Dhttp.proxyPort=1087 -Dhttps.proxySet=true
</jvmArguments>
Copy 添加后的结果如下
1
2
3
4
5
6
7
8
9
10
11
12
13
<build>
<plugins>
<plugin>
<groupId> org.springframework.boot</groupId>
<artifactId> spring-boot-maven-plugin</artifactId>
<configuration>
<jvmArguments> -Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=1087 -Dhttp.proxyHost=127.0.0.1 -Dhttp.proxyPort=1087 -Dhttps.proxySet=true
</jvmArguments>
</configuration>
</plugin>
</plugins>
</build>
Copy 其实就是做了代理的设置
第三个疑问 1
2
org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar [ select userId from UserConnection where providerId = ? and providerUserId = ?] ; nested exception is org.h2.jdbc.JdbcSQLException: Table "USERCONNECTION" not found; SQL statement:
Copy 为什么会提示我数据库表没有呢,不应该是自动为我建立吗,经过网上的查找,确实是没有自动帮你建立,这个需要自己去添加,可能这块的业务逻辑不能耦合的太紧密的原因吧 解决方案在这里 config/MainConfig.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
@ComponentScan ( basePackages = "com.gowhich.springdemo" , excludeFilters = { @Filter ( Configuration . class ) })
@PropertySource ( "classpath:application.properties" )
public class MainConfig {
@Bean ( destroyMethod = "shutdown" )
public DataSource dataSource () {
EmbeddedDatabaseFactory factory = new EmbeddedDatabaseFactory ();
factory . setDatabaseName ( "springdemo" );
factory . setDatabaseType ( EmbeddedDatabaseType . H2 );
factory . setDatabasePopulator ( databasePopulator ());
return factory . getDatabase ();
}
private DatabasePopulator databasePopulator () {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator ();
populator . addScript (
new ClassPathResource ( "JdbcUsersConnectionRepository.sql" , JdbcUsersConnectionRepository . class ));
return populator ; }
}
Copy 同时别忘记创建JdbcUsersConnectionRepository.sql这个文件,放在src/main/resources目录下就可以。然后在运行项目就会自动创建数据库表了。 JdbcUsersConnectionRepository.sql 内容如下,github也有的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
create table UserConnection
(
userId varchar ( 255 ) not null ,
providerId varchar ( 255 ) not null ,
providerUserId varchar ( 255 ),
rank int not null ,
displayName varchar ( 255 ),
profileUrl varchar ( 512 ),
imageUrl varchar ( 512 ),
accessToken varchar ( 1024 ) not null ,
secret varchar ( 255 ),
refreshToken varchar ( 255 ),
expireTime bigint ,
primary key ( userId , providerId , providerUserId )
);
create unique index UserConnectionRank on UserConnection ( userId , providerId , rank );
create table UserProfile
(
userId varchar ( 255 ) not null ,
email varchar ( 255 ),
firstName varchar ( 255 ),
lastName varchar ( 255 ),
name varchar ( 255 ),
username varchar ( 255 ),
primary key ( userId )
);
create unique index UserProfilePK on UserProfile ( userId );
Copy 第四个疑问 1
2
java.lang.IllegalStateException: No user is currently signed in
Copy 这个问题主要原因是官网的例子中使用的是TheadLocal,通过进程的方法共享数据,结果这个数据在再次获取的时候丢失了,具体原因我只查到在使用Spring Cloud的时候回遇到,没查到在使用Spring Boot的时候也会有,但是问题已解决,就是替换下,我这里的实例使用的是官网最新的文档中介绍的,在setCurrentUser和getCurrentUser的逻辑中做了修改,替换了TheadLocal。 修改的地方大概有几个地方,如下
获取用户数据的地方
1
2
3
4
5
@Override
public UserIdSource getUserIdSource () {
return new AuthenticationNameUserIdSource ();
}
Copy 注册用户数据的地方,改用了SecurityContextHolder的方式
1
2
3
4
5
6
7
@Bean
public ProviderSignInController providerSignInController ( ConnectionFactoryLocator connectionFactoryLocator ,
UsersConnectionRepository usersConnectionRepository ) {
return new ProviderSignInController ( connectionFactoryLocator , usersConnectionRepository ,
new SpringSecuritySignInAdapter ());
}
Copy 和
1
2
3
4
5
6
7
8
9
10
public final class SpringSecuritySignInAdapter implements SignInAdapter {
public String signIn ( String userId , Connection <?> connection , NativeWebRequest request ) {
SecurityContextHolder . getContext ()
. setAuthentication ( new UsernamePasswordAuthenticationToken ( userId , null , null ));
return null ;
}
}
Copy 第五个疑问 1
2
org.springframework.social.UncategorizedApiException: ( #12) bio field is deprecated for versions v2.8 and higher
Copy 这个是Facebook对应库版本不兼容的问题,这个网上还是很好找的 问题主要出在这里
1
2
3
4
5
List < Reference > friends = facebook . friendOperations (). getFriends ();
String [] fields = { "id" , "email" , "first_name" , "last_name" };
User userProfile = facebook . fetchObject ( "me" , User . class , fields );
Copy 官网的例子一般是只给出了这个
1
2
List < Reference > friends = facebook . friendOperations (). getFriends ();
Copy 当我们想要调用其他数据的时候 比如用户信息"facebook.userOperations()“这样调用的时候就会报错,实际上就是不支持了,要换,换成如下的方式
1
2
3
String [] fields = { "id" , "email" , "first_name" , "last_name" };
User userProfile = facebook . fetchObject ( "me" , User . class , fields );
Copy OK,埰坑完毕。期待我下次的分享,埰坑踩到无法呼吸。
实践项目地址
1
2
3
git clone https://github.com/durban89/spring-demo.git
git checkout v1.0.5
Copy