Spring cloud oauth2 + jwt 自定义 check_token 返回信息

2,952 阅读4分钟

Spring cloud oauth2 集成jwt后,通常我们获取用户信息只能获取到username,往往在实际开发我们需要的用户信息是userId或者说其他信息

check_token流程简述

我们就需要了解Spring cloud oauth2是如何check_token的,不懂得小伙伴可以移步到 spring cloud oauth2 check token源码解析

  • 了解check_token流程得小伙伴就知道check_token得第一步是被OAuth2AuthenticationProcessingFilter过滤器拦截,过滤器会拿到request中token信息封装成Authentication对象,通过OAuth2AuthenticationManager调用tokenService获取到OAuth2Authentication对象
    OAuth2Authentication对象中包含了客户端以及用户信息还有一些request请求信息
    然后对客户端信息进行一系列得验证,最后当然是存入到SecurityContextHolder.Context中

  • tokenService又做了一些什么事嘞,tokenService会去tokenStore中获取到token信息,不同得tokenStore会有不同得获取方式,比如RedisTokenStore顾名思义就是从redis中获取token信息,那么JwtTokenStore当然是解密jwt token得到token信息

  • 得到token信息后,会通过JwtAccessTokenConverter中extractAuthentication()方法去调用到 DefaultAccessTokenConverter.extractAuthentication()去获取用户信息以及将token信息中得客户端信息封装成OAuth2Request对象,最后在将OAuth2Request对象和用户信息汇总起来构建出一个OAuth2Authentication对象返回

  • DefaultAccessTokenConverter.extractAuthentication()是如何获取得用户信息嘞?说到这我们得压轴类也应该出现了,没错他就是DefaultUserAuthenticationConverter,从类名就可以看出来它负责用户信息转换,DefaultAccessTokenConverter.extractAuthentication()其实就是去调用了DefaultUserAuthenticationConverter获取用户信息,DefaultUserAuthenticationConverter会去获取到token信息中得用户名,然后判断userDetailsService是否为空,不为空则通过userDetailsService去查询用户信息,为空得话直接将username作为用户信息,这下终于水落石出了

DefaultUserAuthenticationConverter源码解析

上面我们说到DefaultUserAuthenticationConverter是压轴类,那么我们就来看一看它主要做了些什么事, 它最终要的方法就是extractAuthentication方法,通过方法我们可以看出来为什么默认情况下只有username返回了

public Authentication extractAuthentication(Map<String, ?> map) {
   if (map.containsKey(USERNAME)) {
      Object principal = map.get(USERNAME);
      Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
      if (userDetailsService != null) {
         UserDetails user = userDetailsService.loadUserByUsername((String) map.get(USERNAME));
         authorities = user.getAuthorities();
         principal = user;
      }
      return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
   }
   return null;
}

private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
   if (!map.containsKey(AUTHORITIES)) {
      return defaultAuthorities;
   }
   Object authorities = map.get(AUTHORITIES);
   if (authorities instanceof String) {
      return AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities);
   }
   if (authorities instanceof Collection) {
      return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils
            .collectionToCommaDelimitedString((Collection<?>) authorities));
   }
   throw new IllegalArgumentException("Authorities must be either a String or a Collection");
}

扯了那么多的蛋,我们还是没有讲到怎么去自定义用户信息返回,没错你猜对了,下面我们就来讲一讲怎么自定义用户信息返回

自定义用户信息返回

思路讲解:原有的方法中判断userDetailsService是否为null,是不是我们将userDetailsService注入进来就可以实现了自定义用户信息返回,没错!!!
但是你想过没,JWT优势就是可以包含用户信息从而减少服务器存储token信息的压力,如果这样子去做,那何必用jwt嘞
我的思路是去继承DefaultUserAuthenticationConverter并实现UserAuthenticationConverter,重写它的extractAuthentication()方法

注意!注意!注意!,我的思路可实现是因为我在生成token的时候,用TokenEnhancer对token进行了增强,所以我的JWT token中包含了我需要的用户信息,我只需要在extractAuthentication方法中将信息出去来,封装成一个对象,放入到Authentication中就可以了

怎么用TokenEnhancer去增强token,可以参考spring cloud oauth 集成 JWT 之 token 增强这篇文章

接下来我们开始撸代码吧

  • 自定义LwUserAuthenticationConverter继承DefaultUserAuthenticationConverter并实现UserAuthenticationConverter,重写它的extractAuthentication()方法
public class LwUserAuthenticationConverter extends DefaultUserAuthenticationConverter implements UserAuthenticationConverter {

    private Collection<? extends GrantedAuthority> defaultAuthorities = new ArrayList<>();

    @Override
    public Authentication extractAuthentication(Map<String, ?> map) {
        if (map.containsKey(USERNAME)) {
            Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
            Map principal = (Map) map.get(SecurityConstants.USER_INFO);
            String userId = MapUtil.getStr(principal, "userId");
            String username = MapUtil.getStr(principal, "username");
            String password = MapUtil.getStr(principal, "password");
            String openid = MapUtil.getStr(principal, "openid");
            String phone = MapUtil.getStr(principal, "phone");
            String avatar = MapUtil.getStr(principal, "avatar");
            LoginUser loginUser = new LoginUser(username, password, userId, phone, avatar, openid, authorities);
            return new UsernamePasswordAuthenticationToken(loginUser, "N/A", authorities);
        }
        return null;
    }


    private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
        if (!map.containsKey(AUTHORITIES)) {
            return defaultAuthorities;
        }
        Object authorities = map.get(AUTHORITIES);
        if (authorities instanceof String) {
            return AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities);
        }
        if (authorities instanceof Collection) {
            return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils
                    .collectionToCommaDelimitedString((Collection<?>) authorities));
        }
        throw new IllegalArgumentException("Authorities must be either a String or a Collection");
    }


}
  • 将JwtAccessTokenConverter类中的userTokenConverter属性替换成我们自定义的LwUserAuthenticationConverter
  • 将TokenStore改为JwtTokenStore,并将JwtAccessTokenConverter注入到JwtTokenStore中
/**
 * 存储策略
 */
@Bean
public TokenStore tokenStore() {
    return new JwtTokenStore(jwtAccessTokenConverter());
}


/**
 * JWT转换器
 */
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
    DefaultAccessTokenConverter defaultConverter = new DefaultAccessTokenConverter();
    defaultConverter.setUserTokenConverter(lwUserAuthenticationConverter());

    JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    converter.setAccessTokenConverter(defaultConverter);
    converter.setSigningKey("123456");
    return converter;
}

/**
 * 自定义用户身份验证转换器
 * @param
 */
@Bean
public LwUserAuthenticationConverter lwUserAuthenticationConverter(){
    return new LwUserAuthenticationConverter();
}
  • 在资源服务器配置文件中设置存储策略

/**
 * 注入token存取策略
 * 开启无状态模式
 *
 * @param resource
 */
@Override
public void configure(ResourceServerSecurityConfigurer resource) throws Exception {
    resource.tokenStore(tokenStore)
            .stateless(true);
}

这样我们就大功告成了,

效果演示

没错,我们已经通过拿到定制的用户信息了

image.png