JSON Web Token(JWT)は、現在最も人気のあるクロスドメイン認証ソリューションの1つです。本日、その謎を解き明かします。
1.物語の起源
JWTと言えば、従来のsession
認証ベースのソリューションとボトルネックについて話しましょう。
従来のsession
対話プロセスは次のとおりです。
ブラウザがサーバにログイン要求を送信すると、認証が渡された後、ユーザー情報がseesion
され保存された中で、その後、サーバーは1を生成しますsessionId
それを置くcookie
にして、ブラウザにそれを返します。
ブラウザが要求を再送信すると、それがされますcookie
置くリクエストヘッダにそれをsessionId
して、サーバーに要求データを送信します。
サーバーはseesion
ユーザー情報を再度取得でき、プロセス全体が完了します。
通常、サーバーはseesion
30分間の非アクティブなどの期間を設定し、ユーザーはのseesion
削除からの情報を保存する必要があります。
session.setMaxInactiveInterval(30 * 60);//30分钟没活动,自动移除
一方、サーバーはseesion
、現在のユーザーがログインしているかどうかを判断することもできます。空白の場合はログがないことを示し、ログインページに直接ジャンプします。空でない場合は、session
取得した情報からユーザーは続行できます。
モノリシックアプリケーションでは、この種の相互作用は問題ありません。
ただし、アプリケーションサーバーの要求量が非常に大きくなり、1台のサーバーでサポートできる要求量が限られている場合は、この時点で要求を遅くしたり遅くしたりするのは簡単OOM
です。
解決策は、単一のサーバーに構成を追加するか、新しいサーバーを追加して、負荷分散を通じてビジネスニーズを満たすことです。
単一のサーバーに構成を追加する場合、要求量は増加し続け、ビジネス処理をサポートできなくなります。
明らかに、新しいサーバーを追加すると、無制限の水平拡張を実現できます。
ただし、新しいサーバーを追加した後、異なるサーバー間の違いsessionId
がA
成功する場合があります。サーバーに正常にログインし、サーバーsession
からユーザー情報を取得できますが、B
サーバー上のsession
情報が見つかりません。これ間違いなく比類のないものです。厄介なことに、ログインを続行するにはログアウトする必要がありました。その結果、A
サーバーsession
はタイムアウトに失敗し、ログイン後に再度ログインを要求するためにログアウトを余儀なくされました。それについて考えるのは恥ずかしいことでした〜 〜
このような状況に直面して、いくつかの大物が一緒に話し合い、token
計画を立てました。
インメモリデータベースに各アプリケーションを接続redis
し、正常にログインしたユーザ情報に特定のアルゴリズムの暗号化を行う発生。ID
呼ばれtoken
、token
ユーザの情報が格納されredis
、ユーザが開始要求が再び、ときにtoken
そこであろうリクエストデータ1。そしてそれをサーバーに送信します。サーバーはtoken
それが存在するかどうかを検証します。存在するredis
場合は検証に合格したことを意味します。存在しない場合はログインページにジャンプするようにブラウザに指示し、プロセスは終了します。
token
このソリューションは、サービスがステートレスであり、すべての情報が分散キャッシュに格納されることを保証します。分散ストレージに基づいて、水平方向に拡張して高い同時実行性をサポートできます。
もちろん、現在は共有スキームspringboot
もあり、session
同様のtoken
スキームがsession
格納さredis
れます。クラスター環境でログインが実装されると、各サーバーはユーザー情報を取得できます。
第二に、JWTとは何ですか
上記では、session
他のtoken
ソリューションについて説明しました。クラスター環境では、それらはすべてサードパーティのキャッシュデータベースredis
に依存してデータを共有します。
redis
1回のログインでどこでも見られる効果を実現するために、ユーザー情報の共有を実現するためにデータベースをキャッシュする必要がないソリューションはありますか?
答えは間違いなくそこにあります。それが今日紹介するものですJWT
。
JWT
完全JSON Web Token
に、実装プロセスは、ユーザーが正常にログインした後、ユーザーの情報が暗号化され、生成さtoken
れてクライアントに返されることを意味します。これは、従来のsession
対話と大差ありません。
相互作用のプロセスは次のとおりです。
唯一の違いは、ということです:token
ユーザーの基本情報を保存するために、より直感的なポイントが最初に配置されてredis
置くために、ユーザデータにtoken
行くに!
その結果、クライアント、サーバーtoken
はユーザーの基本情報にアクセスできるようになります。これは、ブラウザーがユーザー情報を直接取得するため、クライアントは機密情報を保存しないようにすることができるためtoken
です。
JWTは正確にはどのように見えますか?
JWTは3つの情報で構成されており、これら3つの情報テキストは.
リンクされてJWT
文字列を形成します。このような:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
-
最初の部分:これをヘッダーと呼びます。これは、トークンの種類と暗号化プロトコルを格納するために使用されます。これらは通常、固定されています。
-
2番目の部分:これをペイロードと呼び、ユーザーデータがそこに格納されます。
-
3番目の部分:主にサーバーの検証に使用されるビザ(署名)です。
1、ヘッダー
JWTのヘッダーには、次の2つの情報が含まれています。
-
宣言タイプ。これがJWTです。
-
暗号化アルゴリズム、通常はHMACSHA256を直接宣言します。
完全なヘッダーは次のJSONのようになります。
{
'typ': 'JWT',
'alg': 'HS256'
}
base64
暗号化の使用は最初の部分を構成します。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
2、ペイロード
ペイロードは、有効な情報が格納される場所です。有効な情報は、次の3つの部分で構成されます。
-
規格に登録されている宣言。
-
公式声明
-
プライベートステートメント
その中で、規格に登録されているステートメント(推奨されますが必須ではありません)には、次の部分が含まれています 。
-
iss:jwt発行者;
-
sub:jwtが直面しているユーザー。
-
aud:jwtを受信するパーティ。
-
exp:jwtの有効期限。この有効期限は、発行時間よりも長くする必要があります。
-
nbf:jwtが使用できなくなるまでの時間を定義します。
-
iat:jwtの発行時間。
-
jwtの一意のIDは、主にリプレイ攻撃を回避するための1回限りのトークンとして使用されます。
パブリックステートメント部分:一般に、ユーザー関連情報やビジネスに必要なその他の必要な情報を追加して、任意の情報をパブリックステートメントに追加できますが、この部分はクライアントで復号化できるため、機密情報を追加することはお勧めしません。
プライベート宣言部分:プライベート宣言は、プロバイダーとコンシューマーが共同で定義する宣言です。機密情報base64
は対称的に復号化されるため、一般に機密情報の保存はお勧めしません。つまり、情報のこの部分はプレーンテキスト情報として分類できます。
ペイロードを定義します。
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
次に、base64
暗号化してJwt
2番目の部分を取得します。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
3、署名
jwtの3番目の部分はビザ情報であり、このビザ情報は3つの部分で構成されています。
-
ヘッダー(base64の後);
-
ペイロード(base64の後);
-
シークレット(キー);
この部分は、接続で構成される文字列を使用してbase64
暗号化header
およびbase64
暗号化され、次にソルトコンビネーション暗号化で宣言された暗号化メソッドを介して暗号化され、3番目の部分を構成する必要があります。payload
.
header
secret
jwt
//javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, '密钥');
暗号化後、signature
署名情報が取得されます。
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
これらの3つの部分.
を完全な文字列に接続して、最終的なjwtを形成します。
//jwt最终格式
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
これはjavascript
実装による単なるデモンストレーションでありJWT
、発行とキーの保存はすべてサーバー側で行われます。
secret
jwt
発行とjwt
検証に使用されるため、どのシーンでも公開しないでください。
3、実際の戦闘
私はたくさん紹介しました、それを達成する方法は?ナンセンスな話をしないで、真下から始めましょう!
-
springboot
プロジェクトを作成し、JWT
依存ライブラリを追加します
<!-- jwt支持 -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
-
次に、ユーザー情報のクラスを作成し、によって暗号化され
token
て保存されます
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class UserToken implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
private String userId;
/**
* 用户登录账户
*/
private String userNo;
/**
* 用户中文名
*/
private String userName;
}
-
次に、作成および検証する
JwtTokenUtil
ツールクラスを作成しますtoken
token
public class JwtTokenUtil {
//定义token返回头部
public static final String AUTH_HEADER_KEY = "Authorization";
//token前缀
public static final String TOKEN_PREFIX = "Bearer ";
//签名密钥
public static final String KEY = "q3t6w9z$C&F)J@NcQfTjWnZr4u7x";
//有效期默认为 2hour
public static final Long EXPIRATION_TIME = 1000L*60*60*2;
/**
* 创建TOKEN
* @param content
* @return
*/
public static String createToken(String content){
return TOKEN_PREFIX + JWT.create()
.withSubject(content)
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.sign(Algorithm.HMAC512(KEY));
}
/**
* 验证token
* @param token
*/
public static String verifyToken(String token) throws Exception {
try {
return JWT.require(Algorithm.HMAC512(KEY))
.build()
.verify(token.replace(TOKEN_PREFIX, ""))
.getSubject();
} catch (TokenExpiredException e){
throw new Exception("token已失效,请重新登录",e);
} catch (JWTVerificationException e) {
throw new Exception("token验证失败!",e);
}
}
}
-
構成クラスを記述し、クロスドメインを許可し、アクセス許可インターセプターを作成します
@Slf4j
@Configuration
public class GlobalWebMvcConfig implements WebMvcConfigurer {
/**
* 重写父类提供的跨域请求处理的接口
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
// 添加映射路径
registry.addMapping("/**")
// 放行哪些原始域
.allowedOrigins("*")
// 是否发送Cookie信息
.allowCredentials(true)
// 放行哪些原始域(请求方式)
.allowedMethods("GET", "POST", "DELETE", "PUT", "OPTIONS", "HEAD")
// 放行哪些原始域(头部信息)
.allowedHeaders("*")
// 暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
.exposedHeaders("Server","Content-Length", "Authorization", "Access-Token", "Access-Control-Allow-Origin", "Access-Control-Allow-Credentials");
}
/**
* 添加拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//添加权限拦截器
registry.addInterceptor( new AuthenticationInterceptor()) .addPathPatterns("/**").excludePathPatterns("/static/**");
}
}
-
AuthenticationInterceptor
インターセプターを使用してインターフェイスパラメーターを確認します
@Slf4j
public class AuthenticationInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 从http请求头中取出token
final String token = request.getHeader(JwtTokenUtil.AUTH_HEADER_KEY);
//如果不是映射到方法,直接通过
if(!(handler instanceof HandlerMethod)){
return true;
}
//如果是方法探测,直接通过
if (HttpMethod.OPTIONS.equals(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
return true;
}
//如果方法有JwtIgnore注解,直接通过
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method=handlerMethod.getMethod();
if (method.isAnnotationPresent(JwtIgnore.class)) {
JwtIgnore jwtIgnore = method.getAnnotation(JwtIgnore.class);
if(jwtIgnore.value()){
return true;
}
}
LocalAssert.isStringEmpty(token, "token为空,鉴权失败!");
//验证,并获取token内部信息
String userToken = JwtTokenUtil.verifyToken(token);
//将token放入本地缓存
WebContextUtil.setUserToken(userToken);
return true;
}
@Override
public void afterCompletion( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//方法结束后,移除缓存的token
WebContextUtil.removeUserToken();
}
}
-
最後に、
controller
レイヤーユーザーがログインした後、レイヤーを作成token
してヘッダーに保存します。
/**
* 登录
* @param userDto
* @return
*/
@JwtIgnore
@RequestMapping(value = "/login", method = RequestMethod.POST, produces = {"application/json;charset=UTF-8"})
public UserVo login(@RequestBody UserDto userDto, HttpServletResponse response){
//...参数合法性验证
//从数据库获取用户信息
User dbUser = userService.selectByUserNo(userDto.getUserNo);
//....用户、密码验证
//创建token,并将token放在响应头
UserToken userToken = new UserToken();
BeanUtils.copyProperties(dbUser,userToken);
String token = JwtTokenUtil.createToken(JSONObject.toJSONString(userToken));
response.setHeader(JwtTokenUtil.AUTH_HEADER_KEY, token);
//定义返回结果
UserVo result = new UserVo();
BeanUtils.copyProperties(dbUser,result);
return result;
}
基本的にここで行われます!
その中AuthenticationInterceptor
で使用されているのJwtIgnore
は、token
検証コードの取得など、検証を必要としないメソッドに使用されるアノテーションです。@Target({ElementType.METHOD、ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface JwtIgnore {
boolean value() default true;
}
またWebContextUtil
、スレッドキャッシュツール、他のインターフェイスは、この方法でtoken
ユーザー情報を取得できます。
public class WebContextUtil {
//本地线程缓存token
private static ThreadLocal<String> local = new ThreadLocal<>();
/**
* 设置token信息
* @param content
*/
public static void setUserToken(String content){
removeUserToken();
local.set(content);
}
/**
* 获取token信息
* @return
*/
public static UserToken getUserToken(){
if(local.get() != null){
UserToken userToken = JSONObject.parseObject(local.get() , UserToken.class);
return userToken;
}
return null;
}
/**
* 移除token信息
* @return
*/
public static void removeUserToken(){
if(local.get() != null){
local.remove();
}
}
}
最後に、プロジェクトを開始し、postman
テストして、ヘッダーが結果を返すかどうかを確認しましょう。
返された情報を抽出し、ブラウザbase64
を使用して最初の2つの部分を復号化します。
-
最初の部分であるヘッダーの結果は次のとおりです。
-
ペイロードである2番目の部分では、結果は次のようになります。
ヘッダーとペイロードの情報をbase64
復号化できることがはっきりとわかります。
したがって、token
機密情報を保存しないように注意してください。
他のサービスインターフェースをリクエストする必要がある場合は、リクエストヘッダーにパラメータをheaders
追加するだけで済みAuthorization
ます。
パーミッションインターセプターが検証された後、WebContextUtil
ユーザー情報は、インターフェイスメソッドのツールクラスを介してのみ取得できます。
//获取用户token信息
UserToken userToken = WebContextUtil.getUserToken();
4、まとめ
JWT
比較するとsession
プログラムは、理由はjson
汎用性のために、それはJWT
のように、クロス言語サポートすることができJAVA
、JavaScript
、PHP
および他の多くの言語を使用することができるが、session
プログラムがのみJAVA
。
このpayload
部分のために、JWT
他のビジネスロジックに必要な機密性の低い情報を格納できます。
同時に、secret
秘密鍵はデータを検証および復号化できるため、サーバーの秘密鍵を保護することは非常に重要です。
できればhttps
契約書をご利用ください!
5、参照
1.簡単な本-JWT-JSONWEBTOKENとは
2. BlogGarden-セッションとトークンに基づくID認証スキーム
------------------------------------------- <終了> ---- -----------------------------------
JWTを使用してシングルサインオンを実現する方法を教えてください
オリジナルのアヒルの血のファン Javaオタクテクノロジー 5月15日
https://mp.weixin.qq.com/s/3qm3RvSG_1LQ0v676XU7jA
私たちの記事が気に入ったら、転送してクリックすると、より多くの人に見てもらうことができます。また、テクノロジーを愛し、知識の惑星に参加することを学ぶ友人を歓迎します。私たちは一緒に成長し、進歩します。