微服务OAuth 2.1扩展额外信息到JWT并解析(Spring Security 6)

文章目录

一、简介

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时,前两部分默认格式如下

json 复制代码
{
  "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实例。新建一个实现类,如下

java 复制代码
@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。

java 复制代码
@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,例如。

json 复制代码
{
	"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"
	}
}
相关推荐
积流成海4 天前
JWT 解析:三段结构、过期时间换算与常见误区
jwt·盘子工具站
FungLeo11 天前
成为全栈·Flutter App 篇·Refresh Token 旋转与并发 401:安全存储与会话代次
flutter·jwt·移动安全·身份认证·token刷新·成为全栈
豆角焖肉11 天前
SpringBoot+JWT 实现前后端分离登录鉴权
java·开发语言·spring boot·jwt·前后端分离·登录认证
CSharp精选营15 天前
Blazor 里 JWT 过期,用户正下单就被踢
认证·jwt·webassembly·blazor·令牌刷新
白帽攻防录22 天前
SRC 挖洞:WSO2 JWT 算法混淆绕过深度复盘,CVE-2026-5430 不支持的算法怎么变成管理员
网络·算法·安全·网络安全·jwt
EatFan23 天前
Go语言全栈实战:基于 Gin + Vue + JWT + RBAC 从零搭建前后端分离权限管理系统
vue.js·golang·go·vue·gin·jwt·rbac
szephyr1 个月前
前端鉴权实战:Token 存哪、怎么刷新、怎么防 XSS 与 CSRF
鉴权·xss·csrf·jwt·token
2601_962297251 个月前
Authlib 0.13通用Python认证授权库wheel安装包(支持Python 2/3)
python·jwt·oauth2.0·authlib·openidconnect
山甫aa1 个月前
【从零开始的 Web 后端学习】令牌技术一篇搞定(JWT 登录认证保姆级)
后端·学习·spring·web·jwt
FungLeo2 个月前
成为全栈·Node 后端篇·认证方案:JWT 还是 Session
node.js·jwt·session·认证方案