Spring Security 集成 QQ 登录与 JWT 认证
# Spring Security 集成 QQ 登录与 JWT 认证
前言
在基于 Spring Boot 的项目中,集成 QQ 登录和 JWT 认证可以实现更加现代化的无状态认证流程。本节将详细介绍如何配置 QQ 登录、保存用户信息到数据库,以及生成 JWT 返回给前端。实现过程中我们将使用 Spring Security、OAuth2、MyBatis 和 MySQL。
# 1. 项目依赖配置
在 Spring Boot 项目中引入以下依赖,包括 Spring Security OAuth2 客户端、JWT 库、MyBatis 和 MySQL 驱动。
pom.xml 配置:
<dependencies>
<!-- Spring Security OAuth2 Client,用于集成 OAuth2 登录功能 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<!-- JWT 库,用于生成和解析 Token -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- Spring Security 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- MyBatis 依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
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
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
# 2. 配置 QQ 登录的 OAuth2 客户端
在 Spring Boot 中,OAuth2 客户端的配置通常放在 application.yml 或 application.properties 中。
application.yml 配置:
spring:
security:
oauth2:
client:
registration:
qq:
client-id: your-qq-client-id # QQ 应用的 Client ID,需要替换为你自己的
client-secret: your-qq-client-secret # QQ 应用的 Client Secret,需要替换为你自己的
redirect-uri: "{baseUrl}/login/oauth2/code/qq" # 回调地址,填写你的应用地址
authorization-grant-type: authorization_code # 授权类型,通常为 authorization_code
client-name: QQ
scope: get_user_info # QQ 登录需要的权限
provider:
qq:
authorization-uri: https://graph.qq.com/oauth2.0/authorize
token-uri: https://graph.qq.com/oauth2.0/token
user-info-uri: https://graph.qq.com/user/get_user_info
user-name-attribute: openid # 从用户信息中获取 OpenID 的字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
参数说明:
- client-id:QQ 应用的 Client ID,需要在 QQ 互联平台上注册应用后获取。
- client-secret:QQ 应用的 Client Secret,同样需要在 QQ 互联平台上获取。
- redirect-uri:回调地址,QQ 登录成功后跳转回你应用的路径,通常格式为
http://your-app-url/login/oauth2/code/qq。 - scope:定义了 QQ 登录所需的权限,
get_user_info是必须的。
# 3. MyBatis 配置与数据库设计
配置 MyBatis,并设计用户表来存储 QQ 登录返回的用户信息。
application.yml MyBatis 配置:
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.project.model
1
2
3
2
3
SQL 示例:创建用户表
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
openid VARCHAR(50) NOT NULL UNIQUE,
nickname VARCHAR(100),
avatar VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
User 实体类:
public class User {
private Long id;
private String openid;
private String nickname;
private String avatar;
// Getters and Setters
}
1
2
3
4
5
6
7
2
3
4
5
6
7
UserMapper.xml MyBatis 映射文件:
<mapper namespace="com.example.project.mapper.UserMapper">
<select id="findByOpenId" parameterType="String" resultType="com.example.project.model.User">
SELECT * FROM users WHERE openid = #{openid}
</select>
<insert id="insertUser" parameterType="com.example.project.model.User">
INSERT INTO users (openid, nickname, avatar) VALUES (#{openid}, #{nickname}, #{avatar})
</insert>
<update id="updateUser" parameterType="com.example.project.model.User">
UPDATE users SET nickname = #{nickname}, avatar = #{avatar} WHERE openid = #{openid}
</update>
</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
UserMapper 接口:
import com.example.project.model.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper {
User findByOpenId(String openid);
void insertUser(User user);
void updateUser(User user);
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 4. 自定义 OAuth2UserService:处理用户信息与数据库交互
自定义 OAuth2UserService 实现,用于获取 QQ 登录返回的用户信息并与数据库交互。
代码示例:自定义 OAuth2UserService
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
@Autowired
private UserMapper userMapper;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
OAuth2User oAuth2User = delegate.loadUser(userRequest);
// 从 QQ 返回的用户信息中提取必要的信息
Map<String, Object> attributes = oAuth2User.getAttributes();
String openId = (String) attributes.get("openid");
String nickname = (String) attributes.get("nickname");
String avatar = (String) attributes.get("figureurl");
// 检查用户是否存在
User user = userMapper.findByOpenId(openId);
if (user == null) {
// 如果用户不存在,创建新用户并保存到数据库
user = new User();
user.setOpenid(openId);
user.setNickname(nickname);
user.setAvatar(avatar);
userMapper.insertUser(user);
} else {
// 如果用户存在,更新用户信息
user.setNickname(nickname);
user.setAvatar(avatar);
userMapper.updateUser(user);
}
// 返回封装的 OAuth2User 对象
return new DefaultOAuth2User(oAuth2User.getAuthorities(), attributes, "openid");
}
}
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
41
42
43
44
45
46
47
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
41
42
43
44
45
46
47
重要的 API 说明:
DefaultOAuth2UserService:Spring Security 提供的默认 OAuth2UserService 实现,用于加载用户信息。loadUser(OAuth2UserRequest userRequest):自定义的用户加载逻辑,通过此方法获取并处理第三方登录的用户信息。getAttributes():获取第三方返回的用户信息 Map,其中包含了 openid、nickname、avatar 等关键信息。
# 5. 登录成功处理:生成 JWT 并返回给前端
用户登录成功后,生成 JWT 并返回给前端。
代码示例:自定义登录成功处理器
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Component
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Autowired
private JwtTokenProvider jwtTokenProvider;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
OAuth2AuthenticationToken oAuth2AuthenticationToken = (OAuth2AuthenticationToken) authentication;
String username = oAuth2AuthenticationToken.getPrincipal().getAttribute("nickname");
// 生成 JWT
String token = jwtTokenProvider.generateToken(username);
// 构建返回的 JSON 数据
Map<String, String> tokenResponse = new HashMap<>();
tokenResponse.put("token", token);
// 返回 JSON 数据
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_OK);
response.get
Writer().write(new ObjectMapper().writeValueAsString(tokenResponse));
}
}
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
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
重要的 API 说明:
AuthenticationSuccessHandler:Spring Security 提供的接口,用于处理认证成功后的操作。OAuth2AuthenticationToken:封装了 OAuth2 用户认证信息的对象。ObjectMapper:Jackson 提供的工具类,用于将对象转换为 JSON 格式。
# 6. Spring Security 配置:集成 QQ 登录与 JWT 认证
配置 Spring Security 来整合 QQ 登录和 JWT 认证。
代码示例:Spring Security 配置
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/login", "/oauth2/**").permitAll()
.anyRequest().authenticated()
.and()
.oauth2Login()
.loginPage("/login") // 配置自定义登录页面
.userInfoEndpoint()
.userService(new CustomOAuth2UserService()) // 配置自定义的 OAuth2UserService
.and()
.successHandler(new CustomAuthenticationSuccessHandler()); // 配置自定义的成功处理器,返回 JWT
}
}
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
重要的配置项说明:
csrf().disable():禁用 CSRF 防护,因为我们使用的是无状态的 Token 认证。oauth2Login():启用 OAuth2 登录功能,用于处理第三方登录(如 QQ 登录)。loginPage("/login"):指定自定义登录页面的路径。如果未配置,自带的登录页面会自动加载。userService(new CustomOAuth2UserService()):配置自定义的OAuth2UserService,用于加载和处理用户信息。successHandler(new CustomAuthenticationSuccessHandler()):配置自定义的成功处理器,用于登录成功后生成 JWT 并返回给前端。
# 7. JWT 工具类的实现
代码示例:JWT 工具类
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class JwtTokenProvider {
private final String jwtSecret = "mySecretKey";
private final long jwtExpirationMs = 86400000; // 24小时
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
重要的 API 说明:
setSubject(String subject):设置 JWT 的主体,通常是用户名或用户 ID。setIssuedAt(Date issuedAt):设置 JWT 的签发时间。setExpiration(Date expiration):设置 JWT 的过期时间。signWith(SignatureAlgorithm algorithm, String secret):使用指定的签名算法和密钥对 JWT 进行签名。
# 8. 完整流程总结
- 用户点击 QQ 登录按钮,跳转到 QQ 授权页面。
- 用户在 QQ 页面进行授权,授权成功后跳转到回调页面。
- Spring Security 通过 OAuth2 客户端获取到用户信息,并将其保存到数据库。
- 登录成功后,生成 JWT 并以 JSON 格式返回给前端。
- 前端将 JWT 存储在本地(如
localStorage),并在后续请求中携带 JWT 进行身份验证。
编辑此页 (opens new window)
上次更新: 2024/12/28, 18:32:08