Spring Security OAUTH2 获取用户信息

Spring Security OAUTH2 获取用户信息1.user-info-uri与token-info-uri作用:二者皆是为了checktoken,并且顺带返回了用户信息。配置信息位置在资源服务器上。解释:下面代码列举的都是token-info-uri,user-info-uri不解释。user-info-uri原理是在授权服务器认证后将认证信息Principal通过形参绑定的方法通过URL的方式获取用户信息。当然它也有配套的Us…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

1.user-info-uri 与 token-info-uri

作用:二者皆是为了check token,并且顺带返回了用户信息。配置信息位置在资源服务器上。

解释:下面代码列举的都是token-info-uri,user-info-uri不解释。user-info-uri原理是在授权服务器认证后将认证信息Principal通过形参绑定的方法通过URL的方式获取用户信息。当然它也有配套的UserInfoTokenService等等,我没有研究,不过流程大概跟token-info-uri差不多。

server:
  port: 9007
security:
  oauth2:
    client:
      clientId: resource1
      clientSecret: secret
      userAuthorizationUri: http://localhost:9005/oauth/authorize
      grant-type: password
      scope: read
      access-token-uri: http://localhost:9005/oauth/token

    resource:
      token-info-uri: http://localhost:9005/oauth/check_token
      user-info-uri: http://localhost:9005/user
    authorization:
      check-token-access: http://localhost:9005/oauth/check_token

#    resource:
#      jwt:
#        key-uri: http://localhost:9005/oauth/token_key
  basic:
    enabled: false

下面内容默认已经自定义了UserDetail的实现,我们从开始向授权服务器验证token开始分析源码

从RemoteTokenService发起验证请求,可以看到是通过restTemplate发起请求的,并且返回map类型的响应结果

public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {
        MultiValueMap<String, String> formData = new LinkedMultiValueMap();
        formData.add(this.tokenName, accessToken);
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", this.getAuthorizationHeader(this.clientId, this.clientSecret));
        Map<String, Object> map = this.postForMap(this.checkTokenEndpointUrl, formData, headers);
        if (map.containsKey("error")) {
            this.logger.debug("check_token returned error: " + map.get("error"));
            throw new InvalidTokenException(accessToken);
        } else {
            Assert.state(map.containsKey("client_id"), "Client id must be present in response from auth server");
            return this.tokenConverter.extractAuthentication(map);
        }
    }
public class RemoteTokenServices implements ResourceServerTokenServices {
    
    ......

    private Map<String, Object> postForMap(String path, MultiValueMap<String, String> formData, HttpHeaders headers) {
        if (headers.getContentType() == null) {
            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        }

        Map map = (Map)this.restTemplate.exchange(path, HttpMethod.POST, new HttpEntity(formData, headers), Map.class, new Object[0]).getBody();
        return map;
    }
}

接着我们会到达check_token端点,可以看到通过loadAuthentication将token转化成OAuth2Authentication。然后再通过convertAccessToken转换我们需要返回的信息

@RequestMapping({"/oauth/check_token"})
    @ResponseBody
    public Map<String, ?> checkToken(@RequestParam("token") String value) {
        OAuth2AccessToken token = this.resourceServerTokenServices.readAccessToken(value);
        if (token == null) {
            throw new InvalidTokenException("Token was not recognised");
        } else if (token.isExpired()) {
            throw new InvalidTokenException("Token has expired");
        } else {
            OAuth2Authentication authentication = this.resourceServerTokenServices.loadAuthentication(token.getValue());
            Map<String, ?> response = this.accessTokenConverter.convertAccessToken(token, authentication);
            return response;
        }
    }

通过将token作为key从redis取出value,然后反序列化出来认证对象

public OAuth2AccessToken readAccessToken(String tokenValue) {
        byte[] key = this.serializeKey("access:" + tokenValue);
        byte[] bytes = null;
        RedisConnection conn = this.getConnection();

        byte[] bytes;
        try {
            bytes = conn.get(key);
        } finally {
            conn.close();
        }

        OAuth2AccessToken var5 = this.deserializeAccessToken(bytes);
        return var5;
    }

 通过转换器挑选需要返回的数据信息

 public Map<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
        Map<String, Object> response = new HashMap();
        OAuth2Request clientToken = authentication.getOAuth2Request();
        if (!authentication.isClientOnly()) {
            response.putAll(this.userTokenConverter.convertUserAuthentication(authentication.getUserAuthentication()));
        } else if (clientToken.getAuthorities() != null && !clientToken.getAuthorities().isEmpty()) {
            response.put("authorities", AuthorityUtils.authorityListToSet(clientToken.getAuthorities()));
        }

        if (token.getScope() != null) {
            response.put("scope", token.getScope());
        }

        if (token.getAdditionalInformation().containsKey("jti")) {
            response.put("jti", token.getAdditionalInformation().get("jti"));
        }

        if (token.getExpiration() != null) {
            response.put("exp", token.getExpiration().getTime() / 1000L);
        }

        if (this.includeGrantType && authentication.getOAuth2Request().getGrantType() != null) {
            response.put("grant_type", authentication.getOAuth2Request().getGrantType());
        }

        response.putAll(token.getAdditionalInformation());
        response.put("client_id", clientToken.getClientId());
        if (clientToken.getResourceIds() != null && !clientToken.getResourceIds().isEmpty()) {
            response.put("aud", clientToken.getResourceIds());
        }

        return response;
    }

从默认的实现类可以看出,只会将认证的用户名name返回到资源服务器,我们如果想要所有信息,最好是重构此方法。当然这个前提是我们在使用token-info-uri 

public class DefaultUserAuthenticationConverter implements UserAuthenticationConverter {
    private Collection<? extends GrantedAuthority> defaultAuthorities;
    private UserDetailsService userDetailsService;

    public DefaultUserAuthenticationConverter() {
    }

    public void setUserDetailsService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    public void setDefaultAuthorities(String[] defaultAuthorities) {
        this.defaultAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils.arrayToCommaDelimitedString(defaultAuthorities));
    }

    public Map<String, ?> convertUserAuthentication(Authentication authentication) {
        Map<String, Object> response = new LinkedHashMap();
        response.put("user_name", authentication.getName());
        if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
            response.put("authorities", AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
        }

        return response;
    }

开始重写 DefaultUserAuthenticationConverter的convertUserAuthentication方法,将所有授权信息都返回到资源服务器

@Service
public class MyUserAuthenticationConverter extends DefaultUserAuthenticationConverter {

    @Override
    public Map<String, ?> convertUserAuthentication(Authentication authentication) {
        Map<String, Object> response = new LinkedHashMap();
        response.put("user_name", authentication);
        return response;
    }
}

在授权服务器的配置类上,我们把重写的认证转换器设置到配置类上,由于看到源代码只有accessTokenConverter方法,可以得知需要替换整个DefaultAccessTokenConverter,而在DefaultAccessTokenConverter里面我们可以把我们刚刚重构的DefaultUserAuthenticationConverter设置进去。

@Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        Collection<TokenEnhancer> tokenEnhancers = applicationContext.getBeansOfType(TokenEnhancer.class).values();
        TokenEnhancerChain tokenEnhancerChain=new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(new ArrayList<>(tokenEnhancers));
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setReuseRefreshToken(isReuseRefreshToken);
        defaultTokenServices.setSupportRefreshToken(isSupportRefreshToken);
        defaultTokenServices.setTokenStore(tokenStore);
        defaultTokenServices.setAccessTokenValiditySeconds(accessTokenValiditySeconds);
        defaultTokenServices.setRefreshTokenValiditySeconds(refreshTokenValiditySeconds);
        defaultTokenServices.setTokenEnhancer(tokenEnhancerChain);
        //若通过 JDBC 存储令牌
        if (Objects.nonNull(jdbcClientDetailsService)){
            defaultTokenServices.setClientDetailsService(jdbcClientDetailsService);
        }
        DefaultAccessTokenConverter defaultAccessTokenConverter=new DefaultAccessTokenConverter();
        defaultAccessTokenConverter.setUserTokenConverter(new MyUserAuthenticationConverter());
        endpoints
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService)
                .accessTokenConverter(defaultAccessTokenConverter)
                .tokenServices(defaultTokenServices);
    }

当授权服务器响应完毕,我们重新回到资源发武器发起check token请求的函数那里,可以看到当拿到map后,开始用资源服务器也就是本引用中的tokenConverte进行数据的处理

public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {
        MultiValueMap<String, String> formData = new LinkedMultiValueMap();
        formData.add(this.tokenName, accessToken);
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", this.getAuthorizationHeader(this.clientId, this.clientSecret));
        Map<String, Object> map = this.postForMap(this.checkTokenEndpointUrl, formData, headers);
        if (map.containsKey("error")) {
            this.logger.debug("check_token returned error: " + map.get("error"));
            throw new InvalidTokenException(accessToken);
        } else {
            Assert.state(map.containsKey("client_id"), "Client id must be present in response from auth server");
            return this.tokenConverter.extractAuthentication(map);
        }
    }

 我们查看DefaultAcessTokenConverter类中的extractAuthentication方法,发现了核心方法在DefaultUserAccessTokenConcerter中的extractAuthentication方法。

 public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
        Map<String, String> parameters = new HashMap();
        Set<String> scope = this.extractScope(map);
        Authentication user = this.userTokenConverter.extractAuthentication(map);
        String clientId = (String)map.get("client_id");
        parameters.put("client_id", clientId);
        if (this.includeGrantType && map.containsKey("grant_type")) {
            parameters.put("grant_type", (String)map.get("grant_type"));
        }

        Set<String> resourceIds = new LinkedHashSet((Collection)(map.containsKey("aud") ? this.getAudience(map) : Collections.emptySet()));
        Collection<? extends GrantedAuthority> authorities = null;
        if (user == null && map.containsKey("authorities")) {
            String[] roles = (String[])((Collection)map.get("authorities")).toArray(new String[0]);
            authorities = AuthorityUtils.createAuthorityList(roles);
        }

        OAuth2Request request = new OAuth2Request(parameters, clientId, authorities, true, scope, resourceIds, (String)null, (Set)null, (Map)null);
        return new OAuth2Authentication(request, user);
    }

从这个方法可以看出,将user_name对象取出value作为principal。从这里我们也不难想到,要想拿所有的用户信息有两种解决方法。第一种就是从资源服务器那里重写Conveter方法,将所有数据返回。并直接用user_name当成key名,资源服务器无需修改。第二种方法就是不修改授权服务器,而是在资源服务器这里配置好UserDetailsService类,通过user_name从数据库加载信息,如果不配置,那么将不执行这段代码。但是我认为最好是不要采用第二种方法,让授权服务器管理认证和授权即可,不要把资源服务器也牵扯到这上面。让资源服务器保持单一性。 

public Authentication extractAuthentication(Map<String, ?> map) {
        if (map.containsKey("user_name")) {
            Object principal = map.get("user_name");
            Collection<? extends GrantedAuthority> authorities = this.getAuthorities(map);
            if (this.userDetailsService != null) {
                UserDetails user = this.userDetailsService.loadUserByUsername((String)map.get("user_name"));
                authorities = user.getAuthorities();
                principal = user;
            }

            return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
        } else {
            return null;
        }
    }

最后查看资源服务器上获取到的认证信息。当然需要返回什么信息自己可以在授权服务端自行定义,本处就不一一列举了。

直接在形参上 绑定Principal是因为当返回map的时候我们也可以看到会调用TokenConveter的方法进行参数的转化,所以最后返回的信息是一个Authentication,而它的顶级接口就是Principal.所以我们可以通过自动绑定的方式拿到用户信息。

Spring Security OAUTH2 获取用户信息

Spring Security OAUTH2 获取用户信息

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/196006.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 安装PyTorch详细过程

    安装PyTorch详细过程安装PyTorch过程安装anaconda环境管理PyTorch安装检验安装安装anaconda登录anaconda的官网下载,anaconda是一个集成的工具软件不需要我们再次下载。anaconda官网点击下载跳转到这个页面如果你的Python版本正好是3.8版,那便可以直接根据系统去选择自己相应的下载版本就可以了。但是如果你的Python版本号不是当前页面的版本号,那我建议你去选择相对应的版本号。点击archive你就会跳转到下面的页面你可以访问这篇博客去找到当前与你python版本号相对

  • pycharm社区版安装教程 2019_pycharm安装教程2020社区版

    pycharm社区版安装教程 2019_pycharm安装教程2020社区版首先进入JetBrain的官网(国内正常访问):https://www.jetbrains.com/第一眼看到的界面如下图所示:然后找到我们的Pycharm专题页:进入Pycharm的专题页面之后,点击下载按钮(这里有两个按钮,点任何一个都行):然后进入到真正的下载页面你会发现有两个版本的Pycharm,一个是Professional版本(收费),另外一个是Community版本是永久免费的,而且后续升级什么的也都是免费的,我们下载这个就行了,Comm…

  • 是在传统pc的路上走呢,还是跟潮流走移动互联网「建议收藏」

    这些天在准备期末考试,复习c++,可是总是对手机应用的开发感兴趣,往java方向发展可能前途更好么?以我目前接触的信息来看,貌似java语言更受欢迎,而且照现在的趋势发展,手机这种移动终端的发展应该是以后一段时间的趋势,前几天看到一个关于c++的帖子,说以后c++的程序员会两极分化,学得很好的会找到很好的工作,高新,而学的一般的,全都找不到工作。大意是这样,看了很没有信心,现在全校有一半的专业都在

  • 在毕设中学习03

    在毕设中学习035.24文献阅读记录脑电分为诱发性脑电和自发性脑电,诱发性脑电的诱发因素又分为外源性刺激(视觉听觉触觉)和内源性事件相关(计算、思考)keras库keras以TensorFlow/Theano作为后端封装,是一个专门用于深度学习的python模块。包含了全连接层,卷积层,池化层,循环层,嵌入层等等等,常见的深度学习模型。包含用于定义损失函数的Losses用于训练模型的Optimizers评估模型的Metrics定义激活函数的Activations防止过拟合的Regularizers等mn

  • 10个常用的3D建模软件,作为3D建模的软件东西很杂很碎,还需多练习才最重要「建议收藏」

    10个常用的3D建模软件,作为3D建模的软件东西很杂很碎,还需多练习才最重要「建议收藏」很多人都会好奇,电脑是怎么将手绘的2D图形变成3D的实际物品的?究竟是什么神奇魔法能够瞬间将我们的想法变成现实的呢?今天来和大家介绍下工业设计师经常会用到的10个3D建模软件。SolidworksSolidworks是工业设计师经常会用到的一款建模软件。SolidWorks是一款在MircosoftWindows上才能运行的建模计算机辅助设计和计算机辅助工程的计算机程序,由DassaultSystemes开发和发布。这是一款很常见的很普遍的工业设计建模软件,如果你以后有机会在国外找工

  • UML之构件图

    UML之构件图构件图的概念构件图用于静态建模,是表示构件类型的组织以及各种构件之间依赖关系的图。构件图通过对构件间依赖关系的描述来估计对系统构件的修改给系统可能带来的影响。 构件图的组成构件图由:构件,接口,实现和依赖四部分组成构件:描述了系统的一个可执行程序,一个库,一个Web程序等.构件是系统中可替换的物理部分,它包装了实现而且遵从并提供一组接口的实现构件的内容(1)规格说明

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号