blog

Spring Cloud OAuth2(JWTベース)マイクロサービス認証認証戦闘

はじめに\n\nソリューション\n本論文では、主に以下の解決策を使用します:\nステートレストークン認証方式を採用し、サーバ側でユーザのログイン状態を保存する必要がありません;\nスプリングセキュリテ...

Apr 20, 2020 · 21 min. read
シェア

入門

前章では、Spring Security Oauth2の基本的な理論知識とその使用シナリオを紹介しました。本章では、Oauth2を通してSpring Cloudでマイクロサービスの統合認証認可を実現する方法を紹介します。

ソリューション

この記事では以下のソリューションを使用します:

  • ステートレスのトークン認証方式に基づき、サーバーはユーザーのログイン状態を保存する必要がありません;
  • springセキュリティフレームワークとoauth2プロトコルビルドに基づいています;

なぜjwtアプローチを使うのですか?リクエストごとに認証・認可サービスをリモートで呼び出す必要性を避けるため、認証・認可サービスは一度だけバリデーションを行い、JWTを返します。返されたJWTには、パーミッションを含むユーザーに関するすべての情報が含まれています。

ケースプロジェクトアーキテクチャ

3作品:

  • eureka-server: 登録されたサービスセンター、ポート8888。
  • auth-server:認可を担当し、ユーザーにクライアントのclientIdとパスワード、および認可されたユーザーのユーザー名とパスワードを提供するよう要求します。これらの情報が正しく準備された後、auth-serviceはユーザーの基本情報と特権ポイント情報を含み、RSAによって暗号化されたJWTを返します。RSA暗号化。
  • auth-client: 認証クライアント、パブリック依存。他のすべてのリソースサービスは
  • user-server:リソースサービスとして、そのリソースは保護されており、アクセスするには対応する特権が必要です。user-serverサービスは、ユーザから要求されたJWTを取得し、まず公開鍵を通してJWTを復号し、JWTに対応するユーザの情報とユーザの特権情報を取得し、ユーザがリソースにアクセスする特権を持っているかどうかを判断します。
  • order-server:

エンジニアリング・アーキテクチャ図:

エンジニアリングの依存関係:

auth-serverプロジェクトのビルド

maven 依存関係の追加

新しいauth-serverモジュールを作成し、以下の依存関係を追加します:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://..org/POM/..0"
 xmlns:xsi="http://..org/2100"/XMLSchema-instance"
 xsi:schemaLocation="http://..org/POM/..0 http://..org/xsd/maven-...xsd">
 <parent>
 <artifactId>spring-cloud-oauth2</artifactId>
 <groupId>com.hxmec</groupId>
 <version>0.0.1-SNAPSHOT</version>
 </parent>
 <modelVersion>4.0.0</modelVersion>
 <artifactId>auth-server</artifactId>
 <dependencies>
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-oauth2</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-jdbc</artifactId>
 </dependency>
 <dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
 </dependency>
 <dependency>
 <groupId>com.alibaba</groupId>
 <artifactId>druid-spring-boot-starter</artifactId>
 </dependency>
 <dependency>
 <groupId>com.baomidou</groupId>
 <artifactId>mybatis-plus-boot-starter</artifactId>
 </dependency>
 <dependency>
 <groupId>p6spy</groupId>
 <artifactId>p6spy</artifactId>
 <version>3.8.5</version>
 </dependency>
 </dependencies>
 <build>
 <plugins>
 <plugin>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-maven-plugin</artifactId>
 </plugin>
 </plugins>
 </build>
</project>

データテーブルの作成

プロジェクトに必要な主なテーブルは以下の通りです:

テーブルの作成方法は以下の通りです:

-- クライアントアプリケーションの登録詳細
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), -- 認証コードモードでは、URIのリダイレクト後に認証コードを申請する。.
 authorities VARCHAR(256),
 access_token_validity INTEGER, -- 発行されたトークンの有効期限を設定する
 refresh_token_validity INTEGER, -- issuerefresh_tokenトークンの有効期間(未設定はリフレッシュと同時に発行されない)。_token)
 additional_information VARCHAR(4096),
 autoapprove VARCHAR(256) -- 認証コードモードで自動的に認証するためにtrueを設定する。
);
create table oauth_client_token (
 token_id VARCHAR(256),
 token BLOB,
 authentication_id VARCHAR(256) PRIMARY KEY,
 user_name VARCHAR(256),
 client_id 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)
);
create table oauth_refresh_token (
 token_id VARCHAR(256),
 token BLOB,
 authentication BLOB
);
-- 認証コードモードでは、発行された認証コードを格納する。
create table oauth_code (
 code VARCHAR(256), authentication BLOB
);
create table oauth_approvals (
	userId VARCHAR(256),
	clientId VARCHAR(256),
	scope VARCHAR(256),
	status VARCHAR(10),
	expiresAt DATETIME,
	lastModifiedAt DATETIME
);
CREATE TABLE `sys_user` (
 `id` bigint(32) NOT NULL,
 `username` varchar(100) DEFAULT NULL,
 `password` varchar(200) DEFAULT NULL,
 `enable_` tinyint(1) DEFAULT NULL,
 `email` varchar(50) DEFAULT NULL,
 `mobile` varchar(20) DEFAULT NULL,
 `del_flag` tinyint(1) DEFAULT NULL,
 `create_time` datetime DEFAULT NULL,
 `create_user` bigint(32) DEFAULT NULL,
 `modified_time` datetime DEFAULT NULL,
 `modified_user` bigint(32) DEFAULT NULL,
 PRIMARY KEY (`id`)
);
INSERT INTO `oauth_client_details`(`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('app', NULL, '$2a$10$JWSnLszKQANW7OF3p2b8IuIQXTVD8OUN//Q4l/sZGmzyaLEWnC5/u', 'server', 'password,authorization_code,refresh_token,client_credentials', NULL, NULL, 60000, 300, NULL, NULL);
INSERT INTO `oauth_client_details`(`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('order', NULL, '$2a$10$JWSnLszKQANW7OF3p2b8IuIQXTVD8OUN//Q4l/sZGmzyaLEWnC5/u', 'server', 'password,authorization_code,refresh_token,client_credentials', NULL, NULL, 60000, 300, NULL, NULL);
INSERT INTO `oauth_client_details`(`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('user', NULL, '$2a$10$JWSnLszKQANW7OF3p2b8IuIQXTVD8OUN//Q4l/sZGmzyaLEWnC5/u', 'server', 'password,authorization_code,refresh_token,client_credentials', NULL, NULL, 60000, 300, NULL, NULL);
INSERT INTO `sys_user` VALUES (1282941563927805954, 'trazen', '$2a$10$JWSnLszKQANW7OF3p2b8IuIQXTVD8OUN//Q4l/sZGmzyaLEWnC5/u', NULL, 'trazen@126.com', '18559756159', 0, '2020-07-14 15:34:39', NULL, '2020-07-14 15:40:45', NULL);

設定の追加

bootstrap.ymlの設定は次のとおりです。

server:
 port: 8889
spring:
 application:
 name: auth-server
logging:
 pattern:
 console: '%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{15} - %msg%n'
 config: classpath:logback-spring.xml
eureka:
 client:
 service-url:
 defaultZone: "http://localhost:8888"/eureka/

application.ymlの設定は以下の通りです:

# mybatis- plus 
mybatis-plus:
 # xmlカンマまたはセミコロンで区切られた複数のディレクトリをスキャンする。
 mapper-locations: classpath:mapper/*.xml
 # 以下の設定はデフォルト値であり、未設定のままでもよい。
 global-config:
 db-config:
 #主キータイプ AUTO:"データベースIDインクリメント" INPUT:"ユーザー入力ID",ID_WORKER:"グローバルユニークID ", UUID:"グローバルユニークID UUID";
 id-type: ASSIGN_ID
 configuration:
 # 自動ハンプネーミングルールマッピングを有効にするかどうか。:データベース・カラム名からJava属性キャメル命名への類似マッピング
 map-underscore-to-camel-case: true
 # マップが有効な場合、true を返す。:クエリデータが空の場合、フィールドはNULLを返す。,false:このクエリーデータが空でなければ、フィールドは隠される。
 call-setters-on-nulls: true
spring:
 datasource:
 #driver-class-name: com.mysql.cj.jdbc.Driver
 driver-class-name: com.p6spy.engine.spy.P6SpyDriver
 type: com.alibaba.druid.pool.DruidDataSource
 druid:
 url: jdbc:p6spy:mysql://192.8:3306/sc_oauth2?useUnicode=true&characterEncoding=utf-8
 username: root
 password: root
 # 初期接続
 initial-size: 10
 # 接続プールの最大数
 max-active: 100
 # 接続プールの最小数
 min-idle: 10
 # 接続を待つタイムアウトを設定する。
 max-wait: 60000
 # PSCacheを開き、接続ごとにPSCacheのサイズを指定する。
 pool-prepared-statements: true
 max-pool-prepared-statement-per-connection-size: 20
 # 閉じる必要のあるアイドル接続を検出する頻度をミリ秒単位で設定する。
 timeBetweenEvictionRunsMillis: 60000
 # 接続がプールに存在する最小時間をミリ秒単位で設定する。
 min-evictable-idle-time-millis: 300000
 validation-query: SELECT 1 FROM DUAL
 test-while-idle: true
 test-on-borrow: false
 test-on-return: false
 stat-view-servlet:
 enabled: false

カスタマイズされた UserDetailsService

  • クライアントは、トークンの発行を要求するために、実際にはspringセキュリティoauth2がカプセル化されたトークンのエンドポイントにアクセスしている、インターフェイスは、最終的に要求されたアカウント情報に基づいてユーザー情報を取得するUserDetailsServiceメソッドを呼び出します、ちょうどユーザー情報を取得することに注意してください、アカウント情報は、springセキュリティの検証に与えられます。セキュリティの検証に委ねられるので、ここではMysqlテーブルからユーザー情報を取得するようにインターフェイスを書き換えます。
  • UserDetailsServiceクラスは、ユーザー情報を返すことができますロードするユーザーのユーザー名によると、トークンを要求するユーザーであり、ユーザーのアカウントのパスワードの操作を検証するAuthenticationManagerは、ユーザーのアカウントのパスワードを完了するために検証する認証者を呼び出します。

インタフェース MyUserDetailsService.java

/**
 * 機能: UserDetailsServiceインターフェイスを継承する。
 * 
 * 
 */
public interface MyUserDetailsService extends UserDetailsService {
}

実装クラス MyUserDetailsServiceImpl

/**
 * 機能: カスタマイズされた UserDetailsService
 * 
 * 
 */
@Primary
@Service
@AllArgsConstructor
public class MyUserDetailsServiceImpl implements MyUserDetailsService {
 private final SysUserMapper sysUserMapper;
 @Override
 public AuthUserDetail loadUserByUsername(String username) throws UsernameNotFoundException {
 QueryWrapper<SysUser> wrapper = new QueryWrapper<>();
 wrapper.lambda().eq(SysUser::getUsername,username);
 SysUser sysUser = sysUserMapper.selectOne(wrapper);
 if(sysUser == null){
 throw new UsernameNotFoundException("ユーザーは存在しません");
 }else {
 return UserDetailConverter.convert(sysUser);
 }
 }
 public static class UserDetailConverter{
 static AuthUserDetail convert(SysUser user){
 return new AuthUserDetail(user);
 }
 }
}

認証と認可の構成

/**
 * 機能: spring security  
 * 
 * 
 */
@Configuration
@EnableWebSecurity
@AllArgsConstructor
public class Oauth2WebSecurityConfig extends WebSecurityConfigurerAdapter {
 private final MyUserDetailsService myUserDetailsService;
 /**
 * 認証マネージャを再インジェクトする。
 * @return
 * @throws Exception
 */
 @Override
 @Bean
 public AuthenticationManager authenticationManagerBean() throws Exception {
 return super.authenticationManagerBean();
 }
 /**
 * パスワード暗号化BCryptPasswordEncoderを注入する。
 * @return
 */
 @Bean
 public PasswordEncoder passwordEncoder() {
 return new BCryptPasswordEncoder();
 }
 @Override
 protected void configure(AuthenticationManagerBuilder auth) throws Exception {
 // カスタムメソッドを使用してユーザー情報をロードする
 auth.userDetailsService(myUserDetailsService)
 .passwordEncoder(passwordEncoder());
 }
 @Override
 protected void configure(HttpSecurity http) throws Exception {
 http
 .authorizeRequests()
 .anyRequest().authenticated()
 .and()
 .formLogin().and()
 .csrf().disable()
 .httpBasic();
 }
}

拡張トークン エンハンサー TokenEnhancer

Spring SecurityのOauth2の記事では、トークンを取得するデフォルトのインターフェイスはaccessToken、refreshTokenおよびその他の情報のみを取得することを知ることができます。実用的なアプリケーションのシナリオでは、また、ユーザーに関連するいくつかの情報を追加する必要がある場合があります、この時間は、デフォルトの戻り値は、TokenEnhancerの実装のニーズを満たすことができない受信し、それを達成するためにエンハンスメントメソッドを書き換えます。

MyTokenEnhancer.java

/**
 * 機能: 発行されたトークンのトークン情報を強化する
 * 
 * 
 */
public class MyTokenEnhancer implements TokenEnhancer {
 /**
 * クライアントモード
 */
 private final static String CLIENT_CREDENTIALS = "client_credentials";
 @Override
 public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
 //クライアントモードは強化されていない
 if (CLIENT_CREDENTIALS.equals(authentication.getOAuth2Request().getGrantType())) {
 return accessToken;
 }
 //キャリーを強化するフィールドを取得する。
 AuthUserDetail authUserDetail = (AuthUserDetail) authentication.getPrincipal();
 final Map<String, Object> additionalInfo = new HashMap<>(3);
 //トークンで運ばれるフィールドを追加する。
 additionalInfo.put("id", authUserDetail.getSysUser().getId());
 additionalInfo.put("username", authUserDetail.getSysUser().getUsername());
 additionalInfo.put("email", authUserDetail.getSysUser().getEmail());
 additionalInfo.put("mobile", authUserDetail.getSysUser().getMobile());
 DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;
 token.setAdditionalInformation(additionalInfo);
 ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
 return accessToken;
 }
}

認証認可サーバーの設定

カスタムTokenEnhancer、カスタムUserDetailService、再設定されたAuthenticationManager、データベーステーブルを作成するためのデータソースなど、認証センターのoauth2コンポーネントを設定します。

認証認可サーバーのコードは以下の通りです Oauth2AuthServerConfig.java

/**
 * 機能: oauth2 認証サーバー構成
 * 
 * 
 */
@Configuration
@EnableAuthorizationServer
@AllArgsConstructor
public class Oauth2AuthServerConfig extends AuthorizationServerConfigurerAdapter {
 private final AuthenticationManager authenticationManager;
 private final DataSource dataSource;
 private final MyUserDetailsService myUserDetailsService;
 @Override
 public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
 MyClientDetailsService clientDetailsService = new MyClientDetailsService(dataSource);
 clients.withClientDetails(clientDetailsService);
 }
 @Override
 public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
 }
 @Override
 public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
 // エンハンスメントチェーンにエンハンスメントトークンを設定する。
 TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
 enhancerChain.setTokenEnhancers(Arrays.asList(customTokenEnhancer(), jwtAccessTokenConverter()));
 endpoints.tokenStore(tokenStore())
 .authenticationManager(authenticationManager)
 // //トークンのリフレッシュ要求は
 .userDetailsService(myUserDetailsService)
 .tokenEnhancer(enhancerChain);
 }
 /**
 * トークンを保存するポリシーを変更する。デフォルトはメモリ・ポリシーであるため、jwtに変更する。
 * @return
 */
 @Bean
 public TokenStore tokenStore() {
 //トークン認証に基づく
 return new JwtTokenStore(jwtAccessTokenConverter());
 }
 @Bean
 public JwtAccessTokenConverter jwtAccessTokenConverter(){
 JwtAccessTokenConverter jat = new JwtAccessTokenConverter();
 // jwtこの鍵を使用して署名し、トークンを検証するサービスもこの鍵を使用して署名を検証する。
 jat.setSigningKey("hxmec");
 return jat;
 }
 /**
 * トークン発行のデフォルトフィールドはユーザー名とROLEだけなので、カスタムトークンエンハンサーを追加して、追加情報を持つトークンの発行を実装する。
 * @return
 */
 @Bean
 public TokenEnhancer customTokenEnhancer() {
 //カスタム実装
 return new MyTokenEnhancer();
 }
}

認証認可サーバーテスト

sys_user のテストデータは以下の通りです: username:trazen password:321456

具体的なテスト手順は以下の通りです:トークンのエンドポイントアドレスを取得する: http://{ip}:{port}/oauth/token

返されたaccess_tokenkenは、jwtのウェブサイト jwt.io/#decoded-jw... で解析することができます 。

auth-clientプロジェクトの構築

このプロジェクトは主に、リソースサーバー関連の設定を一般的な依存関係としてカプセル化します。

maven 依存関係の追加

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://..org/POM/..0"
 xmlns:xsi="http://..org/2100"/XMLSchema-instance"
 xsi:schemaLocation="http://..org/POM/..0 http://..org/xsd/maven-...xsd">
 <parent>
 <artifactId>spring-cloud-oauth2</artifactId>
 <groupId>com.hxmec</groupId>
 <version>0.0.1-SNAPSHOT</version>
 </parent>
 <modelVersion>4.0.0</modelVersion>
 <artifactId>auth-client</artifactId>
 <dependencies>
 <!-- security ouath2 -->
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-oauth2</artifactId>
 </dependency>
 <!--コンフィギュレーション・ファイル・プロセッサをインポートする。>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-configuration-processor</artifactId>
 <optional>true</optional>
 </dependency>
 <dependency>
 <groupId>javax.servlet</groupId>
 <artifactId>javax.servlet-api</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-openfeign</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.security</groupId>
 <artifactId>spring-security-web</artifactId>
 <version>5.3.3.RELEASE</version>
 </dependency>
 </dependencies>
 <build>
 <plugins>
 <plugin>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-maven-plugin</artifactId>
 </plugin>
 </plugins>
 </build>
</project>

トークンパーサーの書き換え

MyUserAuthenticationConverter.javaのコードは次のとおりです。

/**
 * 機能: checkTokenの結果に従って、トークン・パーサーをユーザー情報に書き換える。
 * 
 * 
 */
public class MyUserAuthenticationConverter implements UserAuthenticationConverter {
 private static final String N_A = "N/A";
 @Override
 public Map<String, ?> convertUserAuthentication(Authentication userAuthentication) {
 return null;
 }
 @Override
 public Authentication extractAuthentication(Map<String, ?> map) {
 if (!map.containsKey(USERNAME)){
 return null;
 }else{
 CurrentUser user = CurrentUser.builder()
 .id((Long) map.get("id"))
 .username((String) map.get(USERNAME))
 .email((String) map.get("email"))
 .mobile((String) map.get("mobile"))
 .build();
 // 許可情報があれば、それをフォーマットする。
 if (map.containsKey("authorities") && map.get("authorities") != null){
 Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
 user.setAuthorities(authorities);
 return new UsernamePasswordAuthenticationToken(user, N_A,authorities);
 }else {
 return new UsernamePasswordAuthenticationToken(user, N_A,null);
 }
 }
 }
 private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
 Object authorities = map.get(AUTHORITIES);
 if (authorities instanceof String) {
 return AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities);
 }
 if (authorities instanceof Collection) {
 return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils
 .collectionToCommaDelimitedString((Collection<?>) authorities));
 }else if (authorities == null){
 }
 throw new IllegalArgumentException("Authorities must be either a String or a Collection");
 }
}

偽インターフェース呼び出しのトークン受け渡し問題の解決

主なコードは以下の通りです: MyFeignClientInterceptor.java

/**
 * 機能: OAuth2FeignRequestInterceptorを拡張する
 * タスクをスケジューリングするような特別なシナリオがFeignインターフェースを呼び出す場合。
 * 指定されたヘッダーをフィルタリングすることで、accessTokenContextRelay を防ぐことができる。.copyToken() 
 * 
 * 
 */
public class MyFeignClientInterceptor extends OAuth2FeignRequestInterceptor {
 private final OAuth2ClientContext oAuth2ClientContext;
 private final AccessTokenContextRelay accessTokenContextRelay;
 /**
 * Default constructor which uses the provided OAuth2ClientContext and Bearer tokens
 * within Authorization header
 *
 * @param oAuth2ClientContext provided context
 * @param resource type of resource to be accessed
 * @param accessTokenContextRelay
 */
 public MyFeignClientInterceptor(OAuth2ClientContext oAuth2ClientContext
 , OAuth2ProtectedResourceDetails resource, AccessTokenContextRelay accessTokenContextRelay) {
 super(oAuth2ClientContext, resource);
 this.oAuth2ClientContext = oAuth2ClientContext;
 this.accessTokenContextRelay = accessTokenContextRelay;
 }
 /**
 * Create a template with the header of provided name and extracted extract
 * 1. 非ウェブリクエストを使用している場合、ヘッダーの違いは以下のようになる。
 * 2. 認証に従って要求されたトークンを復元する。
 *
 * @param template
 */
 @Override
 public void apply(RequestTemplate template) {
 accessTokenContextRelay.copyToken();
 if (oAuth2ClientContext != null
 && oAuth2ClientContext.getAccessToken() != null) {
 super.apply(template);
 }
 }
}

リソースサーバーの構成

コードは次のとおりです。

/**
 * 機能: リソースサーバーの構成
 * 
 * 
 */
@Slf4j
@Configuration
@EnableResourceServer
@AllArgsConstructor
@ComponentScan("com.hxmec.auth")
@EnableConfigurationProperties(AuthClientProperties.class)
public class MyResourceServerConfig extends ResourceServerConfigurerAdapter {
 private final MyAuthenticationEntryPoint baseAuthenticationEntryPoint;
 private final AuthClientProperties authClientProperties;
 private final RestTemplate lbRestTemplate;
 @Override
 public void configure(HttpSecurity http) throws Exception {
 http.csrf().disable();
 // swagger uiをリリースする
 http.authorizeRequests().antMatchers(
 "/v2/api-docs",
 "/swagger-resources/configuration/ui",
 "/swagger-resources",
 "/swagger-resources/configuration/security",
 "/swagger-ui.html",
 "/webjars/**",
 "/api/**/v2/api-docs")
 .permitAll();
 // カスタム設定URLリリースによる。
 if (authClientProperties.getIgnoreUrls() != null){
 for(String url: authClientProperties.getIgnoreUrls()){
 http.authorizeRequests().antMatchers(url).permitAll();
 }
 }
 // 他のすべてのリクエストは、アクセスするためにトークンを必要とする。
 http.authorizeRequests().anyRequest().authenticated();
 }
 @Override
 public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
 if (authClientProperties.getResourceId() != null) {
 resources.resourceId(authClientProperties.getResourceId());
 }
 // ここでの署名キーは認証センターと一致している。
 if (authClientProperties.getSigningKey() == null) {
 log.info("SigningKey is null cant not decode token.......");
 }
 DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
 accessTokenConverter.setUserTokenConverter(new MyUserAuthenticationConverter());
 JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
 //jwtを解析するキーを設定する。
 converter.setSigningKey(authClientProperties.getSigningKey());
 converter.setVerifier(new MacSigner(authClientProperties.getSigningKey()));
 MyTokenServices tokenServices = new MyTokenServices();
 // CustomTokenServicesに3つの依存関係を注入する。
 //トークン保存ポリシーを設定する
 tokenServices.setTokenStore(new JwtTokenStore(converter));
 tokenServices.setJwtAccessTokenConverter(converter);
 tokenServices.setDefaultAccessTokenConverter(accessTokenConverter);
 tokenServices.setRestTemplate(lbRestTemplate);
 resources.tokenServices(tokenServices)
 .authenticationEntryPoint(baseAuthenticationEntryPoint);
 }
}

リソースサーバーアプリケーションuser-server/order-serverプロジェクトの構築

リソース・サーバー・アプリケーションのビルド手順は同じで、order-serverは主にfeignインターフェース・コール経由でトークンを渡す機能を示しています。

maven 依存関係の追加

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://..org/POM/..0"
 xmlns:xsi="http://..org/2100"/XMLSchema-instance"
 xsi:schemaLocation="http://..org/POM/..0 http://..org/xsd/maven-...xsd">
 <parent>
 <artifactId>spring-cloud-oauth2</artifactId>
 <groupId>com.hxmec</groupId>
 <version>0.0.1-SNAPSHOT</version>
 </parent>
 <modelVersion>4.0.0</modelVersion>
 <artifactId>user-server</artifactId>
 <dependencies>
 <dependency>
 <groupId>com.hxmec</groupId>
 <artifactId>auth-client</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 </dependency>
 <!-- SpringRetry 再試行フレームワークの依存関係>
 <dependency>
 <groupId>org.springframework.retry</groupId>
 <artifactId>spring-retry</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
 </dependency>
 </dependencies>
 <build>
 <plugins>
 <plugin>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-maven-plugin</artifactId>
 </plugin>
 </plugins>
 </build>
</project>

Spring Bootスタートアップクラスの作成

UserServerApplication.java

/**
 * 機能: User Server 
 * 
 * 
 */
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class UserServerApplication {
 public static void main(String[] args) {
 SpringApplication.run(UserServerApplication.class, args);
 }
}

設定の追加

bootstrap.ymlの設定

server:
 port: 8890
spring:
 application:
 name: user-server
 cloud:
 loadbalancer:
 retry:
 #リトライメカニズムを有効にする
 enabled: true
logging:
 pattern:
 console: '%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{15} - %msg%n'
 config: classpath:logback-spring.xml
eureka:
 client:
 service-url:
 defaultZone: "http://localhost:8888"/eureka/

application.yml 設定

hx:
 oauth2:
 client:
 # jwt 
 signingKey: hxmec
 resourceId: ${spring.application.name}
 # リリースURL
 ignoreUrls:
 - /oauth/**
 - /user/**
#ribbonグローバル構成
ribbon:
 #リクエスト処理のタイムアウト時間(単位:ms、デフォルト1000
 ReadTimeout: 3000
 #接続確立のタイムアウト(単位:ミリ秒、デフォルト1000
 ConnectTimeout: 3000
feign:
 compression:
 request:
 #GZIP圧縮を有効または無効にする。true: 有効、false: 無効。
 enabled: true
 #圧縮サポート MIME TYPE
 mime-types: text/xml,application/xml,application/json
 #データの最小値を圧縮する
 min-request-size: 2048
 response:
 #レスポンスのGZIP圧縮を有効にするかどうか、true:有効、false:無効。
 enabled: true
 client:
 config:
 #feignグローバル構成
 default:
 #ロギングレベルを指定する。none: ロギングなし、basic: リクエストメソッド、URL、レスポンスステータスコード、実行時間のみがログに記録される。
 #headersフル:リクエストとレスポンスのヘッダー、ボディ、メタデータを記録する。
 loggerLevel: basic
 #feignクライアント構成を指定する。すなわち、指定された起動されたサービスに対してのみ機能する。
 eureka-client:
 loggerLevel: full

テストインターフェイスの作成

UserController.java

/**
 * 機能: 
 * 
 * 
 */
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
 @GetMapping("/a")
 @PreAuthorize("isAuthenticated()")
 public String get(@AuthenticationPrincipal CurrentUser currentUser){
 return "1";
 }
 @GetMapping("/b")
 public String get02(){
 log.info("---------------->{}",SecurityUtils.getCurrentUser());
 return SecurityUtils.getCurrentUser().getUsername();
 }
 @GetMapping("/c")
 public String get03(@AuthenticationPrincipal CurrentUser currentUser){
 Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
 log.info("---------------->{}",currentUser);
 return "3";
 }
}

order-serverの構築で上記のようにデプロイします。

Feignインターフェース・コールのデモ・コードは以下の通り:

/**
 * 機能:
 * 
 * 
 */
@FeignClient(value = "user-server")
public interface UserFeignApi {
 /**
 * userserviceb インターフェース
 * @return
 */
 @GetMapping("/user/b")
 String get02();
}
/**
 * 機能: 
 * 
 * 
 */
@RestController
@RequestMapping("/order")
@Slf4j
@AllArgsConstructor
public class OrderController {
 private final UserFeignApi userFeignApi;
 @GetMapping("/a")
 public String get02(@AuthenticationPrincipal CurrentUser currentUser){
 log.info("---------------->{}", SecurityUtils.getCurrentUser());
 return "1";
 }
 @GetMapping("/b")
 public String getFeign(){
 return userFeignApi.get02();
 }
}

プロジェクトのテスト開始

  • 1.認証認可サービス・インターフェースを通じたトークンの取得

  • 2.ユーザー・サーバー・インターフェースにアクセスするためのトークンを携帯します。 トークンが有効な場合、結果は正常に返されます。トークンが無効な場合、カスタマイズされた認証期限切れメッセージが返されます。

  • 3.feignインターフェイス呼び出しがトークンを渡すことを実証するために、オーダーサーバーインターフェイスをご覧ください。

プロジェクトアドレス:

github.com/ty197287300...

Read next

データ構造とアルゴリズム - 並べ替えと検索のアルゴリズム

ソート:乱れた配列を昇順または降順の配列にすること。ソートアルゴリズムは、バブルソート、選択ソート、挿入ソート、サブサンプションソート、クイックソート... 検索: 配列内の要素の添え字を検索します。配列の indexOf メソッド。検索アルゴリズム:シーケンシャル検索、バイナリ検索...

Apr 20, 2020 · 4 min read