SpringSecurity OAuth2(パスワードモード)を構築するためのステップバイステップのエントリ

OAuth2とは何ですか?

それは、オープンスタンダードの認可できるように設計され、ユーザーができるように、サードパーティ製のアプリケーションをアクセスユーザーが変更するために、サーバー内の特定の民間の資源を、しかし、サードパーティのアプリケーションにサーバーにアカウントパスワードで提供されない場合があります

システムのリソースが保護されている場合、他のサードパーティアプリケーションがこれらの保護されたリソースにアクセスするためにOAuth2プロトコルを使用する必要があることを意味します。これは通常、他のWebサイトでQQを使用するなど、一部の許可されたログインシナリオで使用されます。 WeChatログインなど

OAuth2のいくつかの役割

ユーザー:サードパーティのアプリケーション(または独自のアプリケーション)を使用するユーザー。

クライアント:ユーザーが使用するサードパーティのアプリケーション。

認証(承認)サーバー:トークンの発行を担当するサービスと、保護されたリソースにアクセスするクライアントは、認証サーバーに情報を登録する必要があります。

リソースサーバー:保護されたAPIリソース。

 写真を使用して、これら4つの役割の関係を説明できます。

 Spring Security OAuth2を使用して、認証サーバーとリソースサーバーを構築します

1.認証サーバーを構築します

現在のプロジェクトの多くはマイクロサービスアーキテクチャプロジェクトです。APIリソースを提供するマイクロサービスごとに、それはリソースサーバーであり、クライアントが各マイクロサービスにアクセスするときの認証サーバーは認証サーバーです。訪問するトークンのみを取得します。

各マイクロサービスの依存関係を追加します。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>

サービスの1つは、認証構成をセットアップするための認証サーバーとして使用されます。

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private AuthenticationManager authenticationManager;

    /**
     * 配置authenticationManager用于认证的过程
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .authenticationManager(authenticationManager);
    }

    /**
     * 重写此方法用于声明认证服务器能认证的客户端信息
     * 相当于在认证服务器中注册哪些客户端(包括资源服务器)能访问
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("orderApp")  //声明访问认证服务器的客户端
                .secret(passwordEncoder.encode("123456"))  //客户端访问认证服务器需要带上的密码
                .scopes("read","write")  //获取token包含的哪些权限
                .accessTokenValiditySeconds(3600)  //token过期时间
                .resourceIds("order-service")  //指明请求的资源服务器
                .authorizedGrantTypes("password")  //密码模式
                .and()
                //资源服务器拿到了客户端请求过来的token之后会请求认证服务器去判断此token是否正确或者过期
                //所以此时的资源服务器对于认证服务器来说也充当了客户端的角色
                .withClient("order-service")
                .secret(passwordEncoder.encode("123456"))
                .scopes("read")
                .accessTokenValiditySeconds(3600)
                .resourceIds("order-service")
                .authorizedGrantTypes("password");
    }


    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.checkTokenAccess("isAuthenticated()");
    }
}

最初に@EnableAuthorizationServerアノテーションを追加して、これが認証サーバーであることを宣言します。次に、2番目に書き直されたconfigureメソッドでクライアント情報を設定します。これは、登録されたクライアントのみが認証サーバーにアクセスできるのと同等であり、クライアントはこれらを伝送します。情報とユーザーアカウントのパスワードおよびその他の情報は、認証サーバーへの要求を開始します。ここではパスワードモードになっているため(パスワードモードは通常、APIのセキュリティを保護するために使用されます)、もちろん、ユーザー名とパスワードを確認する必要があります。これらの確認プロセスでは、 AuthenticationManager(最初に書き直されたconfigureメソッド)、およびAuthenticationManagerの取得元も、セットアップする必要があります。SpringSecurityのユーザー情報の認証に関連する新しい構成クラスを作成します。

@Configuration
@EnableWebSecurity
public class OAuth2AuthWebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
        .passwordEncoder(passwordEncoder);
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

}

UserDetailServiceImpl:

@Component
public class UserDetailServiceImpl implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    //认证的过程,由AuthenticationManager去调,从数据库中查找用户信息
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return User.withUsername(username)
                .password(passwordEncoder.encode("123456"))
                .authorities("ROLE_ADMIN")
                .build();
    }
}

アプリケーションを起動し、postmanを使用してhttp:// localhost:9000 / auth / tokenにアクセスします

ユーザー名とパスワードはログインユーザー名とパスワードです。データベースに保存されているユーザー名とパスワードと一致しない場合、認証は失敗します。 、Grant_typeはパスワードであり、現在のモードがパスワードであり、スコープが要求されたアクセス許可であることを示します。

受信した応答。access_tokenは認証が成功した後に認証サーバーから返されるトークンであり、expires_inは有効期限が切れる残りの時間です。リソースサーバーをリクエストするたびにトークンを持参する必要があります。その後、リソースサーバーは認証サーバーにトークンが正しいかどうかを確認するようにリクエストします。このプロセスでは、リソースサーバーのリソースにアクセスできるかどうかは、リソースサーバーが信頼する認証サーバーによって異なります。

1.2クライアント情報とトークンをデータベースに保存します

この時点で、認証サーバーは完成していますが、上記のクライアントの情報とトークンはメモリに保存されています。これは明らかに実際の使用には適していないため、データベースに保存できます。

データベースに対応するテーブルを作成します。

create table oauth_client_details (
  client_id VARCHAR(256) PRIMARY KEY,
  resource_ids VARCHAR(256),
  client_secret VARCHAR(256),
  scope VARCHAR(256),
  authorized_grant_types VARCHAR(256),
  web_server_redirect_uri VARCHAR(256),
  authorities VARCHAR(256),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additional_information VARCHAR(4096),
  autoapprove VARCHAR(256)
);

create table oauth_access_token (
  token_id VARCHAR(256),
  token BLOB,
  authentication_id VARCHAR(256) PRIMARY KEY,
  user_name VARCHAR(256),
  client_id VARCHAR(256),
  authentication BLOB,
  refresh_token VARCHAR(256)
);

クライアント情報をoauth_client_detailsテーブルに追加します。

データベースの依存関係を追加します。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

認証サーバーの構成を変更します。

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private DataSource dataSource;

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

    /**
     * 配置authenticationManager用于认证的过程
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                //设置tokenStore,生成token时会向数据库中保存
                .tokenStore(tokenStore())
                .authenticationManager(authenticationManager);
    }

    /**
     * 重写此方法用于声明认证服务器能认证的客户端信息
     * 相当于在认证服务器中注册哪些客户端(包括资源服务器)能访问
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource);
    }


    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.checkTokenAccess("isAuthenticated()");
    }
}

このとき、認証サーバーを再起動すると、クライアントがトークンリクエストを開始したときに生成されたトークンがデータベースに保存され、認証サーバーが再起動すると、同じクライアントが再度リクエストし、データベース内のトークンが判定されます。トークンの有効期限が切れているかどうか、そうでない場合は直接クライアントに返します。有効期限が切れていない場合は、トークンを再生成し、データベース内の古いトークンを更新します。

2.リソースサーバーを構築します

新しいmavenプロジェクトを作成し、OAuth2依存関係を追加して、リソースサーバーを構成します。

@Configuration
@EnableResourceServer  //声明该服务是一个资源服务器
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        //声明该资源服务器的id,当请求过来是会首先判断token是否有访问该资源服务器的权限
        resources.resourceId("order-service");
    }

    /**
     * 设置访问权限需要重写该方法
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //访问post请求的接口必须要有write权限
                .antMatchers(HttpMethod.POST).access("#oauth2.hasScope('write')")
                //访问get请求的接口必须要有read权限
                .antMatchers(HttpMethod.GET).access("#oauth2.hasScope('read')");
    }
}

 

@Configuration
//资源服务器拿到token之后需要向认证服务器发出请求判断该token是否正确,开启SpringSecurity认证的过程
@EnableWebSecurity
public class OAuth2ResourceWebSecurityConfig extends WebSecurityConfigurerAdapter {


    @Bean
    public ResourceServerTokenServices tokenServices() {
        RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
        //认证时资源服务器就相当于客户端,需要向认证服务器声明自己的信息是否匹配
        remoteTokenServices.setClientId("order-service");
        remoteTokenServices.setClientSecret("123456");
        remoteTokenServices.setCheckTokenEndpointUrl("http://localhost:9000/oauth/check_token");
        return remoteTokenServices;
    }


    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        OAuth2AuthenticationManager oAuth2AuthenticationManager = new OAuth2AuthenticationManager();
        oAuth2AuthenticationManager.setTokenServices(tokenServices());
        return oAuth2AuthenticationManager;
    }
}

2.1認証後にユーザー情報を取得する

リソースサーバーの一部のインターフェイスで認証ユーザー情報を使用する場合があり、SpringSecurity OAuth2はそれを取得するための対応するメソッドも提供します。たとえば、次のようにインターフェイスパラメータに@AuthenticationPricipalアノテーションを追加できます。

@PostMapping("/create")
    public OrderInfo order(@RequestBody OrderInfo orderInfo, @AuthenticationPrincipal String username){
        PriceInfo priceInfo = restTemplate.getForObject("http://localhost:9002/price/get", PriceInfo.class);
        orderInfo.setPrice(priceInfo.getPrice());
        System.out.println("order() username is ===================" + username);
        return orderInfo;
    }

トークンにはユーザー名が含まれているため、デフォルトではユーザー名のみを取得できます。ユーザーオブジェクト全体の情報を取得する場合は、UserDetailServiceを使用して、ユーザー名に基づいてデータベースにクエリを実行し、構成クラスを変更できます。

@Configuration
//资源服务器拿到token之后需要向认证服务器发出请求判断该token是否正确,开启SpringSecurity认证的过程
@EnableWebSecurity
public class OAuth2ResourceWebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    public ResourceServerTokenServices tokenServices() {
        RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
        //认证时资源服务器就相当于客户端,需要向认证服务器声明自己的信息是否匹配
        remoteTokenServices.setClientId("order-service");
        remoteTokenServices.setClientSecret("123456");
        remoteTokenServices.setCheckTokenEndpointUrl("http://localhost:9000/oauth/check_token");
        remoteTokenServices.setAccessTokenConverter(getAccessTokenConverter());
        return remoteTokenServices;
    }

    //设置了AccessTokenConverter就能在认证之后通过token里面的username去调用UserDetailsService去数据库查找出完整的用户信息了
    private AccessTokenConverter getAccessTokenConverter() {
        DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
        DefaultUserAuthenticationConverter userTokenConverter = new DefaultUserAuthenticationConverter();
        userTokenConverter.setUserDetailsService(userDetailsService);
        accessTokenConverter.setUserTokenConverter(userTokenConverter);
        return accessTokenConverter;
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        OAuth2AuthenticationManager oAuth2AuthenticationManager = new OAuth2AuthenticationManager();
        oAuth2AuthenticationManager.setTokenServices(tokenServices());
        return oAuth2AuthenticationManager;
    }
}

UserDetailServiceを追加した後、リソースサーバーは内部でloadUserByUsernameメソッドを呼び出し、認証が成功した後に認証サーバーにトークンを送信した後、ユーザーオブジェクトを返します。ユーザーオブジェクトは、次の時点でインターフェイスメソッドパラメーターによって取得できます。

@PostMapping("/create")
    public OrderInfo order(@RequestBody OrderInfo orderInfo, @AuthenticationPrincipal User user){
        PriceInfo priceInfo = restTemplate.getForObject("http://localhost:9002/price/get", PriceInfo.class);
        orderInfo.setPrice(priceInfo.getPrice());
        System.out.println("order() user is ===================" + user);
        return orderInfo;
    }

このオブジェクトに特定の属性が必要な場合は、注釈にel式を使用できます。

@PostMapping("/create")
    public OrderInfo order(@RequestBody OrderInfo orderInfo, @AuthenticationPrincipal(expression = "#this.id") Long id){
        PriceInfo priceInfo = restTemplate.getForObject("http://localhost:9002/price/get", PriceInfo.class);
        orderInfo.setPrice(priceInfo.getPrice());
        System.out.println("order() useId is ===================" + id);
        return orderInfo;
    }

それ以来、SpringSecurityOAuth2を使用して構築された単純な認証サーバーとリソースサーバーが完成しました。

おすすめ

転載: blog.csdn.net/weixin_37689658/article/details/104149499
おすすめ