├── .gitignore
├── README.md
├── nginx.conf
├── pom.xml
├── spring-security-oauth2-client
├── .gitignore
├── README.md
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── red
│ │ └── zyc
│ │ └── spring
│ │ └── security
│ │ └── oauth2
│ │ └── client
│ │ ├── SpringSecurityOauth2ClientApplication.java
│ │ ├── config
│ │ └── WebSecurityConfig.java
│ │ ├── controller
│ │ └── Oath2Controller.java
│ │ └── security
│ │ ├── CustomizedAccessDeniedHandler.java
│ │ ├── CustomizedAuthenticationEntryPoint.java
│ │ ├── CustomizedOauth2UserService.java
│ │ ├── Oauth2AuthenticationFailureHandler.java
│ │ └── Oauth2AuthenticationSuccessHandler.java
│ └── resources
│ └── application.yml
├── spring-security-oauth2-resourceserver
├── .gitignore
├── README.md
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── red
│ │ └── zyc
│ │ └── spring
│ │ └── security
│ │ └── oauth2
│ │ └── resourceserver
│ │ ├── JwtUtil.java
│ │ ├── SpringSecurityOauth2ResourceServerApplication.java
│ │ └── controller
│ │ └── UserController.java
│ └── resources
│ ├── application.yml
│ ├── key.private
│ └── key.public
└── web
├── favicon.ico
├── index.html
├── js
├── jquery-3.1.1.min.js
├── jquery.jsonview.css
└── jquery.jsonview.js
└── login.html
/.gitignore:
--------------------------------------------------------------------------------
1 | # IntelliJ project files
2 | .idea
3 | *.iml
4 |
5 | # java
6 | target
7 |
8 | # jrebel
9 | rebel.xml
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # spring-security-oauth2-demo
2 | 1. [集成GitHub和QQ社交登录](https://github.com/Allurx/spring-security-oauth2-demo/tree/master/spring-security-oauth2-client)
3 | 2. [使用jwt令牌访问受保护的资源](https://github.com/Allurx/spring-security-oauth2-demo/tree/master/spring-security-oauth2-resourceserver)
4 | 3. 搭建授权服务器([等待spring-security支持](https://spring.io/blog/2020/04/15/announcing-the-spring-authorization-server))
5 |
--------------------------------------------------------------------------------
/nginx.conf:
--------------------------------------------------------------------------------
1 | #user nobody;
2 | worker_processes 1;
3 |
4 | events {
5 | worker_connections 1024;
6 | }
7 |
8 | http {
9 |
10 | include mime.types;
11 | default_type application/octet-stream;
12 | sendfile on;
13 | keepalive_timeout 65;
14 |
15 | server {
16 | listen 80;
17 | server_name localhost;
18 | # 修改为你自己的web工程所在的目标
19 | root your web project root directory;
20 | index login.html;
21 | location /api {
22 | proxy_pass http://localhost:9000;
23 | }
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 | pom
6 |
7 | org.springframework.boot
8 | spring-boot-starter-parent
9 | 2.2.6.RELEASE
10 |
11 | red.zyc
12 | spring-security-oauth2-demo
13 | 0.0.1-SNAPSHOT
14 | spring-security-oauth2-demo
15 | spring-security-oauth2-demo
16 |
17 |
18 | 1.8
19 |
20 |
21 |
22 |
23 | org.projectlombok
24 | lombok
25 | true
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/spring-security-oauth2-client/.gitignore:
--------------------------------------------------------------------------------
1 | # IntelliJ project files
2 | .idea
3 | *.iml
4 |
5 | # java
6 | target
7 |
8 | # jrebel
9 | rebel.xml
--------------------------------------------------------------------------------
/spring-security-oauth2-client/README.md:
--------------------------------------------------------------------------------
1 | # 集成GitHub和QQ社交登录
2 | ## 准备
3 | 1. [本机安装nginx](http://nginx.org/en/download.html)
4 |
5 | 将[nginx.conf](https://github.com/Allurx/spring-security-oauth2-demo/blob/master/nginx.conf)文件添加到你的nginx配置下,修改root节点值为你本地[web工程](https://github.com/Allurx/spring-security-oauth2-demo/tree/master/web)所在的目录
6 | 2. [创建一个GitHub OAuth App](https://github.com/settings/developers)
7 | 3. [创建一个QQ网站应用](https://connect.qq.com)
8 | ## 开始
9 | 1. 修改配置文件
10 | 1. 将spring-security-oauth2-client工程`application.yml`文件中`registration.github`和`registration.qq`属性下的clientId、clientSecret替换为你自己GitHub OAuth App和QQ网站应用的值
11 | 2. 将[登录页](https://github.com/Allurx/spring-security-oauth2-demo/blob/master/web/login.html)script脚本中的data-appid值替换为你自己的QQ网站应用APP ID
12 | 2. 启动nginx和spring-security-oauth2-client工程,分别点击GitHub和QQ登录即可跳转三方授权登录页,最终在首页会显示已认证的用户和客户端信息。
13 | ## 参考
14 | [Spring-Security-OAuth2-Client原理](https://www.zyc.red/Spring/Security/OAuth2/OAuth2-Client/)
15 |
--------------------------------------------------------------------------------
/spring-security-oauth2-client/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | red.zyc
7 | spring-security-oauth2-demo
8 | 0.0.1-SNAPSHOT
9 |
10 | spring-security-oauth2-client
11 | 0.0.1-SNAPSHOT
12 | spring-security-oauth2-client
13 | spring-security-oauth2-client
14 |
15 |
16 | 1.8
17 |
18 |
19 |
20 |
21 | org.springframework.boot
22 | spring-boot-starter-oauth2-client
23 |
24 |
25 | org.springframework.boot
26 | spring-boot-starter-security
27 |
28 |
29 | org.springframework.boot
30 | spring-boot-starter-web
31 |
32 |
33 |
34 |
35 |
36 |
37 | org.springframework.boot
38 | spring-boot-maven-plugin
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/spring-security-oauth2-client/src/main/java/red/zyc/spring/security/oauth2/client/SpringSecurityOauth2ClientApplication.java:
--------------------------------------------------------------------------------
1 | package red.zyc.spring.security.oauth2.client;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | /**
7 | * @author zyc
8 | */
9 | @SpringBootApplication
10 | public class SpringSecurityOauth2ClientApplication {
11 |
12 | public static void main(String[] args) {
13 | SpringApplication.run(SpringSecurityOauth2ClientApplication.class, args);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/spring-security-oauth2-client/src/main/java/red/zyc/spring/security/oauth2/client/config/WebSecurityConfig.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package red.zyc.spring.security.oauth2.client.config;
18 |
19 | import lombok.SneakyThrows;
20 | import lombok.extern.slf4j.Slf4j;
21 | import org.springframework.context.annotation.Configuration;
22 | import org.springframework.http.HttpInputMessage;
23 | import org.springframework.http.HttpOutputMessage;
24 | import org.springframework.http.MediaType;
25 | import org.springframework.http.converter.FormHttpMessageConverter;
26 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
27 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
28 | import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider;
29 | import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient;
30 | import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
31 | import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
32 | import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
33 | import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
34 | import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
35 | import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
36 | import org.springframework.security.web.context.SecurityContextPersistenceFilter;
37 | import org.springframework.util.StreamUtils;
38 | import org.springframework.web.client.RestTemplate;
39 | import red.zyc.spring.security.oauth2.client.security.CustomizedAccessDeniedHandler;
40 | import red.zyc.spring.security.oauth2.client.security.CustomizedAuthenticationEntryPoint;
41 | import red.zyc.spring.security.oauth2.client.security.CustomizedOauth2UserService;
42 | import red.zyc.spring.security.oauth2.client.security.Oauth2AuthenticationFailureHandler;
43 | import red.zyc.spring.security.oauth2.client.security.Oauth2AuthenticationSuccessHandler;
44 |
45 | import java.nio.charset.StandardCharsets;
46 | import java.util.Arrays;
47 | import java.util.Map;
48 | import java.util.stream.Collectors;
49 |
50 | /**
51 | * @author zyc
52 | */
53 | @Slf4j
54 | @Configuration
55 | public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
56 |
57 | public WebSecurityConfig() {
58 | super(true);
59 | }
60 |
61 | @Override
62 | protected void configure(HttpSecurity http) throws Exception {
63 |
64 | http
65 | .authorizeRequests().anyRequest().authenticated().and()
66 |
67 | // 通过httpSession保存认证信息
68 | .addFilter(new SecurityContextPersistenceFilter())
69 |
70 | // 配置OAuth2登录认证
71 | .oauth2Login(oauth2LoginConfigurer -> oauth2LoginConfigurer
72 |
73 | // 认证成功后的处理器
74 | .successHandler(new Oauth2AuthenticationSuccessHandler())
75 |
76 | // 认证失败后的处理器
77 | .failureHandler(new Oauth2AuthenticationFailureHandler())
78 |
79 | // 登录请求url
80 | .loginProcessingUrl("/api/login/oauth2/code/*")
81 |
82 | // 配置授权服务器端点信息
83 | .authorizationEndpoint(authorizationEndpointConfig -> authorizationEndpointConfig
84 | // 授权端点的前缀基础url
85 | .baseUri("/api/oauth2/authorization"))
86 | // 配置获取access_token的端点信息
87 | .tokenEndpoint(tokenEndpointConfig -> tokenEndpointConfig.accessTokenResponseClient(oAuth2AccessTokenResponseClient()))
88 |
89 | // 配置获取userInfo的端点信息
90 | .userInfoEndpoint(userInfoEndpointConfig -> userInfoEndpointConfig.userService(new CustomizedOauth2UserService()))
91 | )
92 |
93 | // 配置匿名用户过滤器
94 | .anonymous().and()
95 |
96 | // 配置认证端点和未授权的请求处理器
97 | .exceptionHandling(exceptionHandlingConfigurer -> exceptionHandlingConfigurer
98 | .authenticationEntryPoint(new CustomizedAuthenticationEntryPoint())
99 | .accessDeniedHandler(new CustomizedAccessDeniedHandler()));
100 |
101 | }
102 |
103 | /**
104 | * qq获取access_token返回的结果是类似get请求参数的字符串,无法通过指定Accept请求头来使qq返回特定的响应类型,并且qq返回的access_token
105 | * 也缺少了必须的token_type字段(不符合oauth2标准的授权码认证流程),spring-security默认远程获取
106 | * access_token的客户端是{@link DefaultAuthorizationCodeTokenResponseClient},所以我们需要
107 | * 自定义{@link QqoAuth2AccessTokenResponseHttpMessageConverter}注入到这个client中来解析qq的access_token响应信息
108 | *
109 | * @return {@link DefaultAuthorizationCodeTokenResponseClient} 用来获取access_token的客户端
110 | * @see authorization-code-request规范
111 | * @see access-token-response规范
112 | * @see qq开发文档
113 | */
114 | private OAuth2AccessTokenResponseClient oAuth2AccessTokenResponseClient() {
115 | DefaultAuthorizationCodeTokenResponseClient client = new DefaultAuthorizationCodeTokenResponseClient();
116 | RestTemplate restTemplate = new RestTemplate(Arrays.asList(
117 | new FormHttpMessageConverter(),
118 |
119 | // 解析标准的AccessToken响应信息转换器
120 | new OAuth2AccessTokenResponseHttpMessageConverter(),
121 |
122 | // 解析qq的AccessToken响应信息转换器
123 | new QqoAuth2AccessTokenResponseHttpMessageConverter(MediaType.TEXT_HTML)));
124 | restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
125 | client.setRestOperations(restTemplate);
126 | return client;
127 | }
128 |
129 | /**
130 | * 自定义消息转换器来解析qq的access_token响应信息
131 | *
132 | * @see OAuth2AccessTokenResponseHttpMessageConverter#readInternal(java.lang.Class, org.springframework.http.HttpInputMessage)
133 | * @see OAuth2LoginAuthenticationProvider#authenticate(org.springframework.security.core.Authentication)
134 | */
135 | private static class QqoAuth2AccessTokenResponseHttpMessageConverter extends OAuth2AccessTokenResponseHttpMessageConverter {
136 |
137 | public QqoAuth2AccessTokenResponseHttpMessageConverter(MediaType... mediaType) {
138 | setSupportedMediaTypes(Arrays.asList(mediaType));
139 | }
140 |
141 | @SneakyThrows
142 | @Override
143 | protected OAuth2AccessTokenResponse readInternal(Class extends OAuth2AccessTokenResponse> clazz, HttpInputMessage inputMessage) {
144 |
145 | String response = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8);
146 |
147 | log.info("qq的AccessToken响应信息:{}", response);
148 |
149 | // 解析响应信息类似access_token=YOUR_ACCESS_TOKEN&expires_in=3600这样的字符串
150 | Map tokenResponseParameters = Arrays.stream(response.split("&")).collect(Collectors.toMap(s -> s.split("=")[0], s -> s.split("=")[1]));
151 |
152 | // 手动给qq的access_token响应信息添加token_type字段,spring-security会按照oauth2规范校验返回参数
153 | tokenResponseParameters.put(OAuth2ParameterNames.TOKEN_TYPE, "bearer");
154 | return this.tokenResponseConverter.convert(tokenResponseParameters);
155 | }
156 |
157 | @Override
158 | protected void writeInternal(OAuth2AccessTokenResponse tokenResponse, HttpOutputMessage outputMessage) {
159 | throw new UnsupportedOperationException();
160 | }
161 | }
162 |
163 |
164 | }
165 |
--------------------------------------------------------------------------------
/spring-security-oauth2-client/src/main/java/red/zyc/spring/security/oauth2/client/controller/Oath2Controller.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package red.zyc.spring.security.oauth2.client.controller;
18 |
19 | import org.springframework.security.core.annotation.AuthenticationPrincipal;
20 | import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
21 | import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
22 | import org.springframework.security.oauth2.core.user.OAuth2User;
23 | import org.springframework.web.bind.annotation.GetMapping;
24 | import org.springframework.web.bind.annotation.RequestMapping;
25 | import org.springframework.web.bind.annotation.RestController;
26 |
27 | /**
28 | * @author zyc
29 | */
30 | @RequestMapping("/api/oath2")
31 | @RestController
32 | public class Oath2Controller {
33 |
34 | /**
35 | * 获取当前认证的OAuth2用户信息,默认是保存在{@link javax.servlet.http.HttpSession}中的
36 | *
37 | * @param user OAuth2用户信息
38 | * @return OAuth2用户信息
39 | */
40 | @GetMapping("/user")
41 | public OAuth2User user(@AuthenticationPrincipal OAuth2User user) {
42 | return user;
43 | }
44 |
45 | /**
46 | * 获取当前认证的OAuth2客户端信息,默认是保存在{@link javax.servlet.http.HttpSession}中的
47 | *
48 | * @param oAuth2AuthorizedClient OAuth2客户端信息
49 | * @return OAuth2客户端信息
50 | */
51 | @GetMapping("/client")
52 | public OAuth2AuthorizedClient user(@RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient oAuth2AuthorizedClient) {
53 | return oAuth2AuthorizedClient;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/spring-security-oauth2-client/src/main/java/red/zyc/spring/security/oauth2/client/security/CustomizedAccessDeniedHandler.java:
--------------------------------------------------------------------------------
1 | package red.zyc.spring.security.oauth2.client.security;
2 |
3 |
4 | import org.springframework.security.access.AccessDeniedException;
5 | import org.springframework.security.web.access.AccessDeniedHandler;
6 | import org.springframework.security.web.access.ExceptionTranslationFilter;
7 |
8 | import javax.servlet.http.HttpServletRequest;
9 | import javax.servlet.http.HttpServletResponse;
10 | import java.io.IOException;
11 | import java.nio.charset.StandardCharsets;
12 |
13 |
14 | /**
15 | * 接口无权访问处理器
16 | * {@link ExceptionTranslationFilter#handleSpringSecurityException}
17 | *
18 | * @author zyc
19 | */
20 | public class CustomizedAccessDeniedHandler implements AccessDeniedHandler {
21 |
22 | @Override
23 | public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
24 | response.setCharacterEncoding(StandardCharsets.UTF_8.name());
25 | response.setContentType("text/plain");
26 | response.getWriter().write("用户未授权");
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/spring-security-oauth2-client/src/main/java/red/zyc/spring/security/oauth2/client/security/CustomizedAuthenticationEntryPoint.java:
--------------------------------------------------------------------------------
1 | package red.zyc.spring.security.oauth2.client.security;
2 |
3 |
4 | import org.springframework.security.core.AuthenticationException;
5 | import org.springframework.security.web.AuthenticationEntryPoint;
6 | import org.springframework.security.web.access.ExceptionTranslationFilter;
7 |
8 | import javax.servlet.http.HttpServletRequest;
9 | import javax.servlet.http.HttpServletResponse;
10 | import java.io.IOException;
11 | import java.nio.charset.StandardCharsets;
12 |
13 |
14 | /**
15 | * 接口需要特定的权限,但是当前用户是匿名用户或者是记住我的用户
16 | * {@link ExceptionTranslationFilter#handleSpringSecurityException}
17 | *
18 | * @author zyc
19 | */
20 | public class CustomizedAuthenticationEntryPoint implements AuthenticationEntryPoint {
21 |
22 | @Override
23 | public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
24 | response.setCharacterEncoding(StandardCharsets.UTF_8.name());
25 | response.setContentType("text/plain");
26 | response.getWriter().write("用户未认证");
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/spring-security-oauth2-client/src/main/java/red/zyc/spring/security/oauth2/client/security/CustomizedOauth2UserService.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package red.zyc.spring.security.oauth2.client.security;
18 |
19 | import com.fasterxml.jackson.core.type.TypeReference;
20 | import com.fasterxml.jackson.databind.ObjectMapper;
21 | import lombok.SneakyThrows;
22 | import lombok.extern.slf4j.Slf4j;
23 | import org.springframework.core.ParameterizedTypeReference;
24 | import org.springframework.http.RequestEntity;
25 | import org.springframework.http.ResponseEntity;
26 | import org.springframework.security.core.GrantedAuthority;
27 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
28 | import org.springframework.security.oauth2.client.registration.ClientRegistration;
29 | import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
30 | import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
31 | import org.springframework.security.oauth2.core.OAuth2AccessToken;
32 | import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
33 | import org.springframework.security.oauth2.core.user.OAuth2User;
34 | import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
35 | import org.springframework.web.client.RestTemplate;
36 | import org.springframework.web.util.UriComponentsBuilder;
37 |
38 | import java.util.LinkedHashSet;
39 | import java.util.Map;
40 | import java.util.Objects;
41 | import java.util.Set;
42 |
43 | /**
44 | * @author zyc
45 | */
46 | @Slf4j
47 | public class CustomizedOauth2UserService extends DefaultOAuth2UserService {
48 |
49 | private static final String QQ = "qq";
50 | private static final String QQ_OPEN_ID_URL = "https://graph.qq.com/oauth2.0/me";
51 |
52 | private final RestTemplate restTemplate = new RestTemplate();
53 |
54 | private final ObjectMapper objectMapper = new ObjectMapper();
55 |
56 | @SneakyThrows
57 | @Override
58 | public OAuth2User loadUser(OAuth2UserRequest userRequest) {
59 | ClientRegistration clientRegistration = userRequest.getClientRegistration();
60 | String registrationId = clientRegistration.getRegistrationId();
61 |
62 | // 获取qq用户信息的流程很奇葩需要我们自定义
63 | if (QQ.equals(registrationId)) {
64 |
65 | String tokenValue = userRequest.getAccessToken().getTokenValue();
66 |
67 | // openId请求
68 | RequestEntity> openIdRequest = RequestEntity.get(UriComponentsBuilder.fromUriString(QQ_OPEN_ID_URL).queryParam("access_token", tokenValue)
69 | .build()
70 | .toUri()).build();
71 |
72 | // openId响应
73 | ResponseEntity openIdResponse = restTemplate.exchange(openIdRequest, new ParameterizedTypeReference() {
74 | });
75 |
76 | log.info("qq的openId响应信息:{}", openIdResponse);
77 |
78 | // openId响应是类似callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} );这样的字符串
79 | String openId = extractQqOpenId(Objects.requireNonNull(openIdResponse.getBody()));
80 |
81 | // userInfo请求
82 | RequestEntity> userInfoRequest = RequestEntity.get(UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri())
83 | .queryParam("access_token", tokenValue)
84 | .queryParam("openid", openId)
85 | .queryParam("oauth_consumer_key", clientRegistration.getClientId())
86 | .build()
87 | .toUri()).build();
88 |
89 | // userInfo响应
90 | ResponseEntity userInfoResponse = restTemplate.exchange(userInfoRequest, new ParameterizedTypeReference() {
91 | });
92 |
93 | log.info("qq的userInfo响应信息:{}", userInfoResponse);
94 |
95 | String userNameAttributeName = clientRegistration.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();
96 | Map userAttributes = extractQqUserInfo(Objects.requireNonNull(userInfoResponse.getBody()));
97 | Set authorities = new LinkedHashSet<>();
98 | authorities.add(new OAuth2UserAuthority(userAttributes));
99 | OAuth2AccessToken token = userRequest.getAccessToken();
100 | for (String authority : token.getScopes()) {
101 | authorities.add(new SimpleGrantedAuthority("SCOPE_" + authority));
102 | }
103 | return new DefaultOAuth2User(authorities, userAttributes, userNameAttributeName);
104 | }
105 | return super.loadUser(userRequest);
106 | }
107 |
108 |
109 | /**
110 | * 提取qq的openId
111 | *
112 | * @param openIdResponse qq的openId响应字符串
113 | * @return qq的openId
114 | */
115 | @SneakyThrows
116 | private String extractQqOpenId(String openIdResponse) {
117 | String openId = openIdResponse.substring(openIdResponse.indexOf('(') + 1, openIdResponse.indexOf(')'));
118 | Map map = objectMapper.readValue(openId, new TypeReference