网站授权QQ登录
- 首先需要去QQ互联申请应用
- 填写网站的相关信息,以及回调地址,需要进行审核。
- 申请流程暂时不说了,百度一下挺多申请失败案例的解决方案的,你懂的现在越来越严格了,甚至一个错别字都不让有。
QQ登录的完整流程
-
用户点击QQ登录
用户在你的前端页面点击QQ登录按钮,发送请求到后端。 -
重定向到QQ授权页面
后端也可以直接重定向到QQ的授权页面
,也可以将授权页面的地址返回给前端, 由前端将用户重定向到QQ的授权页面,授权页面的地址通常是一个URL,类似于:https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=YOUR_APP_ID&redirect_uri=YOUR_REDIRECT_URI&state=YOUR_STATE
其中:
YOUR_APP_ID
:你在QQ开放平台注册应用时获得的APP ID。YOUR_REDIRECT_URI
:你在QQ开放平台设置的回调URL。YOUR_STATE
:一个随机字符串,用于防止CSRF攻击。
-
用户授权
用户在QQ的授权页面点击同意授权。 -
QQ重定向到回调URL
授权成功后,QQ会将用户重定向到你设置的redirectUri
,并在回调地址的查询参数中加上一个code
参数和原先的state
。 -
前端获取
code
并验证state
前端从回调地址中解析出code
参数。同时,验证返回的state
是否与最初发送的state
一致,以确保这不是一个CSRF攻击。
state参数也可以交给后端进行验证
这里的回调地址就是QQ互联上面写的回调地址,前端会在这个回调地址的页面发送请求给后端,同时携带code和state参数(这两个参数从回调地址里面取出来的)
-
前端向后端发送登录请求并携带code和state参数
前端发起请求,将code和state
发送到后端的/callback
接口。 -
后端获取Access Token
后端使用code
,向QQ服务器请求访问令牌(Access Token)。这通常涉及到一个POST请求到https://graph.qq.com/oauth2.0/token
,带有code
、YOUR_APP_ID
、YOUR_APP_KEY
(你的应用密钥)和YOUR_REDIRECT_URI
作为参数。 -
后端获取OpenID
使用Access Token,后端可以向QQ服务器请求OpenID,这是一个代表QQ用户唯一标识的值。 -
后端获取用户信息
后端使用Access Token和OpenID,请求QQ服务器以获取用户的基本信息。 -
创建或登录用户
后端可以使用从QQ获取的用户信息来:- 检查数据库中是否已经有一个与此QQ账户关联的用户。
- 如果是,则登录该用户。
- 如果不是,则创建一个新用户,并与该QQ账户关联。
-
返回结果到前端
后端可以返回一个令牌(如JWT)或其他标识已登录用户的信息到前端。 -
前端处理登录状态
前端根据后端的响应处理用户的登录状态,例如保存JWT,显示用户的信息等。
代码示例
1. 添加依赖
在pom.xml
中添加相关的依赖:
<!-- Spring Boot Web Starter: 提供了创建web应用所需要的所有必要依赖,包括内嵌的Tomcat服务器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Apache HttpClient: 一个流行的库,用于处理HTTP请求 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<!-- Jackson Databind: 用于将Java对象与JSON数据进行序列化和反序列化 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
2. 配置文件
在application.properties
添加:
qq.appId=YOUR_APP_ID
qq.appKey=YOUR_APP_KEY
qq.redirectUri=http://yourdomain.com/auth/qq/callback
3. 实现Service
创建一个QQAuthService
用于处理与QQ的交互。
@Service
public class QQAuthService {
// 从application.properties中读取配置值
@Value("${qq.appId}")
private String appId;
@Value("${qq.appKey}")
private String appKey;
@Value("${qq.redirectUri}")
private String redirectUri;
// 创建一个可关闭的HTTP客户端,用于发送请求
private final CloseableHttpClient httpClient = HttpClients.createDefault();
/**
* 获取Access Token
*
* @param code 从QQ回调URL中获得的授权码
* @return 返回Access Token
* @throws IOException 处理HTTP请求可能会出现的IO异常
*/
public String getAccessToken(String code) throws IOException {
// 构建获取Access Token的URL
String url = "https://graph.qq.com/oauth2.0/token?grant_type=authorization_code&client_id="
+ appId + "&client_secret=" + appKey + "&code=" + code + "&redirect_uri=" + redirectUri;
// 创建一个HttpGet请求
HttpGet httpGet = new HttpGet(url);
// 执行该请求,并获取响应
CloseableHttpResponse response = httpClient.execute(httpGet);
// 将响应内容转换为字符串
String responseStr = EntityUtils.toString(response.getEntity());
// 解析响应内容,提取access_token
String accessToken = responseStr.split("&")[0].split("=")[1];
return accessToken;
}
/**
* 获取OpenID
*
* @param accessToken Access Token
* @return 返回OpenID
* @throws IOException 处理HTTP请求可能会出现的IO异常
*/
public String getOpenId(String accessToken) throws IOException {
// 构建获取OpenID的URL
String url = "https://graph.qq.com/oauth2.0/me?access_token=" + accessToken;
// 创建一个HttpGet请求
HttpGet httpGet = new HttpGet(url);
// 执行该请求,并获取响应
CloseableHttpResponse response = httpClient.execute(httpGet);
// 将响应内容转换为字符串
String responseStr = EntityUtils.toString(response.getEntity());
// 解析响应内容,提取openid
String openId = responseStr.substring(responseStr.lastIndexOf(":\"") + 2, responseStr.lastIndexOf("\"}"));
return openId;
}
/**
* 获取用户信息
*
* @param accessToken Access Token
* @param openId OpenID
* @return 返回用户信息的JSON字符串
* @throws IOException 处理HTTP请求可能会出现的IO异常
*/
public String getUserInfo(String accessToken, String openId) throws IOException {
// 构建获取用户信息的URL
String url = "https://graph.qq.com/user/get_user_info?access_token=" + accessToken + "&oauth_consumer_key="
+ appId + "&openid=" + openId;
// 创建一个HttpGet请求
HttpGet httpGet = new HttpGet(url);
// 执行该请求,并获取响应
CloseableHttpResponse response = httpClient.execute(httpGet);
// 将响应内容转换为字符串
String responseStr = EntityUtils.toString(response.getEntity());
// 直接返回用户信息的JSON字符串
return responseStr;
}
}
4. 创建Controller
@RestController
@RequestMapping("/auth/qq")
public class QQAuthController {
@Autowired
private QQAuthService qqAuthService;
/**
* 重定向用户到QQ登录页面。
*
* @param response HttpServletResponse对象,用于发送重定向。
* @param session HttpSession对象,用于存储state。
* @throws IOException 如果重定向失败。
*/
@GetMapping("/login")
public void qqLogin(HttpServletResponse response, HttpSession session) throws IOException {
// 生成并存储一个唯一的state值
String state = UUID.randomUUID().toString();
session.setAttribute("qq_oauth_state", state);
// 构建QQ授权的URL
String url = "https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id="
+ appId + "&redirect_uri=" + URLEncoder.encode(redirectUri, "UTF-8") + "&state=" + state;
// 当你在移动端上请求登录时,可以在请求中带上display=mobile参数
// 系统就会跳转到移动版的QQ登录页面,为用户提供更好的体验
if ("mobile".equals(display)) {
url += "&display=mobile";
}
// 重定向到QQ授权页面
response.sendRedirect(url);
}
/**
* 处理QQ授权的回调请求。
*
* @param code QQ授权返回的code。
* @param state QQ授权返回的state。
* @param session HttpSession对象,用于验证state。
* @return 用户信息或错误消息。
*/
@GetMapping("/callback")
public String qqCallback(@RequestParam String code, @RequestParam String state, HttpSession session) {
// 检查返回的state是否与存储的一致
String storedState = (String) session.getAttribute("qq_oauth_state");
if (storedState == null || !storedState.equals(state)) {
return "Error: state does not match";
}
try {
// 获取并使用AccessToken和OpenID
String accessToken = qqAuthService.getAccessToken(code);
String openId = qqAuthService.getOpenId(accessToken);
String userInfo = qqAuthService.getUserInfo(accessToken, openId);
// 这里可以进行用户注册或登录操作
// 这里可以根据用户是新用户还是老用户决定是注册后登录还是直接登录
// 然后返回登录用户的信息
return userInfo;
} catch (IOException e) {
return "Error occurred during QQ auth: " + e.getMessage();
}
}
}