DMOZ中文网站分类目录-免费收录各类优秀网站的中文网站目录.
  • DmozDir.org
DMOZ目录快速登录入口-免费收录各类优秀网站的中文网站目录.由人工编辑,并提供网站分类目录检索及地区分类目录检索,是站长免费推广网站的有力平台!

SpringCloud Alibaba微服务实战二十一

  • SpringCloud Alibaba微服务实战二十一

  • 已被浏览: 70 次2020年12月16日    来源:  https://blog.csdn.net/jianzhang11/article/details/109831635
  • 今天内容主要是解决一位粉丝提的问题 如何在jwt中添加用户的额外信息并在资源服务器中获取这些数据。 涉及的知识点有以下三个如何在返回的jwt中添加自定义数据 如何在jwt中添加用户的额外数据 比如用户id、手机号码 如何在资源服务器

    今天内容主要是解决一位粉丝提的问题:如何在jwt中添加用户的额外信息并在资源服务器中获取这些数据。

    涉及的知识点有以下三个:

    • 如何在返回的jwt中添加自定义数据

    • 如何在jwt中添加用户的额外数据,比如用户id、手机号码

    • 如何在资源服务器中取出这些自定义数据

    下面我们分别来看如何实现。

    如何在返回的jwt中添加自定义数据

    这个问题比较简单,只要按照如下两步即可:

    1. 编写自定义token增强器

    package?com.javadaily.auth.security;
    
    import?org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
    import?org.springframework.security.oauth2.common.OAuth2AccessToken;
    import?org.springframework.security.oauth2.provider.OAuth2Authentication;
    import?org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
    
    import?java.util.HashMap;
    import?java.util.Map;
    
    /**
    ?*?<p>
    ?*?<code>JwtTokenEnhancer</code>
    ?*?</p>
    ?*?Description:
    ?*?自定义Token增强
    ?*?@author?javadaily
    ?*?@date?2020/7/4?15:56
    ?*/
    public?class?CustomJwtTokenConverter?extends?JwtAccessTokenConverter{
    ????@Override
    ????public?OAuth2AccessToken?enhance(OAuth2AccessToken?oAuth2AccessToken,?OAuth2Authentication?authentication)?{
    ????????Object?principal?=?authentication.getUserAuthentication().getPrincipal();
    ????????final?Map<String,Object>?additionalInformation?=?new?HashMap<>(4);
    ????????additionalInformation.put("author","java日知录");
    ????????additionalInformation.put("weixin","javadaily");
    ????????((DefaultOAuth2AccessToken)?oAuth2AccessToken).setAdditionalInformation(additionalInformation);
    ????????return?super.enhance(oAuth2AccessToken,authentication);
    ????}
    }
    
    
    1. 在认证服务器AuthorizationServerConfig中配置自定义token增强器

    @Bean
    public?JwtAccessTokenConverter?jwtTokenEnhancer(){
    ?//自定义jwt?输出内容,若不需要就直接使用JwtAccessTokenConverter
    ?JwtAccessTokenConverter?converter?=?new?CustomJwtTokenConverter();
    ?//?设置对称签名
    ?converter.setSigningKey("javadaily");
    ?return?converter;
    }
    

    通过上述两步配置,我们生成的jwt token中就可以带上 authorweixin 两个属性了,效果如下:

    有的同学可能要问,为什么配置了这个增强器就会生成额外属性了呢?

    ?

    这是因为我们会使用 DefaultTokenServices#createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken)方法时有如下一段代码:

    private?OAuth2AccessToken?createAccessToken(OAuth2Authentication?authentication,?OAuth2RefreshToken?refreshToken)?{
    ?DefaultOAuth2AccessToken?token?=?new?DefaultOAuth2AccessToken(UUID.randomUUID().toString());
    ?int?validitySeconds?=?getAccessTokenValiditySeconds(authentication.getOAuth2Request());
    ?if?(validitySeconds?>?0)?{
    ??token.setExpiration(new?Date(System.currentTimeMillis()?+?(validitySeconds?*?1000L)));
    ?}
    ?token.setRefreshToken(refreshToken);
    ?token.setScope(authentication.getOAuth2Request().getScope());
    
    ?return?accessTokenEnhancer?!=?null???accessTokenEnhancer.enhance(token,?authentication)?:?token;
    }
    

    如果系统配置了 accessTokenEnhancer就会调用 accessTokenEnhancerenhance() 方法进行token增强。我们是继承了 JwtAccessTokenConverter,所以会在jwt token的基础上增加额外的信息。

    如何在jwt中添加用户的额外数据

    要添加额外数据我们还是要从 CustomJwtTokenConverter想办法,要添加用户的额外数据比如用户id和手机号码那就必须要在用户中包含这些信息。

    原来我们自定义的 UserDetailServiceImpl中返回的是默认的 UserDetails。里面只包含用户名属性,即username,代码调试效果如下:

    所以我们这里需要自定一个 UserDetails,包含用户的额外属性,然后在 UserDetailServiceImpl中再返回我们这个自定义对象,最后在 enhance方法中强转成自定义用户对象并添加额外属性。

    实现顺序如下:

    1. 自定义UserDetails

    import?lombok.Getter;
    import?org.springframework.security.core.GrantedAuthority;
    import?org.springframework.security.core.userdetails.User;
    
    import?java.util.Collection;
    
    /**
    ?*?<p>
    ?*?<code>CustomUser</code>
    ?*?</p>
    ?*?Description:
    ?*?自定义用户信息
    ?*?@author?jianzh5
    ?*?@date?2020/11/17?15:05
    ?*/
    public?class?SecurityUser?extends?User?{
    ????@Getter
    ????private?Integer?id;
    
    ????@Getter
    ????private?String?mobile;
    
    ????public?SecurityUser(Integer?id,?String?mobile,
    ????????????????????????String?username,?String?password,
    ????????????????????????Collection<??extends?GrantedAuthority>?authorities)?{
    ????????super(username,?password,?authorities);
    ????????this.id?=?id;
    ????????this.mobile?=?mobile;
    ????}
    }
    
    1. 在UserDetailServiceImpl中返回自定义对象

    private?UserDetails?buildUserDetails(SysUser?sysUser)?{
    ?Set<String>?authSet?=?new?HashSet<>();
    ?List<String>?roles?=?sysUser.getRoles();
    ?if(!CollectionUtils.isEmpty(roles)){
    ??roles.forEach(item?->?authSet.add(CloudConstant.ROLE_PREFIX?+?item));
    ??authSet.addAll(sysUser.getPermissions());
    ?}
    
    ?List<GrantedAuthority>?authorityList?=?AuthorityUtils.createAuthorityList(authSet.toArray(new?String[0]));
    
    ?return?new?SecurityUser(
    ???sysUser.getId(),
    ???sysUser.getMobile(),
    ???sysUser.getUsername(),
    ???sysUser.getPassword(),
    ???authorityList
    ?);
    }
    
    1. 在ehance方法中获取当前用户并设置用户信息

    public?class?CustomJwtTokenConverter?extends?JwtAccessTokenConverter{
    ????@Override
    ????public?OAuth2AccessToken?enhance(OAuth2AccessToken?oAuth2AccessToken,?OAuth2Authentication?authentication)?{
    ????????SecurityUser?securityUser?=?(SecurityUser)?authentication.getUserAuthentication().getPrincipal();
    ????????final?Map<String,Object>?additionalInformation?=?new?HashMap<>(4);
    ????????additionalInformation.put("userId",?securityUser.getId());
    ????????additionalInformation.put("mobile",?securityUser.getMobile());
    ??...
    ????????((DefaultOAuth2AccessToken)?oAuth2AccessToken).setAdditionalInformation(additionalInformation);
    ????????return?super.enhance(oAuth2AccessToken,authentication);
    ????}
    }
    

    如何在资源服务器中获取这些自定义信息

    通过上面的配置我们可以往jwt的token中添加上用户的数据信息,但是在资源服务器中还是获取不到,通过

    SecurityContextHolder.getContext().getAuthentication().getPrincipal()获取到的用户信息还是只包含用户名。

    这里还是得从token的转换器入手,默认情况下 JwtAccessTokenConverter 会调用 DefaultUserAuthenticationConverter中的 extractAuthentication方法从token中获取用户信息。

    我们先看看具体实现逻辑:

    public?class?DefaultUserAuthenticationConverter?implements?UserAuthenticationConverter?{
    ?...
    ?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;
    ?}
    ?...
    }
    

    在没有注入 UserDetailService的情况下oauth2只会获取用户名 user_name。如果注入了 UserDetailService就可以返回所有用户信息。

    所以这里我们对应的实现方式也有两种:

    1. 在资源服务器中也注入UserDetailService,这种方法不推荐,资源服务器与认证服务器分开的情况下强行耦合在一起,也需要加入用户认证的功能。

    2. 扩展DefaultUserAuthenticationConverter,重写extractAuthentication方法,手动取出额外数据,然后在资源服务器配置中将其注入到AccessTokenConverter中。

    这里我们采用第二种方法实现,实现顺序如下:

    1. 自定义token解析器,从jwt token中解析用户信息。

    package?com.javadaily.common.security.component;
    
    import?com.javadaily.common.security.user.SecurityUser;
    import?org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import?org.springframework.security.core.Authentication;
    import?org.springframework.security.core.GrantedAuthority;
    import?org.springframework.security.core.authority.AuthorityUtils;
    import?org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
    import?org.springframework.util.StringUtils;
    
    import?java.util.Collection;
    import?java.util.Map;
    
    public?class?CustomUserAuthenticationConverter?extends?DefaultUserAuthenticationConverter?{
    
    ????/**
    ?????*?重写抽取用户数据方法
    ?????*?@author?javadaily
    ?????*?@date?2020/11/18?10:56
    ?????*?@param?map?用户认证信息
    ?????*?@return?Authentication
    ?????*/
    ????@Override
    ????public?Authentication?extractAuthentication(Map<String,??>?map)?{
    ????????if?(map.containsKey(USERNAME))?{
    ????????????Collection<??extends?GrantedAuthority>?authorities?=?getAuthorities(map);
    ????????????String?username?=?(String)?map.get(USERNAME);
    ????????????Integer?id?=?(Integer)?map.get("userId");
    ????????????String?mobile??=?(String)?map.get("mobile");
    ????????????SecurityUser?user?=?new?SecurityUser(id,?mobile,?username,"N/A",?authorities);
    ????????????return?new?UsernamePasswordAuthenticationToken(user,?"N/A",?authorities);
    ????????}
    ????????return?null;
    ????}
    
    ????private?Collection<??extends?GrantedAuthority>?getAuthorities(Map<String,??>?map)?{
    ????????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");
    ????}
    
    }
    
    1. 编写自定义token转换器,注入自定义解压器

    public?class?CustomAccessTokenConverter?extends?DefaultAccessTokenConverter{
    
    ????public?CustomAccessTokenConverter(){
    ????????super.setUserTokenConverter(new?CustomUserAuthenticationConverter());
    ????}
    }
    
    1. 在资源服务器中配置类ResourceServerConfig中注入自定义token转换器

    @Bean
    public?JwtAccessTokenConverter?jwtTokenConverter(){
    ?JwtAccessTokenConverter?jwtAccessTokenConverter?=?new?JwtAccessTokenConverter();
    ?jwtAccessTokenConverter.setSigningKey("javadaily");
    ?jwtAccessTokenConverter.setAccessTokenConverter(new?CustomAccessTokenConverter());
    
    ?return?jwtAccessTokenConverter;
    }
    

    通过上面三步配置我们再调用 SecurityContextHolder.getContext().getAuthentication().getPrincipal()方法时就可以获取到用户的额外信息了。

    当然我们可以再来一个工具类,从上下文中直接获取用户信息:

    @UtilityClass
    public?class?SecurityUtils?{
    ????/**
    ?????*?获取Authentication
    ?????*/
    ????public?Authentication?getAuthentication()?{
    ????????return?SecurityContextHolder.getContext().getAuthentication();
    ????}
    
    ????public?SecurityUser?getUser(){
    ????????Authentication?authentication?=?getAuthentication();
    ????????if?(authentication?==?null)?{
    ????????????return?null;
    ????????}
    ????????return?getUser(authentication);
    ????}
    
    ????/**
    ?????*?获取当前用户
    ?????*?@param?authentication?认证信息
    ?????*?@return?当前用户
    ?????*/
    ????private?static?SecurityUser?getUser(Authentication?authentication)?{
    ????????Object?principal?=?authentication.getPrincipal();
    ????????if(principal?instanceof?SecurityUser){
    ????????????return?(SecurityUser)?principal;
    ????????}
    ????????return?null;
    ????}
    }
    

    如果本文对你有帮助,

    别忘记来个三连:

    点赞,转发,评论

    咱们下期见!

    收藏?等于白嫖点赞?才是真情!

    End

    干货分享

    这里为大家准备了一份小小的礼物,关注公众号,输入如下代码,即可获得百度网盘地址,无套路领取!

    001:《程序员必读书籍》
    002:《从无到有搭建中小型互联网公司后台服务架构与运维架构》
    003:《互联网企业高并发解决方案》
    004:《互联网架构教学视频》
    006:《SpringBoot实现点餐系统》
    007:《SpringSecurity实战视频》
    008:《Hadoop实战教学视频》
    009:《腾讯2019Techo开发者大会PPT》

    010:?微信交流群

    近期热文top

    1、关于JWT Token 自动续期的解决方案

    2、SpringBoot开发秘籍-事件异步处理

    3、架构师之路-服务器硬件扫盲

    4、架构师之路-微服务技术选型

    5、RocketMQ进阶?-?事务消息


    以上信息来源于网络,如有侵权,请联系站长删除。

    TAG:二十一 SpringCloud Alibaba

  • 上一篇:自定义 loader 读取 *.vue 文件源码
  • 与“SpringCloud Alibaba微服务实战二十一”相关的资讯
  • SpringCloud
  • 三. SpringCloud服务注册与发现
  • SpringCloud Alibaba Nacos注册中心源码浅析
  • 二. SpringCloud基本Rest微服务工程搭建
  • 数据挖掘算法和实践(二十一):kaggle经典