微服务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;
    }
}
  • 如果XcUsernull,返回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 {
    	//...
    }
}

JwtAuthenticationTokenSpring 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"
	}
}
相关推荐
Z3r4y3 天前
【JWT安全】portswigger JWT labs 全解
web安全·网络安全·jwt·jwt安全·portswigger
那你为何对我三笑留情5 天前
二、Spring Boot集成Spring Security之实现原理
java·spring boot·spring·spring security
那你为何对我三笑留情6 天前
一、Spring Boot集成Spring Security之自动装配
java·spring boot·spring·spring security
Lsland..8 天前
JWT令牌技术介绍及使用
springboot·jwt
攸攸太上11 天前
Spring Security学习
java·学习·spring·spring security
stark张宇17 天前
lnmp - 登录技术方案设计与实现
php·jwt
missterzy1 个月前
JWT双令牌认证实现无感Token自动续约
jwt
_.Switch1 个月前
Django 后端架构开发:JWT 项目实践与Drf版本控制
数据库·后端·python·中间件·架构·django·jwt
春哥的魔法书2 个月前
JSON Web Token (JWT): 理解与应用
前端·json·状态模式·jwt·token
brief of gali2 个月前
Jdk17 jwt报错 java.lang.ClassNotFoundException: javax.xml.bind.DatatypeConverter
java·开发语言·springboot·jwt