【问题标题】:Spring OAuth2 - Manually creating an access token in the token storeSpring OAuth2 - 在令牌存储中手动创建访问令牌
【发布时间】:2013-09-03 09:31:05
【问题描述】:

我有一种情况,我想自己创建一个访问令牌(所以不是通过通常的过程)。我想出了这样的事情:

@Inject
private DefaultTokenServices defaultTokenServices;

... 

OAuth2Authentication auth = xxx;
OAuth2AccessToken  token = defaultTokenServices.createAccessToken(auth);

唯一的问题是我不确定如何创建 OAuth2Authentication(在我的代码中带有 xxx 的部分)。我有用户和客户信息,并且我知道我想授予该令牌的权限。

【问题讨论】:

    标签: java spring oauth spring-security oauth-2.0


    【解决方案1】:

    在这里,您的用例可能会根据您使用的流程而略有不同。这适用于密码授予流程。有一些自定义类,如令牌存储、令牌增强器等。但这实际上只是根据我们自己的需要修改的 spring 类的扩展版本。

            HashMap<String, String> authorizationParameters = new HashMap<String, String>();
            authorizationParameters.put("scope", "read");
            authorizationParameters.put("username", "mobile_client");
            authorizationParameters.put("client_id", "mobile-client");
            authorizationParameters.put("grant", "password");
    
            DefaultAuthorizationRequest authorizationRequest = new DefaultAuthorizationRequest(authorizationParameters);
            authorizationRequest.setApproved(true);
    
            Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
            authorities.add(new SimpleGrantedAuthority("ROLE_UNTRUSTED_CLIENT"));
            authorizationRequest.setAuthorities(authorities);
    
            HashSet<String> resourceIds = new HashSet<String>();
            resourceIds.add("mobile-public");
            authorizationRequest.setResourceIds(resourceIds);
    
            // Create principal and auth token
            User userPrincipal = new User(user.getUserID(), "", true, true, true, true, authorities);
    
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userPrincipal, null, authorities) ;
    
            OAuth2Authentication authenticationRequest = new OAuth2Authentication(authorizationRequest, authenticationToken);
            authenticationRequest.setAuthenticated(true);
    
            CustomTokenStore tokenStore = new CustomTokenStore();
    
            // Token Enhancer
            CustomTokenEnhancer tokenEnhancer = new CustomTokenEnhancer(user.getUserID());
    
            CustomTokenServices tokenServices = new CustomTokenServices();
            tokenServices.setTokenEnhancer(tokenEnhancer);
            tokenServices.setSupportRefreshToken(true);
            tokenServices.setTokenStore(tokenStore);
    
            OAuth2AccessToken accessToken = tokenServices.createAccessTokenForUser(authenticationRequest, user);
    

    【讨论】:

    • 这里的 DefaultAuthorizationRequest 是什么?
    • 如果没有 import 语句,这不是很有帮助。
    【解决方案2】:

    下面是如何使用 TokenEndpoint 接口(用于暴露 REST 服务)生成 Token:

    @Inject
    private TokenEndpoint tokenEndpoint;
    
    public ResponseEntity<?> getToken(Principal principal) {
    
            HashMap<String, String> parameters = new HashMap<String, String>();
            parameters.put("client_id", "appid");
            parameters.put("client_secret", "myOAuthSecret");
            parameters.put("grant_type", "password");
            parameters.put("password", myUser.getPassword());
            parameters.put("scope", "read write");
            parameters.put("username", myUser.getLogin());
    
            return tokenEndpoint.getAccessToken(principal, parameters);
    }
    

    【讨论】:

    • getAccessToken 不适用于新版本的 spring-security-oauth2-2.x.x,除非您先调用 tokenEndpoint.setAllowedRequestMethods(new HashSet(Arrays.asList(HttpMethod.POST, HttpMethod.GET))。由于getAccessToken 在后台调用postAccessToken,您可以立即调用postAccessToken
    • @Inject TokenEndpoint tokenEndpoint 获取空 tokenEndpoint
    【解决方案3】:

    另外,要手动生成OAuth2 Accesss Token,我们可以使用TokenService的实例

    @Autowired
    private AuthorizationServerEndpointsConfiguration configuration;
    
    @Override
    public String generateOAuth2AccessToken(User user, List<Role> roles, List<String> scopes) {
    
        Map<String, String> requestParameters = new HashMap<String, String>();
        Map<String, Serializable> extensionProperties = new HashMap<String, Serializable>();
    
        boolean approved = true;
        Set<String> responseTypes = new HashSet<String>();
        responseTypes.add("code");
    
        // Authorities
        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
        for(Role role: roles)
            authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
    
        OAuth2Request oauth2Request = new OAuth2Request(requestParameters, "clientIdTest", authorities, approved, new HashSet<String>(scopes), new HashSet<String>(Arrays.asList("resourceIdTest")), null, responseTypes, extensionProperties);
    
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUsername(), "N/A", authorities);
    
        OAuth2Authentication auth = new OAuth2Authentication(oauth2Request, authenticationToken);
    
        AuthorizationServerTokenServices tokenService = configuration.getEndpointsConfigurer().getTokenServices();
    
        OAuth2AccessToken token = tokenService.createAccessToken(auth);
    
        return token.getValue();
    }
    

    【讨论】:

    • 通过这种方法,我们可以使用生成的访问令牌访问资源,在访问令牌到期时,它不会发出带有刷新令牌的访问令牌。即使客户详细信息正确,它也会提供未经授权的客户
    • 它给了我 com.demo.sas.service.UserLoginService 中的 Field tokenService 需要一个找不到的 'org.springframework.security.core.token.TokenService' 类型的 bean。我正在使用带有 @Autowired 注释的 TokenService。
    【解决方案4】:

    我的解决方案基于 Mop So 的回答,但没有使用:

    return tokenEndpoint.getAccessToken(principal, parameters);
    

    我用过:

    tokenEndpoint.postAccessToken(principal, parameters);
    

    为什么?因为如果你使用tokenEndpoint.getAccessToken(principal, parameters),endpoing 会抛出一个HttpRequestMethodNotSupportedException,因为它没有被GET 方法调用。至少,spring-security-oauth2-2.0.13.RELEASE 发生在我身上是这样的

    public OAuth2AccessToken getAccessToken() throws HttpRequestMethodNotSupportedException {
        HashMap<String, String> parameters = new HashMap<>();
        parameters.put("client_id", CLIENT_ID);
        parameters.put("client_secret", CLIENT_SECRET);
        parameters.put("grant_type", "client_credentials");
    
        ClientDetails clientDetails = clientDetailsStore.get(CLIENT_ID);
    
        // Create principal and auth token
        User userPrincipal = new User(CLIENT_ID, CLIENT_SECRET, true, true, true, true, clientDetails.getAuthorities());
    
        UsernamePasswordAuthenticationToken principal = new UsernamePasswordAuthenticationToken(userPrincipal, CLIENT_SECRET,
                clientDetails.getAuthorities());
    
        ResponseEntity<OAuth2AccessToken> accessToken = tokenEndpoint.postAccessToken(principal, parameters);
    
        return accessToken.getBody();
    }
    

    【讨论】:

    • 您可以预先设置 TokenEndpoint 允许的请求方法:tokenEndpoint.setAllowedRequestMethods(new HashSet(Arrays.asList(HttpMethod.POST, HttpMethod.GET)),但由于getAccessToken 在后台调用postAccessToken,您的方式就是要走的路。好答案!
    【解决方案5】:

    这对我有用:

    @Override public OAuth2AccessToken getToken(String username, String password) {
        HashMap<String, String> parameters = new HashMap<String, String>();
        parameters.put("client_id", clientid);
        parameters.put("grant_type", "password");
        parameters.put("password", username);
        parameters.put("scope", scope);
        parameters.put("username", password);
    
        AuthorizationRequest authorizationRequest = defaultOAuth2RequestFactory.createAuthorizationRequest(parameters);
        authorizationRequest.setApproved(true);
    
        OAuth2Request oauth2Request = defaultOAuth2RequestFactory.createOAuth2Request(authorizationRequest);
        // Create principal and auth token
        final UsernamePasswordAuthenticationToken loginToken = new UsernamePasswordAuthenticationToken(
                username, password);
        Authentication authentication = authenticationManager.authenticate(loginToken);
    
        OAuth2Authentication authenticationRequest = new OAuth2Authentication(oauth2Request, authentication);
        authenticationRequest.setAuthenticated(true);
    
        OAuth2AccessToken accessToken = tokenServices.createAccessToken(authenticationRequest);
    
        return accessToken;
    }
    

    在 Oauth2Configuration 中:

    @Bean
        DefaultOAuth2RequestFactory defaultOAuth2RequestFactory() {
        return new DefaultOAuth2RequestFactory(clientDetailsService);
    }
    

    Oauth2Configuration 的其余部分应如文章中所示:

    http://stytex.de/blog/2016/02/01/spring-cloud-security-with-oauth2/

    【讨论】:

    • 我遇到了 BadCredentialsExcepton .. 我该如何解决这个问题?
    【解决方案6】:

    问题

    我对这里列出的所有实现都遇到了问题,所以我终于设法通过无状态服务器、oauth2 和 google social 获得了自己的实现。它只是教程的最后一部分,缺少here

    对我来说,问题是在执行 google oauth 后,我需要将 10 秒持续时间的令牌换成长期存在的令牌。为此,我需要生成一个 JWT 令牌并将其与我自己生成的真实访问令牌交换。

    实施

    @Service
    class SocialTokenVerificationService {
    
        @Autowired
        private lateinit var jwsTokenService: JWSTokenService
        @Autowired
        private lateinit var clientDetailsService: ClientDetailsService
        @Autowired
        private lateinit var userService: UserService
        @Autowired
        private lateinit var tokenServices: DefaultTokenServices
        @Autowired
        private lateinit var tokenRequestFactory: OAuth2RequestFactory
    
        fun verifyToken(token: String): OAuth2AccessToken? {
            val claimSet = jwsTokenService.parseToken(token)
            val userDetails = userService.loadUserByUsername(claimSet.subject)
    
            val client = clientDetailsService.loadClientByClientId(DEFAULT_SERVER_CLIENT)
            val parameters = HashMap<String, String>()
            val authentication = UsernamePasswordAuthenticationToken(userDetails, null, userDetails.authorities)
            return tokenServices.createAccessToken(OAuth2Authentication(
                    tokenRequestFactory.createOAuth2Request(client, TokenRequest(parameters, client.clientId, listOf("read", "write"), "password")),
                    authentication
            ))
        }
    }
    
    • JWSTokenService:它是一个自我实现的类,用于对 google oauth 和 mine 之间的交换令牌进行编码和解码。
    • ClientDetailsService: bean 声明为授权服务器的一部分。来自我的数据库

      覆盖有趣的配置(客户端:ClientDetailsS​​erviceConfigurer){ 客户端.jdbc(数据源) }

    • UserService:只是一个扩展UserDetailsService以从数据库中获取我的用户的用户服务

    • DefaultTokenServices:作为主 bean 实现如下

      @Bean
      @Primary
      fun tokenServices(): DefaultTokenServices {
          val defaultTokenServices = DefaultTokenServices()
          defaultTokenServices.setTokenStore(tokenStore())
          defaultTokenServices.setSupportRefreshToken(true)
          defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter())
          return defaultTokenServices
      }
      
    • OAuth2RequestFactory:作为bean实现如下

      @Bean
      fun oauthRequestFactory(clientsDetails: ClientDetailsService): OAuth2RequestFactory {
          return DefaultOAuth2RequestFactory(clientsDetails)
      }
      

    有了所有这些依赖关系,我需要做的是生成一个存储到数据库中的令牌,并在不提供密码的情况下遵循与其他流程相同的流程:

    1. 解析 jws 令牌并验证其有效性
    2. 加载已通过 google 验证的用户
    3. 使用UsernamePasswordAuthenticationToken 类生成Authentication。这是关键部分,调用DefaultTokenServices#createAccessToken获取新的token。它需要一些参数来执行请求:
      • OAuth2Request:可以用OAuth2RequestFactory创建
      • 之前创建的Authentication
      • 我们需要与触发此令牌请求的客户端生成TokenRequest。就我而言,我有硬编码

    总结

    回顾一下如何手动创建令牌:

    • 我们需要请求令牌服务给我们一个令牌
    • 为此,我们需要提供身份验证详细信息和执行请求的客户端
    • 有了这 2 个,我们可以获得一个新的令牌并正常提供它

    【讨论】:

      【解决方案7】:

      在 spring boot 2.2.2 项目中,我使用以下代码来执行密码流服务器端: 我必须指定authorizedClientManager.setContextAttributesMapper,因为PasswordOAuth2AuthorizedClientProvider 需要上下文中的特定属性。希望对您有所帮助。

      配置(application.yaml):

      spring:
        security:
          oauth2:
            client:
              provider:
                yourOauthProvider:
                  user-info-uri: ...
                  authorization-uri: ...
                  token-uri: ...
      
              registration:
                regId:
                  clientId: ...
                  clientSecret: ...
                  provider: yourOauthProvider
                  authorization-grant-type: password
                  redirect-uri-template: "{baseUrl}/login/oauth2/code/{registrationId}"
                  scope:
      

      接线:

      @Configuration
      public class Oauth2ClientConfig {
      
          @Bean
          public OAuth2AuthorizedClientManager authorizedClientManager(
                  ClientRegistrationRepository clientRegistrationRepository,
                  OAuth2AuthorizedClientRepository authorizedClientRepository) {
      
              OAuth2AuthorizedClientProvider authorizedClientProvider =
                      OAuth2AuthorizedClientProviderBuilder.builder()
                              .password()
                              .build();
      
              DefaultOAuth2AuthorizedClientManager authorizedClientManager =
                      new DefaultOAuth2AuthorizedClientManager(
                              clientRegistrationRepository, authorizedClientRepository);
              authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
              authorizedClientManager.setContextAttributesMapper(r -> {
                  Map<String, Object> m = new HashMap<>();
                  m.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, r.getPrincipal().getPrincipal());
                  m.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, r.getPrincipal().getCredentials());
                  return m;
              });
      
              return authorizedClientManager;
          }
      }
      

      服务:

      class AuthService {
          @Autowired
          private OAuth2AuthorizedClientManager authorizedClientManager;
          public OAuth2AccessToken authenticate(String user, String password) {
      
              Authentication principal = new UsernamePasswordAuthenticationToken(
                      user,
                      password);
      
              OAuth2AuthorizeRequest authorizeRequest = 
                  OAuth2AuthorizeRequest.withClientRegistrationId("regId")
                      .principal(principal)
                      .build();
      
              OAuth2AuthorizedClient authorizedClient =
                  this.authorizedClientManager.authorize(authorizeRequest);
      
              return authorizedClient.getAccessToken();
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2015-04-12
        • 2020-06-17
        • 2017-06-02
        • 2021-07-08
        • 2018-05-06
        • 2018-06-02
        • 2018-08-12
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多