目次
I.はじめに
この記事では、ログイン時にトークンを作成し、ゲートウェイでトークンを確認し、ユーザー情報をヘッダーリクエストヘッダーに抽出して、ダウンストリームのビジネスシステムに送信する方法について説明します。
このプロジェクトでは、マイクロサービスの統合ルーティングのゲートウェイとしてSpring Cloud Gatewayを使用しています。認証認証モジュールはトークンの生成を担当し、ゲートウェイゲートウェイモジュールはトークンの検証と入口の統合を担当します。
ゲートウェイの役割は、APIサービスへのアクセスを保護、拡張、および制御するためのAPIアーキテクチャとしての役割です。APIゲートウェイは、アプリケーションまたはサービス(REST APIインターフェイスサービスを提供)の前にあるシステムであり、承認、アクセス制御、トラフィック制限などを管理するために使用されます。これにより、REST APIインターフェイスサービスはAPIゲートウェイによって保護され、すべての呼び出し元に対して透過的になります。 。したがって、APIゲートウェイの背後に隠されたビジネスシステムは、これらの戦略的インフラストラクチャを処理する代わりに、サービスの作成と管理に集中できます。
1. Spring CloudGatewayの概要
スプリングクラウドゲートウェイは公式れるスプリングゲートウェイは、以下のような技術に基づいて開発されたスプリング5.0、スプリングブート2.0とプロジェクト炉。スプリングクラウドゲートウェイはmicroserviceアーキテクチャの管理方法をルーティングする単純かつ効果的な統一されたAPIを提供することを目的とします。Spring Cloudエコシステムのゲートウェイとして、Spring Cloud GatewayはZUULの置き換えを目指しています。これは、統一されたルーティング方法を提供するだけでなく、セキュリティ、監視/埋め込みポイント、電流制限など、フィルタチェーンに基づくゲートウェイの基本機能も提供します。待つ。
2.トークンの概要
JWTは、実際には3つの部分から構成された文字列であり、ヘッダ、ペイロード、および署名。これら三つの部品はすべてJSON形式です。
詳細については、https://blog.csdn.net/a1036645146/article/details/103726635を参照してください。
2.1ヘッダー
ヘッダーは、タイプや署名アルゴリズムなど、JWTに関する最も基本的な情報を説明するために使用されます。
{
"typ": "JWT",
"alg": "HS256"
}
ここでは、これがJWTであり、使用する署名アルゴリズムがHS256アルゴリズムであることを説明しました。
2.2ペイロード
ペイロードを使用して、機密性の低い情報を入力できます。
{
"iss": "John Wu JWT",
"iat": 1441593502,
"exp": 1441594722,
"aud": "www.example.com",
"sub": "[email protected]",
"from_user": "B",
"target_user": "A"
}
最初の5つのフィールドは、JWT標準によって定義されています。
iss
:JWTの発行者sub
:このJWTの対象となるユーザーaud
:JWTを受け取る当事者exp
(期限切れ):いつ期限切れになりますか?これがUnixのタイムスタンプですiat
(発行日):いつ発行されたか
ヘッダーとペイロードをそれぞれBase64エンコードした後、2つの文字列が取得.
され、エンコードされた2つの文字列が英語のピリオド(頭から先)で接続されて、新しい文字列が形成されます。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0
2.3署名
最後に、HS256アルゴリズムを使用して、上記のスプライスされた文字列を暗号化します。暗号化するときは、秘密も提供する必要があります。暗号化されたコンテンツも文字列であり、最後の文字列が署名です。この署名を文字列の後ろに接続することで、完全なjwtを取得できます。ヘッダー部分とペイロード部分が改ざんされた場合、改ざん者はキーが何であるかを認識せず、新しい署名部分を生成できないため、サーバーは通過できません。jwtでは、メッセージ本文は透過的であり、署名はメッセージが透過的でないことを保証できます。改ざんされています。
もちろん、誰かのトークンが他人に盗まれた場合、私はそれを助けることはできません。また、泥棒は正当なユーザーであると思います。これは、実際には、他人に盗まれた人のセッションIDと同じです。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJyb2xlcyI6InB1cmNoYXNlciIsInNvdXJjZSI6IlBDIiwidXNlck5hbWUiOiIxNTYyMjEzMzIzMSIsImV4cCI6MTYwNDgwMDM5NSwidXNlcklkIjoiMTE5MCJ9
.zpM6dyakS1N6kySMIEHuOZfN8l4WlybRbq6VK1cDqGc
2.ログイン認証プロセス
ユーザーが正常にログインすると、認証サービス(authモジュール)がトークンを生成し、後続のすべてのフロントエンド要求はこのトークンを要求ヘッダーに入れます。ゲートウェイは、トークンを検証し、ユーザー情報を要求のヘッダーに配置して、ダウンストリームシステムがユーザー情報を簡単に取得できるようにします。
トークン認証に基づく基本的な認証プロセスは次のとおりです。
- ユーザーはログイン情報を入力し(またはトークンインターフェイスを呼び出してユーザー情報を渡し)、認証のために認証サービスに送信します。注:ID認証サービスは、マイクロサービスの分割に応じて、サーバーと一緒にすることも、分離することもできます。ここに単一の認証モジュールがあります。
- 認証サービス(auth)は、ログイン情報が正しいかどうかを確認し、正しい場合はトークンを返します(一般的なインターフェイスには、ユーザーの基本情報、権限の範囲、有効時間などの情報が含まれます)。
- クライアントはトークンを保存し、別のインターフェイスを再度要求すると、トークンをHTTP要求ヘッダーに配置して、関連するAPI呼び出しを開始できます。
- バックエンドインターフェイスにアクセスするには、クライアントはゲートウェイを通過する必要があります。ゲートウェイはインターセプターを均一に構成し、トークンの権限を確認してから、対応するバックエンドの他のマイクロサービスインターフェイスに転送します。
- 検証に合格すると、サーバーは関連するリソースとデータを返します。
プロジェクトには次のモジュールが含まれます。
- ゲートウェイ-ゲートウェイサービス
- auth-認証サービス
- ユーザーサービス
- 注文サービス
- (その他のマイクロサービス)..。
各モジュールアプリケーションはSpringBootに基づいて構築され、構成センターおよび登録センターとしてNacosを使用し、サービス間のインターフェイス呼び出しにFeignを使用し、Httpインターフェイス(REST API)を公開し、API統合入口としてゲートウェイを使用します。フロントエンド要求はダウンストリームマイクロサービスを直接呼び出すことはできません。特定のインターフェース。
3.実際の戦闘を計画する
1.認証認証サービス
SpringBootを介して認証認証サービスモジュールを構築し、登録および構成センターでNacosを使用します。
このモジュールは主にログインインターフェイスを提供し、トークンを生成します。
1.1 pom.xml
jwtツールキットをインポートし、最新の安定バージョンを使用する必要があります。
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>${jwt.version}</version>
</dependency>
1.2JWTUtils.javaツールクラス
/**
* jwt工具类
*
* @author stwen_gan
* @since
*/
public class JWTUtils {
// token 签名的秘钥,可设置到配置文件中
private static final String SECRET_KEY = "secretKey:123456";
// token过期时间
public static final long TOKEN_EXPIRE_TIME = 7200 * 1000;
/**
* 生成jwt
*/
public String createJwt(String userId){
Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);
//设置头信息
HashMap<String, Object> header = new HashMap<>(2);
header.put("typ", "JWT");
header.put("alg", "HS256");
// 生成 token:头部+载荷+签名
return JWT.create().withHeader(header)
.withClaim(RequestKeyConstants.USER_ID,userId)
.withExpiresAt(new Date(System.currentTimeMillis()+TOKEN_EXPIRE_TIME)).sign(algorithm);
}
/**
* 解析jwt
*/
public Map<String, Claim> parseJwt(String token) {
Map<String, Claim> claims = null;
try {
Algorithm algorithm = Algorithm.HMAC256(key);
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);
claims = jwt.getClaims();
return claims;
} catch (Exception e) {
return null;
}
}
}
注:このツールの署名キーと有効期限を構成センターのファイルに一律に構成してから、メソッドを介してパラメーターを渡すことをお勧めします。
トークン構成:
- 頭
{
"typ": "JWT",
"alg": "HS256"
}
- ペイロード:ユーザーは、機密性の低いユーザー情報(ユーザーID、ユーザー名、権限、役割など)を保存します。
- 署名:指定された秘密鍵を使用して、上記の2つの部分に署名します
1.3ログインインターフェイス
LoginController
注:デモンストレーションの便宜上、コードの一部は省略されています。
@PostMapping("/auth/login")
public Result login(HttpServletRequest request, @Valid @RequestBody LoginDTO loginDto) {
String ip = IpAddressUtils.getIpAddr(request);
// 获取用户信息、比对密码
Result<UserDTO> result = loginFeignApi.login(loginDto,ip);
if(ResultCode.SUCCESS.getCode()!=result.getCode()){
log.error(result.getMsg());
return result;
}
UserDTO user = result.getData();
String token = JWTUtils.createJwt(user.getId() + "");
data.put("token",token);
return Result.success(data);
}
その中で、loginFeignApi.login(loginDto、ip)は 、feignを介してユーザーサービスモジュールを呼び出すため のインターフェイスです。ユーザー名に従ってユーザー情報を照会し、次のようにパスワードに対してMD5暗号化とソルト検証を実行します。
User user = userService.getUserByName(loginDTO.getUserName());
// 密码md5 校验
if (!user.getPassword().equals(MD5Utils.toMD5Password(user.getUserSalt(), loginDTO.getPassword()))){
userService.userLog(user,ip,UserLogTypeEnum.LOGIN_FAIL,loginDTO.getSource());
return Result.error(ResultCode.PASSWORD_ERROR);
}
2.ゲートウェイゲートウェイサービス
同様に、ゲートウェイサービスモジュールはSpringBootを介して構築され、登録および構成センターはNacosを均一に使用し、他のサービスAPIの統合エントリとして他のサービスインターフェイスのルーティング戦略を構成します。
ゲートウェイは、トークンを検証し、ユーザー関連情報を要求のヘッダーに入れて、ダウンストリームシステムがユーザー情報を簡単に取得できるようにします。
2.1 pom.xml
同様に、jwtツールキットを導入する必要があります
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>${jwt.version}</version>
</dependency>
2.2 application.properties
server.port=1234
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
# gateway 路由策略
spring.cloud.gateway.routes[0].id=user
spring.cloud.gateway.routes[0].uri=lb://user-service
spring.cloud.gateway.routes[0].predicates[0]=Path=/userService/**
spring.cloud.gateway.routes[0].filters[0]=StripPrefix=1
spring.cloud.gateway.routes[1].id=order
spring.cloud.gateway.routes[1].uri=lb://order-service
spring.cloud.gateway.routes[1].predicates[0]=Path=/orderService/**
spring.cloud.gateway.routes[1].filters[0]=StripPrefix=1
### 。。。。。。。
2.3トークンの確認
GatewayFilter または GlobalFilterフィルターインターフェイスを実装できます。フロントエンドリクエストがゲートウェイを通過すると、トークンの有効性を検証するためにフィルター処理されます。ここでは、GlobalFilterインターフェイスを実装します。
TokenFilter.java
/**
* @description: token过滤器
* @author: xianhao_gan
* @date:
**/
@Slf4j
@Component
public class TokenFilter implements GlobalFilter, Ordered {
@Autowired
private JwtUtils jwtUtils;
/**
* 校验token
*
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String token = request.getHeaders().getFirst(RequestKeyConstants.TOKEN);
//检查token是否为空
if (StringUtils.isEmpty(token)) {
return denyAccess(exchange, ResultCode.TOKEN_NULL);
}
Map claimMap1 = jwtUtils.parseJwt(token);
//token有误
if (claimMap.containsKey("exception")) {
log.error() (claimMap1.get("exception").toString());
return denyAccess(exchange, ResultCode.TOKEN_INVALID);
}
//token无误,将用户信息设置进header中,传递到下游服务
Map<String, Claim> claimMap = claimMap1;
String userId = claimMap.get(RequestKeyConstants.USER_ID).asString();
Consumer<HttpHeaders> headers = httpHeaders -> {
httpHeaders.add(RequestKeyConstants.USER_ID, userId);
};
request.mutate().headers(headers).build();
// todo 权限校验
return chain.filter(exchange);
}
/**
* 拦截并返回自定义的json字符串
*/
private Mono<Void> denyAccess(ServerWebExchange exchange, ResultCode resultCode) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.OK);
//这里在返回头添加编码,否则中文会乱码
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
byte[] bytes = JSON.toJSONBytes(Result.error(resultCode), SerializerFeature.WriteMapNullValue);
DataBuffer buffer = response.bufferFactory().wrap(bytes);
return response.writeWith(Mono.just(buffer));
}
@Override
public int getOrder() {
return -1;
}
}
注:
-
もちろん、実際のプロジェクトのコードはこれよりも複雑になります。デモンストレーションを容易にし、基本的な考え方を説明するために、コードの一部を省略しています。実際の設計と開発は、この考え方に従って拡張できます。
-
ログイン後に生成されたトークンの場合、バックエンドは固定の有効期間を設定します。有効期間中、ユーザーはアクセス用のトークンを携帯します。トークンの有効期限が切れると、トークンは無効になります。フロントエンドはログインページにジャンプしてユーザーに再度ログインさせますが、ユーザーエクスペリエンスはフレンドリーではありません。
-
改善:アクティブユーザーは、トークンの有効期限が切れた後、気付かないうちに新しいトークンを自動的に取得し、アクセスのためにこの新しいトークンを携帯する必要があります。一方、長期間非アクティブだったユーザーは、JWTの有効期限が切れた後に再度ログインする必要があります。後で、トークンタイムアウト更新戦略を追加する時間があります。
参照:https://www.cnblogs.com/cjsblog/p/12425912.html
●なぜアリババは90秒で100億に抵抗できるのですか?-サーバー側の高同時分散アーキテクチャの進化
● B2Beコマースプラットフォーム--ChinaPayUnionPay電子決済機能
● Zookeeperの分散ロックを学び、インタビュアーに感心してあなたを見てもらいましょう
● SpringCloudeコマーススパイクマイクロサービス-Redisson分散ロックソリューション
もっと良い記事をチェックして、公式アカウントを入力してください-私にお願いします-過去に素晴らしい
深くソウルフルなパブリックアカウント0.0