Java Spring OAuth2 - 在令牌存储中手动创建访问令牌

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18536521/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 08:52:34  来源:igfitidea点击:

Spring OAuth2 - Manually creating an access token in the token store

javaspringoauthspring-securityoauth-2.0

提问by checklist

I have a situation where I would like to create an access token myself (so not through the usual process). I have come up with something like this:

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

@Inject
private DefaultTokenServices defaultTokenServices;

... 

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

The only problem is that I am not sure how to create the OAuth2Authentication (in my code the part with xxx). I have the user & client info and I know which Authorities I want to grant this token.

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

采纳答案by Michael

Here it is, your use case may differ slightly based on the flow you are using. This is what works for a password grant flow. There are a few custom class like token store, token enhancer ect. but that is really just extended versions of the spring classes modified for our own needs.

在这里,您的用例可能会根据您使用的流程略有不同。这适用于密码授予流程。有一些自定义类,如令牌存储、令牌增强器等。但这实际上只是根据我们自己的需要修改的 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);

回答by Mop So

Here is how to generate a Token using the TokenEndpoint interface (used to expose REST service) :

以下是使用 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);
}

回答by Duc Huy Ta

Other way, to manually generate an OAuth2 Accesss Tokenwe can use an instance of TokenService

其他方式,手动生成一个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();
}

回答by Laci

This has worked for me:

这对我有用:

@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;
}

In the Oauth2Configuration:

在 Oauth2Configuration 中:

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

The rest of the Oauth2Configuration should look like in the article:

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

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

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

回答by flags

I based my solution on Mop So's answer but instead of using:

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

return tokenEndpoint.getAccessToken(principal, parameters);

I used:

我用了:

tokenEndpoint.postAccessToken(principal, parameters);

Why? Because if you use tokenEndpoint.getAccessToken(principal, parameters)the endpoing will throw you a HttpRequestMethodNotSupportedExceptionbecause it has not been called with a GETmethod. At least, this is what happened to me with spring-security-oauth2-2.0.13.RELEASE

为什么?因为如果你使用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();
}

回答by droidpl

Problem

问题

I had problems with all the implementations listed here, so I finally managed to get my own with a stateless server, oauth2 and google social. Its just the last part of the tutorial that is missing here

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

The problem for me is that after executing the google oauth, I need to exchange a 10 second duration token for a long lived token. In order to do that I need to generate a JWT token and exchange it with a real access token generated by myself.

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

Implementation

执行

@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: its a self implemented class that encodes and decodes the exchanging token between google oauth and mine.
  • ClientDetailsService: bean declared as as part of the authorization server. Comes from my database

    override fun configure(clients: ClientDetailsServiceConfigurer) { clients.jdbc(datasource) }

  • UserService: just a user service that extends UserDetailsServiceto obtain my users from the database

  • DefaultTokenServices: implemented as a primary bean as follows

    @Bean
    @Primary
    fun tokenServices(): DefaultTokenServices {
        val defaultTokenServices = DefaultTokenServices()
        defaultTokenServices.setTokenStore(tokenStore())
        defaultTokenServices.setSupportRefreshToken(true)
        defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter())
        return defaultTokenServices
    }
    
  • OAuth2RequestFactory: implemented as a bean as follows

    @Bean
    fun oauthRequestFactory(clientsDetails: ClientDetailsService): OAuth2RequestFactory {
        return DefaultOAuth2RequestFactory(clientsDetails)
    }
    
  • JWSTokenService:它是一个自我实现的类,用于编码和解码 google oauth 和我的之间的交换令牌。
  • ClientDetailsService: bean 声明为授权服务器的一部分。来自我的数据库

    覆盖乐趣配置(客户端:ClientDetailsS​​erviceConfigurer){clients.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)
    }
    

With all this dependencies, what I need to do to generate a token that gets stored into the database and follows the same flows as the other ones without providing a password is:

有了所有这些依赖项,我需要做的是生成一个令牌,该令牌存储到数据库中并遵循与其他令牌相同的流程而无需提供密码:

  1. Parse the jws token and verify its validity
  2. Load the user that was authenticated with google
  3. Generate an Authenticationusing the UsernamePasswordAuthenticationTokenclass. This is the key part, call DefaultTokenServices#createAccessTokento obtain a new token. It needs some arguments to execute the request:
    • OAuth2Request: it can be created with the OAuth2RequestFactory
    • The Authenticationcreated previously
    • We need to generate a TokenRequestwith the client that is triggering this token request. In my case I have that hardcoded
  1. 解析 jws 令牌并验证其有效性
  2. 加载通过谷歌认证的用户
  3. Authentication使用UsernamePasswordAuthenticationToken类生成一个。这是关键部分,调用DefaultTokenServices#createAccessToken获取新令牌。它需要一些参数来执行请求:
    • OAuth2Request: 它可以用 OAuth2RequestFactory
    • Authentication先前创建
    • 我们需要TokenRequest与触发此令牌请求的客户端生成一个。在我的情况下,我有那个硬编码

Summary

概括

So to recap how to create a token manually:

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

  • We need to ask the token services to give us a token
  • For that we need to provide the authentication details and a client who does the request
  • With those 2 we can obtain a new token and serve it normally
  • 我们需要要求令牌服务给我们一个令牌
  • 为此,我们需要提供身份验证详细信息和执行请求的客户端
  • 有了这 2 个,我们可以获得一个新的令牌并正常提供它

回答by revau.lt

In a spring boot 2.2.2 project I'm using the following code to do a pasword flow server side: I had to specify authorizedClientManager.setContextAttributesMappersince PasswordOAuth2AuthorizedClientProvideris expecting specific attributes in the context. Hope that helps.

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

Config (application.yaml):

配置(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:

Wiring:

接线:

@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;
    }
}

Service:

服务:

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();
    }
}