oauth2.0 JdbcClientDetailsServiceを使用してデータベースから適切な構成を読み込む

17043 ワード

前節では、ライセンスコードをredisに格納し、対応するリクエストのパスをin-memoryで格納する構成について説明しましたが、これはメモリに格納されていますが、実際に開発された私たちのデータはデータテーブルからクエリーされることを望んでいますが、どうすればいいのでしょうか.
1.in-memoryストレージのレビュー
/**
         * inMemory              
         */
        clients.inMemory()
                //client Id
                .withClient("normal-app")
                .authorizedGrantTypes("authorization_code", "implicit")
                .authorities("ROLE_CLIENT")
                .scopes("read","write")
                .resourceIds(resourceId)
                .accessTokenValiditySeconds(accessTokenValiditySeconds)//       
                .and()
                .withClient("trusted-app")
                .authorizedGrantTypes("client_credentials", "password")
                .authorities("ROLE_TRUSTED_CLIENT")
                .scopes("read", "write")
                .resourceIds(resourceId)
                .accessTokenValiditySeconds(accessTokenValiditySeconds)
                .secret("secret");

この方法を使用している場合、対応する認証コードの要求経路は以下の通りです.
http://localhost:8787/oauth/authorize?client_id=normal-app&response_type=code&scope=read&redirect_uri=/resources/user

該当するパラメータを照合してください
次にjwtのトークン方式を使用し、対応するリクエストパスは以下の通りです.
http://localhost:8787/oauth/token?code=r8YBUL&grant_type=authorization_code&client_id=normal-app&redirect_uri=/resources/user

これはメモリに格納するストレージ方式です
2.データベースから該当するフィールドのパラメータを読み込む必要がある場合は、次のように構成します.
  @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

        //   InMemoryTokenStore             ( ,         ,            )。            ,           ,                。
        // JdbcTokenStore      JDBC  ,               。                ,     JDBC  ,      ,           ,         ,         。   JdbcTokenStore   “spring-jdbc”    。

        //        jdbc       
        clients.withClientDetails(clientDetails());


    }

ここでは、以前のメモリからの読み取り方法を削除し、代わりにclientDetails()という方法を見て、この方法を見てみましょう.
 @Bean
    public ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }

このbeanを構成するだけでいいですが、私たちのdatasourceはymlプロファイルで構成されています.注入するだけです.
import javax.sql.DataSource;
@Resource
private DataSource dataSource;

しかし、ここではまだ終わっていません.まず、JdbcClientDetailsServiceがデータベースからどのように読み込まれたかについてお話しします.次のように、アクセスして対応するソースコードを表示できます.
public JdbcClientDetailsService(DataSource dataSource) {
        this.updateClientDetailsSql = DEFAULT_UPDATE_STATEMENT;
        this.updateClientSecretSql = "update oauth_client_details set client_secret = ? where client_id = ?";
        this.insertClientDetailsSql = "insert into oauth_client_details (client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove, client_id) values (?,?,?,?,?,?,?,?,?,?,?)";
        this.selectClientDetailsSql = "select client_id, client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove from oauth_client_details where client_id = ?";
        this.passwordEncoder = NoOpPasswordEncoder.getInstance();
        Assert.notNull(dataSource, "DataSource required");
        this.jdbcTemplate = new JdbcTemplate(dataSource);
        this.listFactory = new DefaultJdbcListFactory(new NamedParameterJdbcTemplate(this.jdbcTemplate));
    }

彼自身がデフォルトのフィールドを持つテーブルであり、対応するクエリーの方法があるため、sqlは以下のようなテーブルを作成する必要があります.
-- ----------------------------
-- Table structure for oauth_client_details            
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details` (
  `client_id` varchar(48) NOT NULL,
  `resource_ids` varchar(256) DEFAULT NULL,
  `client_secret` varchar(256) DEFAULT NULL,
  `scope` varchar(256) DEFAULT NULL,
  `authorized_grant_types` varchar(256) DEFAULT NULL,
  `web_server_redirect_uri` varchar(256) DEFAULT NULL,
  `authorities` varchar(256) DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL,
  `refresh_token_validity` int(11) DEFAULT NULL,
  `additional_information` varchar(4096) DEFAULT NULL,
  `autoapprove` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

これはデフォルトのクラスのテーブルです.一般的にはデフォルトでいいです.ここでは、上記のフィールドに基づいて関連する内容を構成する必要があります.以下のようにします.
ここで設定したアクセスパスは、次のとおりです.
//  :             
http://localhost:8787/oauth/token?client_id=normal-app&grant_type=authorization_code&code=1oCj8e&redirect_uri=http://localhost:8787/resources/user

トークンのアクセスパスは次のとおりです.
//         :
http://localhost:8787/resources/user?access_token=9d62c7b0-780e-4c6a-ad5a-56d79a089342

codeを前のステップで生成したcodeに置き換えるのを覚えています
3.落とし穴の回顧:
以前はトークンtokenを格納するためにjwtを使用していましたが、jwtokenはどうしても現れないことに気づきました.何度もチェックした結果、エラーが見つかりました.コードは以下の通りです.
package urity.demo.oauth2;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.AccessTokenConverter;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import urity.demo.service.RedisAuthenticationCodeServices;
import urity.demo.support.MyUserDetailService;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Resource
    private DataSource dataSource;

    @Value("${resource.id:spring-boot-application}")//   spring-boot-application
    private String resourceId;


    @Value("${access_token.validity_period:36000}")
    private int accessTokenValiditySeconds = 36000;

    //           security         
    @Resource
    private AuthenticationManager authenticationManager;

    @Resource
    private RedisAuthenticationCodeServices redisAuthenticationCodeServices;

    @Resource
    private MyUserDetailService myUserDetailService;


    //security
    //            。
    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')");
        oauthServer.checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')");

    }

    //              Bean
    @Bean
    public ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }


//    @Bean //   TokenStore  
//    public JdbcTokenStore jdbcTokenStore() {
//        return new JdbcTokenStore(dataSource);
//    }

    // ClientDetailsServiceConfigurer(     AuthorizationServerConfigurer)        JDBC             。         
    //clientId:(  )   ID。
    //secret:(       )     (   )。     
    //scope:       。          (   ),         。read write all
    //authorizedGrantTypes:            。     。
    //authorities         (   Spring Security    )。
    //                    (  ,      JdbcClientDetailsService)   ClientDetailsManager  (     ClientDetailsService   )          。
    //  :JDBC            (                )
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

        //   InMemoryTokenStore             ( ,         ,            )。            ,           ,                。
        // JdbcTokenStore      JDBC  ,               。                ,     JDBC  ,      ,           ,         ,         。   JdbcTokenStore   “spring-jdbc”    。


//        /**
//         * inMemory              
//         */
//        clients.inMemory()
//                //client Id
//                .withClient("normal-app")
//                .authorizedGrantTypes("authorization_code", "implicit")
//                .authorities("ROLE_CLIENT")
//                .scopes("read","write")
//                .resourceIds(resourceId)
//                .accessTokenValiditySeconds(accessTokenValiditySeconds)//       
//                .and()
//                .withClient("trusted-app")
//                .authorizedGrantTypes("client_credentials", "password")
//                .authorities("ROLE_TRUSTED_CLIENT")
//                .scopes("read", "write")
//                .resourceIds(resourceId)
//                .accessTokenValiditySeconds(accessTokenValiditySeconds)
//                .secret("secret");

        //        jdbc       
        clients.withClientDetails(clientDetails());


    }


    //AuthorizationEndpoint                 AuthorizationServerEndpointsConfigurer。     ,          ,    (            ,     )。           :
    //authenticationManager:           AuthenticationManager。
    //userDetailsService:     UserDetailsService      (  a GlobalAuthenticationManagerConfigurer),                    ,          
    //authorizationCodeServices:  AuthorizationCodeServices             (  )。
    //implicitGrantService:         。
    //tokenGranter:(TokenGranter               )
    // XML            authorization-server。

    /**
     * /oauth/authorize              ,
     *           ,
     *                              。
     *           UserApprovalHandler,
     * AuthorizationEndpoint           
     *
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(this.authenticationManager);
        endpoints.accessTokenConverter(accessTokenConverter());//jwt
        //          
//        endpoints.tokenStore(jdbcTokenStore());
        // jwt   
        endpoints.tokenStore(jwtStore());


        //     
        endpoints.authorizationCodeServices(redisAuthenticationCodeServices);


        endpoints.userDetailsService(myUserDetailService);


        //   TokenServices           uuid       jwt       jwt    
//        DefaultTokenServices tokenServices = new DefaultTokenServices();

        //        jdbc         
//        tokenServices.setTokenStore(endpoints.getTokenStore());

        //     jwt   token
        // tokenServices.setTokenStore(jwtStore());
//        tokenServices.setSupportRefreshToken(false);
//        tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
//        tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
//        tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30)); // 30 
//        endpoints.tokenServices(tokenServices);

        // @Bean
        //    RedisTokenStore redisTokenStore(){
        //        return new RedisTokenStore(redisConnectionFactory);
        //    }
        // endpoints.tokenStore(redisTokenStore());
    }

    //  jwttoken     
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {

        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter() {
            /**
             *     token   
             *           
             *
             */

            @Override
            public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {

                String userName = authentication.getUserAuthentication().getName();
                //          UserDetail       link{SecurityConfiguration}
                User user = (User) authentication.getUserAuthentication().getPrincipal();
                /**      token   ***/
                final Map additionalInformation = new HashMap<>();
                additionalInformation.put("userName", userName);
                additionalInformation.put("roles", user.getAuthorities());
                ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation);
                OAuth2AccessToken enhancedToken = super.enhance(accessToken, authentication);
                return enhancedToken;
            }

        };
        //    ,                      ,      RSA       
        accessTokenConverter.setSigningKey("123");
        return accessTokenConverter;

    }


    @Bean
    public TokenStore jwtStore() {

        TokenStore tokenStore = new JwtTokenStore(accessTokenConverter());

        return tokenStore;
    }


    /**
     *            token
     *
     * @return
     */
    @Bean
    public ResourceServerTokenServices defaultTokenServices() {
        final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenEnhancer(accessTokenConverter());
        defaultTokenServices.setTokenStore(jwtStore());
        return defaultTokenServices;
    }

}

注意:DefaultTokenServicesとjwttokenの構成はすべて存在しないでください.そうしないと、システムはDefaultTokenServicesの構成のみを探します.つまり、生成されたtokenは常にデフォルトのUUIDになります.ここでは、どちらかの構成をコードに選択するしかありません.
/   TokenServices           uuid       jwt       jwt    
//        DefaultTokenServices tokenServices = new DefaultTokenServices();

        //        jdbc         
//        tokenServices.setTokenStore(endpoints.getTokenStore());

        //     jwt   token
        // tokenServices.setTokenStore(jwtStore());
//        tokenServices.setSupportRefreshToken(false);
//        tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
//        tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
//        tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30)); // 30 
//        endpoints.tokenServices(tokenServices);

4.コツ:
パス認可後にcodeを取得すると、jwttokenをcodeで要求し、postmanでテストすることができます.
ここではbasic Authでname:normal-appを記入するだけでいいので、パスワードは記入しなくてもいいです.
http://localhost:8787/oauth/token?client_id=normal-app&grant_type=authorization_code&code=csTjhK&redirect_uri=http://localhost:8787/resources/user

得られたjsonを見てみましょう
ここで生成するjwtokenにはこれに対応するjwtの情報が携帯されているが、この文字列は実際にはHeaderとPayloadが暗号化してつなぎ合わせたものであり、それに応じて次のjwtの関連解析を見ることができる.
以下にアクセスできます.https://www.jsonwebtoken.io/このtokenの情報を解析します.
ここではHeaderとPayloadの情報を見ることができます.Headerは主にtypeと暗号化アルゴリズムを格納しています.ここではHS 256です.私たちは主にPayloadの情報を見ています.
{
 "aud": [
  "resourceId"
 ],
 "user_name": "test",
 "scope": [
  "read"
 ],
 "roles": [
  {
   "authority": "ROLE_USER"
  },
  {
   "authority": "admin"
  }
 ],
 "exp": 1532662701,
 "userName": "test",
 "authorities": [
  "admin",
  "ROLE_USER"
 ],
 "jti": "066cefa0-0a7a-40da-87a0-133c5a9c64d3",
 "client_id": "normal-app",
 "iat": 1532659101
}

ここではログインするユーザ名、tokenのライフサイクルなどを見ることができる.生成されたjwttokenが携帯する情報がどのようなものかをより明確に理解することができます.