微服务OAuth 2.1扩展额外信息到JWT并解析(Spring Security 6)
文章目录
- 一、简介
- 二、重写UserDetailsService
- 三、Controller解析JWT获取用户信息
- 四、后记
一、简介
Version | |
---|---|
Java | 17 |
SpringCloud | 2023.0.0 |
SpringBoot | 3.2.1 |
Spring Authorization Server | 1.2.1 |
Spring Security | 6.2.1 |
mysql | 8.2.0 |
Spring Authorization Server
使用JWT
时,前两部分默认格式如下
{
"kid": "33bd1cad-62a6-4415-89a6-c2c816f3d3b1",
"alg": "RS256"
}
{
"sub": "XcWebApp",
"aud": "XcWebApp",
"nbf": 1707373072,
"iss": "http://localhost:63070/auth",
"exp": 1707380272,
"iat": 1707373072,
"jti": "62e885c5-6b3f-49a2-aa10-b2e872a52b33"
}
现在我们要把用户信息也扩展到JWT
,最简便的方法就是将用户信息写成JSON
字符串替换sub
字段。其中用户信息由xc_user
数据库表存储。
二、重写UserDetailsService
注释掉原来的UserDetailsService
实例。新建一个实现类,如下
@Component
@Slf4j
public class UserServiceImpl implements UserDetailsService {
@Autowired
XcUserMapper xcUserMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据username查询数据库
XcUser xcUser = xcUserMapper.selectOne(new LambdaQueryWrapper<XcUser>()
.eq(XcUser::getUsername, username));
//用户不存在,返回null
if (xcUser == null){
return null;
}
//用户存在,拿到密码,封装成UserDetails,密码对比由框架进行
String password = xcUser.getPassword();
//扩展用户信息
xcUser.setPassword(null);
String userInfo = JSON.toJSONString(xcUser);
UserDetails userDetails = User.withUsername(userInfo).password(password).authorities("read").build();
return userDetails;
}
}
- 如果
XcUser
为null
,返回null
,这里处理了用户不存在的情况。 - 如果用户存在,则获取密码,放到
UserDetails
中,密码比对过程我们不关心,由框架完成。如果使用了加密算法,这里的password
应该是密文。 - 我们可以把用户信息转成JSON字符串放入
withUsername
。这样框架生成JWT时就会把用户信息也放进去。
是的,你没有猜错,UserDetails
只要返回密码框架就能比对成功,不需要再返回username
。
三、Controller解析JWT获取用户信息
写一个工具类,通过Security ContextHolder.getContext()
上下文获取Authentication
然后解析JWT
。
@Slf4j
public class SecurityUtil {
public static XcUser getUser(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication instanceof JwtAuthenticationToken) {
JwtAuthenticationToken jwtAuth = (JwtAuthenticationToken) authentication;
Map<String, Object> tokenAttributes = jwtAuth.getTokenAttributes();
Object sub = tokenAttributes.get("sub");
return JSON.parseObject(sub.toString(), XcUser.class);
}
return null;
}
@Data
public static class XcUser implements Serializable {
//...
}
}
JwtAuthenticationToken
是Spring Authorization Server
的一个类,可以帮助我们解析JWT
。
四、后记
从SecurityContextHolder.getContext().getAuthentication()
解析我们放进去的XcUser的方法不止一种,将其打印出来就可以看出,有多个地方包含了XcUser
,例如。
{
"authenticated": true,
"authorities": [{
"authority": "SCOPE_all"
}],
"credentials": {
"audience": ["XcWebApp"],
"claims": {
"sub": "{\"createTime\":\"2022-09-28 08:32:03\",\"id\":\"48\",\"name\":\"系统管理员\",\"sex\":\"1\",\"status\":\"1\",\"username\":\"admin\",\"utype\":\"101003\"}",
"aud": ["XcWebApp"],
"nbf": "2024-02-08T14:01:16Z",
"scope": ["all"],
"iss": "http://localhost:63070/auth",
"exp": "2024-02-08T16:01:16Z",
"iat": "2024-02-08T14:01:16Z",
"jti": "91df8f15-2096-4e03-a927-877b51bf5997"
},
"expiresAt": "2024-02-08T16:01:16Z",
"headers": {
"kid": "e14df18d-1c95-441d-80d6-8457f3ceba9e",
"alg": "RS256"
},
"id": "91df8f15-2096-4e03-a927-877b51bf5997",
"issuedAt": "2024-02-08T14:01:16Z",
"issuer": "http://localhost:63070/auth",
"notBefore": "2024-02-08T14:01:16Z",
"subject": "{\"createTime\":\"2022-09-28 08:32:03\",\"id\":\"48\",\"name\":\"系统管理员\",\"sex\":\"1\",\"status\":\"1\",\"username\":\"admin\",\"utype\":\"101003\"}",
"tokenValue": "eyJraWQiOiJlMTRkZjE4ZC0xYzk1LTQ0MWQtODBkNi04NDU3ZjNjZWJhOWUiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ7XCJjcmVhdGVUaW1lXCI6XCIyMDIyLTA5LTI4IDA4OjMyOjAzXCIsXCJpZFwiOlwiNDhcIixcIm5hbWVcIjpcIuezu-e7n-euoeeQhuWRmFwiLFwic2V4XCI6XCIxXCIsXCJzdGF0dXNcIjpcIjFcIixcInVzZXJuYW1lXCI6XCJhZG1pblwiLFwidXR5cGVcIjpcIjEwMTAwM1wifSIsImF1ZCI6IlhjV2ViQXBwIiwibmJmIjoxNzA3NDAwODc2LCJzY29wZSI6WyJhbGwiXSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo2MzA3MC9hdXRoIiwiZXhwIjoxNzA3NDA4MDc2LCJpYXQiOjE3MDc0MDA4NzYsImp0aSI6IjkxZGY4ZjE1LTIwOTYtNGUwMy1hOTI3LTg3N2I1MWJmNTk5NyJ9.NZ3f_Pkh871L1c8XkV2PxHfn17pWjaRvBd9HQQTRJhfFvNBN7zoh2riumpfVUj_xVmnCadVX3YE4ARxc0CuiV1QyVDpFnmKiuvWdsRVV9NF5Kkb67CGtF2zw1l2gFdSDFWAwOq1SemtogHX5a4XFF2kG6kx9ZOSL4EoiQMMhUOwfY6mL9Zdcgq_E28kfnrAk__q84rgo9JPvj3jH6cm_oS63-tNXZYdClDG61DHS4Bw7cswUfVf_bcI_a8kXfiM8SzCnROvxe1hU2dM88qxUgkI1GPlrtZhe9z7113XP7ilaPo2UknCFh_OSbfUeUDmP1GpTaspfGmnHhBXLQyG06Q"
},
"details": {
"remoteAddress": "192.168.222.1"
},
"name": "{\"createTime\":\"2022-09-28 08:32:03\",\"id\":\"48\",\"name\":\"系统管理员\",\"sex\":\"1\",\"status\":\"1\",\"username\":\"admin\",\"utype\":\"101003\"}",
"principal": {
"audience": ["XcWebApp"],
"claims": {
"sub": "{\"createTime\":\"2022-09-28 08:32:03\",\"id\":\"48\",\"name\":\"系统管理员\",\"sex\":\"1\",\"status\":\"1\",\"username\":\"admin\",\"utype\":\"101003\"}",
"aud": ["XcWebApp"],
"nbf": "2024-02-08T14:01:16Z",
"scope": ["all"],
"iss": "http://localhost:63070/auth",
"exp": "2024-02-08T16:01:16Z",
"iat": "2024-02-08T14:01:16Z",
"jti": "91df8f15-2096-4e03-a927-877b51bf5997"
},
"expiresAt": "2024-02-08T16:01:16Z",
"headers": {
"kid": "e14df18d-1c95-441d-80d6-8457f3ceba9e",
"alg": "RS256"
},
"id": "91df8f15-2096-4e03-a927-877b51bf5997",
"issuedAt": "2024-02-08T14:01:16Z",
"issuer": "http://localhost:63070/auth",
"notBefore": "2024-02-08T14:01:16Z",
"subject": "{\"createTime\":\"2022-09-28 08:32:03\",\"id\":\"48\",\"name\":\"系统管理员\",\"sex\":\"1\",\"status\":\"1\",\"username\":\"admin\",\"utype\":\"101003\"}",
"tokenValue": "eyJraWQiOiJlMTRkZjE4ZC0xYzk1LTQ0MWQtODBkNi04NDU3ZjNjZWJhOWUiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ7XCJjcmVhdGVUaW1lXCI6XCIyMDIyLTA5LTI4IDA4OjMyOjAzXCIsXCJpZFwiOlwiNDhcIixcIm5hbWVcIjpcIuezu-e7n-euoeeQhuWRmFwiLFwic2V4XCI6XCIxXCIsXCJzdGF0dXNcIjpcIjFcIixcInVzZXJuYW1lXCI6XCJhZG1pblwiLFwidXR5cGVcIjpcIjEwMTAwM1wifSIsImF1ZCI6IlhjV2ViQXBwIiwibmJmIjoxNzA3NDAwODc2LCJzY29wZSI6WyJhbGwiXSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo2MzA3MC9hdXRoIiwiZXhwIjoxNzA3NDA4MDc2LCJpYXQiOjE3MDc0MDA4NzYsImp0aSI6IjkxZGY4ZjE1LTIwOTYtNGUwMy1hOTI3LTg3N2I1MWJmNTk5NyJ9.NZ3f_Pkh871L1c8XkV2PxHfn17pWjaRvBd9HQQTRJhfFvNBN7zoh2riumpfVUj_xVmnCadVX3YE4ARxc0CuiV1QyVDpFnmKiuvWdsRVV9NF5Kkb67CGtF2zw1l2gFdSDFWAwOq1SemtogHX5a4XFF2kG6kx9ZOSL4EoiQMMhUOwfY6mL9Zdcgq_E28kfnrAk__q84rgo9JPvj3jH6cm_oS63-tNXZYdClDG61DHS4Bw7cswUfVf_bcI_a8kXfiM8SzCnROvxe1hU2dM88qxUgkI1GPlrtZhe9z7113XP7ilaPo2UknCFh_OSbfUeUDmP1GpTaspfGmnHhBXLQyG06Q"
},
"token": {
"audience": ["XcWebApp"],
"claims": {
"sub": "{\"createTime\":\"2022-09-28 08:32:03\",\"id\":\"48\",\"name\":\"系统管理员\",\"sex\":\"1\",\"status\":\"1\",\"username\":\"admin\",\"utype\":\"101003\"}",
"aud": ["XcWebApp"],
"nbf": "2024-02-08T14:01:16Z",
"scope": ["all"],
"iss": "http://localhost:63070/auth",
"exp": "2024-02-08T16:01:16Z",
"iat": "2024-02-08T14:01:16Z",
"jti": "91df8f15-2096-4e03-a927-877b51bf5997"
},
"expiresAt": "2024-02-08T16:01:16Z",
"headers": {
"kid": "e14df18d-1c95-441d-80d6-8457f3ceba9e",
"alg": "RS256"
},
"id": "91df8f15-2096-4e03-a927-877b51bf5997",
"issuedAt": "2024-02-08T14:01:16Z",
"issuer": "http://localhost:63070/auth",
"notBefore": "2024-02-08T14:01:16Z",
"subject": "{\"createTime\":\"2022-09-28 08:32:03\",\"id\":\"48\",\"name\":\"系统管理员\",\"sex\":\"1\",\"status\":\"1\",\"username\":\"admin\",\"utype\":\"101003\"}",
"tokenValue": "eyJraWQiOiJlMTRkZjE4ZC0xYzk1LTQ0MWQtODBkNi04NDU3ZjNjZWJhOWUiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ7XCJjcmVhdGVUaW1lXCI6XCIyMDIyLTA5LTI4IDA4OjMyOjAzXCIsXCJpZFwiOlwiNDhcIixcIm5hbWVcIjpcIuezu-e7n-euoeeQhuWRmFwiLFwic2V4XCI6XCIxXCIsXCJzdGF0dXNcIjpcIjFcIixcInVzZXJuYW1lXCI6XCJhZG1pblwiLFwidXR5cGVcIjpcIjEwMTAwM1wifSIsImF1ZCI6IlhjV2ViQXBwIiwibmJmIjoxNzA3NDAwODc2LCJzY29wZSI6WyJhbGwiXSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo2MzA3MC9hdXRoIiwiZXhwIjoxNzA3NDA4MDc2LCJpYXQiOjE3MDc0MDA4NzYsImp0aSI6IjkxZGY4ZjE1LTIwOTYtNGUwMy1hOTI3LTg3N2I1MWJmNTk5NyJ9.NZ3f_Pkh871L1c8XkV2PxHfn17pWjaRvBd9HQQTRJhfFvNBN7zoh2riumpfVUj_xVmnCadVX3YE4ARxc0CuiV1QyVDpFnmKiuvWdsRVV9NF5Kkb67CGtF2zw1l2gFdSDFWAwOq1SemtogHX5a4XFF2kG6kx9ZOSL4EoiQMMhUOwfY6mL9Zdcgq_E28kfnrAk__q84rgo9JPvj3jH6cm_oS63-tNXZYdClDG61DHS4Bw7cswUfVf_bcI_a8kXfiM8SzCnROvxe1hU2dM88qxUgkI1GPlrtZhe9z7113XP7ilaPo2UknCFh_OSbfUeUDmP1GpTaspfGmnHhBXLQyG06Q"
},
"tokenAttributes": {
"sub": "{\"createTime\":\"2022-09-28 08:32:03\",\"id\":\"48\",\"name\":\"系统管理员\",\"sex\":\"1\",\"status\":\"1\",\"username\":\"admin\",\"utype\":\"101003\"}",
"aud": ["XcWebApp"],
"nbf": "2024-02-08T14:01:16Z",
"scope": ["all"],
"iss": "http://localhost:63070/auth",
"exp": "2024-02-08T16:01:16Z",
"iat": "2024-02-08T14:01:16Z",
"jti": "91df8f15-2096-4e03-a927-877b51bf5997"
}
}