Spring OAuth2资源服务:如何将Jwt转换为自定义MyUser认证主体
当前已配置Spring Security的SecurityFilterChain,OAuth2资源服务器部分配置如下:
.oauth2ResourceServer(resourceServerConfigurer -> { resourceServerConfigurer.jwt(jwtConfigurer -> { final var jwtAuthenticationConverter = new JwtAuthenticationConverter(); jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(myJwtGrantedAuthoritiesMapper); jwtConfigurer.jwtAuthenticationConverter(jwtAuthenticationConverter); }); }) .authorizeHttpRequests(requests -> requests.anyRequest().authenticated());
当前配置运行正常,可通过SecurityContextHolder.getAuthentication().getPrincipal()获取Jwt实例,再通过jwt.getClaimAsString("given_name")等方法获取用户信息。
希望将Jwt中的部分数据映射到自定义的MyUser类,使认证主体变为MyUser实例,避免直接操作Jwt对象。请问如何实现将JwtAuthenticationToken/Jwt转换为以MyUser为主体的认证令牌?
核心思路是自定义JWT认证转换器,替换默认的JwtAuthenticationConverter,在转换器中完成Jwt到MyUser的映射,并生成以MyUser为主体的认证令牌。
步骤1:定义自定义MyUser类
先创建封装用户信息的MyUser类,按需添加需要从JWT中提取的字段:
public class MyUser { private String userId; private String givenName; private String familyName; // 构造方法 public MyUser(String userId, String givenName, String familyName) { this.userId = userId; this.givenName = givenName; this.familyName = familyName; } // 省略getter/setter方法 }
步骤2:自定义JWT认证转换器
继承JwtAuthenticationConverter并重写createJwtAuthenticationToken方法,在方法中完成Jwt到MyUser的转换:
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import java.util.Collection; import java.util.function.Function; public class CustomJwtAuthenticationConverter extends JwtAuthenticationConverter { private final Function<Jwt, Collection<GrantedAuthority>> authoritiesConverter; public CustomJwtAuthenticationConverter(Function<Jwt, Collection<GrantedAuthority>> authoritiesConverter) { this.authoritiesConverter = authoritiesConverter; } @Override protected JwtAuthenticationToken createJwtAuthenticationToken(Jwt jwt, Collection<GrantedAuthority> authorities) { // 从JWT中提取字段,构造MyUser实例 String userId = jwt.getClaimAsString("sub"); String givenName = jwt.getClaimAsString("given_name"); String familyName = jwt.getClaimAsString("family_name"); MyUser myUser = new MyUser(userId, givenName, familyName); // 返回以MyUser为主体的JwtAuthenticationToken return new JwtAuthenticationToken(jwt, authorities, myUser); } }
注:
JwtAuthenticationToken的第三个参数即为认证主体,传入MyUser实例即可,同时保留原始Jwt对象作为第一个参数,方便后续可能的额外操作。
步骤3:替换Security配置中的转换器
在原有的SecurityFilterChain配置中,使用自定义转换器替换默认实现:
.oauth2ResourceServer(resourceServerConfigurer -> { resourceServerConfigurer.jwt(jwtConfigurer -> { // 实例化自定义转换器,传入已有的权限转换器 final var customJwtConverter = new CustomJwtAuthenticationConverter(myJwtGrantedAuthoritiesMapper); jwtConfigurer.jwtAuthenticationConverter(customJwtConverter); }); }) .authorizeHttpRequests(requests -> requests.anyRequest().authenticated());
步骤4:验证结果
配置完成后,通过SecurityContextHolder获取的认证主体即为MyUser实例,可直接调用其方法获取信息:
MyUser currentUser = (MyUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); String userName = currentUser.getGivenName();
可选:自定义认证令牌类
如果不想使用JwtAuthenticationToken,可以自定义继承AbstractAuthenticationToken的令牌类:
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.authentication.AbstractAuthenticationToken; import java.util.Collection; public class MyUserAuthenticationToken extends AbstractAuthenticationToken { private final MyUser principal; private final Jwt jwt; public MyUserAuthenticationToken(MyUser principal, Jwt jwt, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.principal = principal; this.jwt = jwt; setAuthenticated(true); } @Override public Object getCredentials() { return jwt; } @Override public Object getPrincipal() { return principal; } }
然后在自定义转换器的createJwtAuthenticationToken方法中返回该令牌即可:
@Override protected JwtAuthenticationToken createJwtAuthenticationToken(Jwt jwt, Collection<GrantedAuthority> authorities) { MyUser myUser = new MyUser(jwt.getClaimAsString("sub"), jwt.getClaimAsString("given_name"), jwt.getClaimAsString("family_name")); return new MyUserAuthenticationToken(myUser, jwt, authorities); }
内容的提问来源于stack exchange,提问作者ETLJ

