目次
1 はじめに
フロントエンドがリクエストを開始すると、毎回リクエスト ヘッダー内の JWT トークンがサーバーに送信されます。サーバーは、正当な JWT トークンを送信するかどうかを判断するために、すべてのリクエストを一様にインターセプトする必要があります。では、トークンの有効性を検証するためにすべてのリクエストを一様に傍受するにはどうすればよいでしょうか? ここでは、2 つのソリューションを学習します。
1. フィルター フィルター
2. インターセプター インターセプター
2. フィルター
(1) 説明
Filter は、JavaWeb の 3 つの主要コンポーネント (サーブレット、フィルター、リスナー) の 1 つであるフィルターを表します。
フィルタは、いくつかの特別な機能を実現するために、リソースのリクエストを傍受できます.
フィルタを使用した後、Web サーバー上のリソースにアクセスする場合は、まずフィルタを通過する必要があります. フィルタが処理された後、対応するリソースにアクセスできます.
通常、フィルターは、ログインの検証、Unicode の処理、機密性の高い文字の処理など、いくつかの一般的な操作を完了します。
(2) 利用手順
ステップ 1、フィルターを定義します。クラスを定義し、Filter インターフェースを実装し、そのすべてのメソッドを書き直します。
ステップ 2、フィルターを構成します。 @WebFilter アノテーションを Filter クラスに追加して、リソースをインターセプトするパスを構成します。@ServletComponentScan をブート クラスに追加して、サーブレット コンポーネントのサポートを有効にします。
(3) 説明例
フィルタを定義
@WebFilter(urlPatterns = "/*") //配置过滤器要拦截的请求路径( /* 表示拦截浏览器的所有请求 )
public class DemoFilter implements Filter {
@Override //初始化方法, 只调用一次
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init 初始化方法执行了");
}
@Override //拦截到请求之后调用, 调用多次
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("Demo 拦截到了请求...放行前逻辑");
//放行
chain.doFilter(request,response);
}
@Override //销毁方法, 只调用一次
public void destroy() {
System.out.println("destroy 销毁方法执行了");
}
}
init メソッド: フィルターの初期化メソッド。Filter オブジェクトは Web サーバーの起動時に自動的に作成され、init 初期化メソッドはフィルター オブジェクトの作成時に自動的に呼び出され、このメソッドは 1 回だけ呼び出されます。
doFilter メソッド: このメソッドは、リクエストがインターセプトされるたびに呼び出されるため、このメソッドは複数回呼び出され、リクエストがインターセプトされるたびに doFilter() メソッドが 1 回呼び出されます。
破棄方法: 破棄の方法です。サーバーをシャットダウンすると、自動的に destroy メソッド destroy が呼び出され、この destroy メソッドは 1 回だけ呼び出されます。
Filter が定義された後, Filter は実際には有効にならず, Filter の構成を完了する必要があります. Filter の構成は非常に簡単です. Filter クラスに @WebFilter というアノテーションを追加するだけで済みます.属性 urlPatterns を指定し、この属性を介してフィルターを指定します。
スタートアップ クラスにアノテーション @ServletComponentScan を追加し、この @ServletComponentScan アノテーションを使用して、サーブレット コンポーネントの SpringBoot プロジェクト サポートを有効にします。
@ServletComponentScan
@SpringBootApplication
public class TliasWebManagementApplication {
public static void main(String[] args) {
SpringApplication.run(TliasWebManagementApplication.class, args);
}
}
(4) 具体例
フィルター:
package com.example.demo.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/**
* @Author linaibo
* @Date 2023/4/8 21:36
* @PackageName:com.example.demo.filter
* @ClassName: DemoFilter
* @Version 1.0
*/
@WebFilter("/*") // 配置过滤器要拦截的请求路径( /* 表示拦截浏览器的所有请求 )
public class DemoFilter implements Filter {
@Override //初始化方法, 只调用一次
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
System.out.println("初始化方法执行");
}
@Override //拦截到请求之后调用, 调用多次
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 放行前逻辑
System.out.println("拦截到了请求,放行前的逻辑");
// 放行到后面进行处理,如果没有拦截器,直接进入controller,如果有,则进入拦截器
filterChain.doFilter(servletRequest, servletResponse);
// 放行后逻辑
System.out.println("拦截到了请求,放行后的逻辑");
}
@Override //销毁方法, 只调用一次
public void destroy() {
System.out.println("销毁方法执行了");
Filter.super.destroy();
}
}
スタートアップ クラス:
package com.example.demo;
import com.example.demo.domain.Student;
import com.example.demo.service.IBmsBillMemoService;
import com.example.demo.service.impl.BmsBillMemoServiceImpl;
import org.apache.commons.beanutils.PropertyUtils;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.client.RestTemplate;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
@SpringBootApplication
//开启定时任务功能
@EnableScheduling
// @EnableConfigurationProperties(Student.class)
//@MapperScan("com.example.demo.mapper")
@ServletComponentScan
public class DemoApplication {
// @Bean
// public RestTemplate getRestTemplate(){
// return new RestTemplate();
// }
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
ConfigurableApplicationContext ctx = SpringApplication.run(DemoApplication.class, args);
Object bean = ctx.getBean("bmsBillMemoServiceImpl");
System.out.println(bean);
IBmsBillMemoService bean1 = ctx.getBean(IBmsBillMemoService.class);
System.out.println(bean1);
BmsBillMemoServiceImpl bean2 = ctx.getBean("bmsBillMemoServiceImpl", BmsBillMemoServiceImpl.class);
System.out.println(bean2);
Object get = ctx.getBean("get");
System.out.println(get);
Map<String, Object> get1 = PropertyUtils.describe("get");
Student bean3 = ctx.getBean(Student.class);
System.out.println(bean3.toString());
}
}
以下の結果:
初期化メソッドの実行後、まずリリース前のロジックを実行してからインターセプタに入る、またはコントローラに入り、処理が完了したらリリース後のロジックを実行し、フロントエンドに戻ります。
(5) フィルタの詳細
1. フィルター実行プロセス
フィルターでリクエストをインターセプトした後、次の Web リソースに引き続きアクセスする場合は、解放操作を実行する必要があります.解放操作は、FilterChain オブジェクトで doFilter() メソッドを呼び出すことです. doFilter() メソッドはリリース操作の前のロジックに属します。
リリース後のWebリソースにアクセスすると、フィルタに戻ります. フィルタに戻った後、必要に応じてリリース後のロジックを実行できます. リリース後のロジックはdoFilter()のコード行の後に記述します.
2.インターセプトパス
3.フィルターチェーン
いわゆるフィルター チェーンとは、Web アプリケーションで複数のフィルターを構成でき、複数のフィルターがフィルター チェーンを形成することを意味します。
例: この Web サーバーでは、2 つのフィルターが定義されており、これら 2 つのフィルターがフィルター チェーンを形成します。
そして、このチェーンのフィルターは、実行時に 1 つずつ実行されます. 最初のフィルターが最初に実行され、次に 2 番目のフィルターがリリース後に実行されます. 最後のフィルターが実行されると、対応する Web ページがアクセスしたリソース。
Web リソースにアクセスした後、今紹介したフィルターの実行プロセスに従い、フィルターが解放された後にフィルターに戻ってロジックを実行し、解放された後にロジックを実行する場合は順序が逆になります。
まず、フィルタ 2 のリリース後にロジックを実行し、次にフィルタ 1 のリリース後にロジックを実行し、最後にブラウザにデータで応答します。
フィルタの実行順序はクラス名に関連しています. アノテーションモードで構成されたフィルタの実行優先度は, フィルタクラス名の自動ソートによって決定されます. クラス名が大きいほど優先度が高くなります.
3. ログイン認証のフィルター実装
(1) 実施プロセス
ログイン認証の基本的なプロセス:
- バックグラウンド管理システムに入るには、まずログイン操作を完了し、次にログイン インターフェイス ログインにアクセスする必要があります。
- ログインに成功したら、サーバー側で JWT トークンを生成し、JWT トークンをフロント エンドに返します。フロント エンドは JWT トークンを保存します。
- 後続の各リクエストでは、JWT トークンがサーバーに送信されます. リクエストがサーバーに到達した後、対応するビジネス関数にアクセスする場合は、まずトークンの有効性を確認する必要があります.
- 検証トークン操作では、ログイン検証フィルターを使用して、フィルター内のトークンの有効性を検証します。トークンが無効な場合、エラー メッセージが返され、対応するリソースへのアクセスが許可されません。トークンが存在し、それが有効である場合、対応する Web リソースにアクセスし、対応するビジネス操作を実行するためにトークンが解放されます。
フィルタ フィルタの実装手順についてはおそらく知っていると思いますが、ログイン認証フィルタを正式に開発する前に、次の 2 つの質問について考えます。
1. すべてのリクエストがインターセプトされた後、トークンを検証する必要がありますか?
回答: **ログイン リクエストの例外**
2. リクエストをインターセプトした後、どのような状況でリクエストを解放し、ビジネス オペレーションを実行できますか?
回答: **トークンがあり、トークンの検証に合格 (合法); それ以外の場合は、ログインしていないというエラー結果が返されます。
上記のビジネス プロセスに基づいて、特定の操作手順を分析します。
1. リクエスト URL を取得する
2. リクエスト URL に login が含まれているかどうかを判断します。含まれている場合はログイン操作を意味します。そのままにしておきます 3. リクエスト
ヘッダーでトークンを取得します
4. トークンが存在するかどうかを判断し、存在しない場合は return を返しますエラー 結果 (ログインしていない)
5. トークンを解析し、解析に失敗した場合はエラー結果を返す (ログインしていない)
6. リリース
(2) 具体的な実施
@Slf4j
@WebFilter(urlPatterns = "/*") //拦截所有请求
public class LoginCheckFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
//前置:强制转换为http协议的请求对象、响应对象 (转换原因:要使用子类中特有方法)
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//1.获取请求url
String url = request.getRequestURL().toString();
log.info("请求路径:{}", url); //请求路径:http://localhost:8080/login
//2.判断请求url中是否包含login,如果包含,说明是登录操作,放行
if(url.contains("/login")){
chain.doFilter(request, response);//放行请求
return;//结束当前方法的执行
}
//3.获取请求头中的令牌(token)
String token = request.getHeader("token");
log.info("从请求头中获取的令牌:{}",token);
//4.判断令牌是否存在,如果不存在,返回错误结果(未登录)
if(!StringUtils.hasLength(token)){
log.info("Token不存在");
Result responseResult = Result.error("NOT_LOGIN");
//把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)
String json = JSONObject.toJSONString(responseResult);
response.setContentType("application/json;charset=utf-8");
//响应
response.getWriter().write(json);
return;
}
//5.解析token,如果解析失败,返回错误结果(未登录)
try {
JwtUtils.parseJWT(token);
}catch (Exception e){
log.info("令牌解析失败!");
Result responseResult = Result.error("NOT_LOGIN");
//把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)
String json = JSONObject.toJSONString(responseResult);
response.setContentType("application/json;charset=utf-8");
//响应
response.getWriter().write(json);
return;
}
//6.放行
chain.doFilter(request, response);
}
}