java 过滤器 + 拦截器 + JWT 原理以及实践

一、前言

还记得上次我写过几篇在实际项目中如何使用jwt《公众号授权 + jwt》《小程序授权+ jwt》《微信支付》

紧接着,就有个小伙伴,问了我一个这样的问题:授权使用=jwt签发token后,登录、注册等,用户是不需要token的,此时,应该怎么排除这些请求的url呢?
在这里插入图片描述

得嘞,今天咱们就掰扯掰扯这件事,如何使用“过滤器”或“拦截器”实现登录、注册的过滤

二、 是不是有人也这么写过?

在写此文时,我曾想到小编曾经的SAO操作,回看自己的代码,我滑稽的笑了。
在这里插入图片描述

因为小编的Controller层代码都是类似这样写的:

@RestController
public class LoginController {

    @Resource
    private HttpServletRequest request;
    
    @Resource
    private JwtUtil	jwtUtil;

    @ApiOperation(value = "根据订单id删除订单接口")
    @PostMapping(value = "/deleteById")
    public RespResult deleteById(String id) {
        String header = request.getHeader("token");//获取头信息,头中key为token
        if (StringUtils.isEmpty(header)) {
            return new RespResult(400, "请求头不能为空"); //头为空返回权限不足
        }
        if (!header.startsWith("Bearer ")) { // 和前端约定,头的值是Bearer + 空格 + jwt签发的token
            return new RespResult(400, "token错误");//头不符合约定,返回权限不足
        }
        String token = header.substring(7);  //提取头中的token
        Claims claims = jwtUtil.parseJWT(token); //使用jwt工具解密token
        if (claims == null) {
            return new RespResult(400, "token验证失败");
        }
        //调用service层删除订单
        // ...... 此处省略
        return new RespResult(200, "删除成功");
    }
}

卧CAO!小编这代码可真“丧心病狂”,现在只有一个接口,当接口有几十个时,写几十遍吗?,代码不冗余吗?何况一天就8小时,7小时在复制粘贴,怕是非常酸爽按。。。

接下来,我们就好好唠一唠:如何用过滤器或拦截器干掉上面冗余的代码!

三、有过滤器为啥不用

首先,我们要使用过滤器,肯定的先了解过滤器的原理;

过滤器(Filter)

过滤器 (Filter) 是处于客户端服务器资源文件之间的一道过滤网,在访问资源文件之前,通过一系列的过滤器 (也就是过滤链) 对请求进行修改、判断等,把不符合规则的请求在中途拦截或修改。也可以对响应进行过滤,拦截或修改响应。
在这里插入图片描述
如图,浏览器发出的请求先递交给第一个filter进行过滤,符合规则则放行,递交给filter链中的下一个过滤器进行过滤。过滤器在链中的顺序与它在yml或config中配置顺序有关。

大家都知道,filter中最重要的一个方法就是:doFilter()方法

在doFilter()方法中,chain.doFilter()前的一般是对request执行的过滤操作,chain.doFilter后面的代码一般是对response执行的操作。

来来来,我们看一下过滤链代码的执行顺序是怎么样的:
在这里插入图片描述
这图 so easy 吧:
chain.doFilter()前,对请求资源进行过滤(eg: 登录权限验证、资源访问权限控制、敏感词汇过滤、字符编码转换等等操作)
在chain.doFilter()后,给前端一些响应,例:{“code”: “6000”,“message”: “TOKEN 验证失败”}

来,咋们用filter,优化下小编的SAO代码:

首先,在yml配置文件中,设置不需要过滤的URI

filter:
  config:
    excludeUrls: /user/login,/user/register  #登录和注册不需过滤,直接放行

接下来,写一个过滤器:MyFilter

@Slf4j
@Order(1) //数字越小,越先执行此过滤器
@Component
@WebFilter(filterName = "MyFilter", urlPatterns = {"/**"}) // 过滤规则——所有
public class MyFilter implements Filter {

    @Resource
    private JwtUtils jwtUtils; // 自己jwt工具类

    @Value("${filter.config.excludeUrls}")
    private String excludeUrls; // 获取配置文件中不需要过滤的uri

    private List<String> excludes;

    // token验证失败时的响应消息
    private static final String VALID_ERROR = "{\"code\": \"6000\",\"message\": \"TOKEN 验证失败\"}";

    @Override
    public void init(FilterConfig filterConfig) {
    	// 初始化
    	// 移除配置文件中不过滤url,字符串的前空白和尾部空白
        excludes = Splitter.on(",").trimResults().splitToList(this.excludeUrls); 
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        String uri = request.getRequestURI(); //获取请求uri
        String token = request.getHeader("token"); // 获取头中token
        try {
            if (this.isExcludesUrl(uri)) { // 判断请求uri是否需要过滤(方法在下面)
                chain.doFilter(req, resp); // 不需要,放行
            } else {
                if (!validateParams(token)) { // 验证头中的token(方法在下面)
                    response.getWriter().write(VALID_ERROR); // 验证失败,返回验证失败消息
                    return;
                }
                chain.doFilter(request, resp); // 验证成功,放行
            }
        } catch (Exception ex) {
            log.error("Exception error", ex);
            response.getWriter().write(VALID_ERROR); // 抛出异常,返回验证失败消息
        } finally {
            response.flushBuffer(); // 将缓冲信息输出到页面
        }

    }

    private boolean validateParams(String token) {
    	// 验证是否为空,格式是否满足预定要求
        if (!StringUtils.isEmpty(token) && token.startsWith("bearer ")) {
            String token = token.substring(7);
            Map<String, Object> map = jwtUtils.extractJwt(token); // 掉用jwt解密方法解密
            if (map != null) { 
                return true;  // 解密成功,返回true
            }
        }
        return false; // 解密失败,返回false
    }
    
    private boolean isExcludesUrl(String path) {
        for (String v : this.excludes) { 
            if (path.startsWith(v)) {// 判断请求uri 是否满足配置文件uri要求
                return true;  // 满足、也就是请求uri 为 登录、注册,返回true
            }
        }
        return false; // 不满足、也就是请求uri 不是登录、注册,返回false
    }
}

最后,我们以空的请求头使用Postman访问下“根据订单id删除订单接口”接口:localhost:8080/deleteById;结果为:

{
    "code": 6000,
    "message": "TOKEN 验证失败"
}

这样,一个简单的filter,就取代了上面小编“辣眼睛”的操作,同时也排除了不需要验证的接口(登录、注册等),这才是真的SAO。。。

在这里插入图片描述

四、有拦截器为啥不用

来,我们先看看拦截器的原理;
在这里插入图片描述
拦截器,在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。拦截是AOP的一种实现策略

他有三个方法:

分别实现预处理后处理(调用了Service并返回ModelAndView,但未进行页面渲 染)、返回处理(已经渲染了页面)

  • preHandle中,可以进行编码、安全控制等在操作之前处理;
  • postHandle中,有机会修改ModelAndView在操作之后处理;
  • afterCompletion中,可以根据ex是否为null判断是否发生了异常,进行日志记录,页面操作完之后。
来,咋们再用拦截器,优化下小编的SAO代码:

首先,编写拦截器配置文件类:

/**
 * Create By CodeCow on 2020/7/26.
 */
public class MyInterceptorConfig extends WebMvcConfigurationSupport {

    @Resource
    private MyInterceptor myInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册拦截器,要声明拦截器对象和要拦截的请求
        registry.addInterceptor(myInterceptor)
                .addPathPatterns("/**") //所有路径都被拦截
                .excludePathPatterns("/user/login") // 排除用户登录请求
                .excludePathPatterns("/user/register"); // 排除用户注册请求
    }
}

接下来,编写拦截器实现类

@Component
public class MyInterceptor implements HandlerInterceptor {

    @Resource
    private JwtUtils jwtUtils;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        String headToken = request.getHeader("token"); // 获取头中token
        // 验证是否为空,格式是否满足预定要求
        if (!StringUtils.isEmpty(headToken) && headToken.startsWith("bearer ")) {
            String token = headToken.substring(7);
            Map<String, Object> map = jwtUtils.extractJwt(token); // 掉用jwt解密方法解密
            if (map != null) {
                request.setAttribute("claims_map", map); // 把jwt解密后的map放入request域中
                // 其他操作略。。。。。
            }
            // 其他操作略。。。。。
        }
        return true;
    }
}

最后,当我们需要用到token时,==直接从request域中获取“claims_map”==即可;

这样,一个简单的Interceptor,同样取代了上面小编的SAO操作,真香!

五、后记

好啦,今就先聊到这里吧,本文仅仅是抛砖引玉而已,浅聊了过滤器和拦截器配合jwt的简单实用,实际项目中如何选择,还得看个人爱好和编程风格

其次,大道万千,殊途同归,不管是过滤器还是拦截器,其实原理都大同小异,懂其原理,小小filter和Interceptor,还不信手拈来。。。

★★ 推荐 ★★

中国传统文化,先仔细看,若有用,再收藏, 给自己一点思考的时间,微信搜索:CodeCow,关注这个非常SAO的程序员

也可以加小编微信:CodeCow-6666 私信小编

  • 3
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先,需要了解什么是拦截器JWT拦截器是一个在请求处理之前或之后进行拦截拦截的组件,类似于过滤器,可以对请求进行预处理、后处理等。在 Spring 框架中,可以使用拦截器来实现一些通用的处理逻辑,例如身份验证、日志记录等。 JWT(JSON Web Token)是一种用于身份验证的标准,它可以在用户和服务器之间传递安全可靠的信息,并且不需要在服务器端存储用户的信息。JWT 由三部分组成:头部、载荷和签名。头部包含加密算法和类型,载荷包含用户信息和过期时间等,签名用于验证数据的完整性和真实性。 接下来,我们来实现自定义拦截器JWT 身份验证。 1. 创建 Auth0 账号并创建应用 在 Auth0 官网注册账号并创建一个应用,获取应用的客户端 ID 和客户端密钥。 2. 添加 Auth0 Spring Security 集成依赖 在 Maven 或 Gradle 中添加 Auth0 Spring Security 集成依赖,以 Maven 为例: ```xml <dependency> <groupId>com.auth0</groupId> <artifactId>auth0-spring-security-api</artifactId> <version>1.5.0</version> </dependency> ``` 3. 创建 JWTUtils 工具类 在项目中创建 JWTUtils 工具类,用于生成和解析 JWT。 ```java import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.DecodedJWT; import java.util.Date; public class JWTUtils { private static final long EXPIRE_TIME = 30 * 60 * 1000; // 过期时间,单位毫秒 private static final String TOKEN_SECRET = "secret"; // 密钥 /** * 生成 token * * @param userId 用户 ID * @return token */ public static String createToken(String userId) { Date expireAt = new Date(System.currentTimeMillis() + EXPIRE_TIME); return JWT.create() .withIssuer("auth0") .withClaim("userId", userId) .withExpiresAt(expireAt) .sign(Algorithm.HMAC256(TOKEN_SECRET)); } /** * 验证 token * * @param token token * @return 用户 ID */ public static String verifyToken(String token) { DecodedJWT jwt = JWT.require(Algorithm.HMAC256(TOKEN_SECRET)) .withIssuer("auth0") .build() .verify(token); return jwt.getClaim("userId").asString(); } } ``` 4. 创建 Auth0Config 配置类 在项目中创建 Auth0Config 配置类,用于配置 Auth0 的参数。 ```java import com.auth0.spring.security.api.JwtWebSecurityConfigurer; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @Configuration @EnableWebSecurity public class Auth0Config { @Value(value = "${auth0.apiAudience}") private String apiAudience; @Value(value = "${auth0.issuer}") private String issuer; /** * 配置 Auth0 参数 */ public void configure(HttpSecurity http) throws Exception { JwtWebSecurityConfigurer.forRS256(apiAudience, issuer) .configure(http) .authorizeRequests() .antMatchers("/api/public/**").permitAll() .anyRequest().authenticated(); } } ``` 5. 创建 Auth0Interceptor 拦截器 在项目中创建 Auth0Interceptor 拦截器,用于拦截请求并进行身份验证。 ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Component public class Auth0Interceptor implements HandlerInterceptor { @Autowired private Auth0Client auth0Client; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String authorizationHeader = request.getHeader("Authorization"); if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { String token = authorizationHeader.substring(7); String userId = JWTUtils.verifyToken(token); if (userId != null) { request.setAttribute("userId", userId); return true; } } response.setStatus(401); return false; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } } ``` 6. 创建 Auth0Client 客户端 在项目中创建 Auth0Client 客户端,用于与 Auth0 进行交互,例如获取用户信息等。 ```java import com.auth0.client.auth.AuthAPI; import com.auth0.client.mgmt.ManagementAPI; import com.auth0.exception.Auth0Exception; import com.auth0.json.auth.TokenHolder; import com.auth0.json.mgmt.users.User; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component public class Auth0Client { @Value(value = "${auth0.domain}") private String domain; @Value(value = "${auth0.clientId}") private String clientId; @Value(value = "${auth0.clientSecret}") private String clientSecret; /** * 获取访问令牌 * * @return 访问令牌 * @throws Auth0Exception Auth0 异常 */ public String getAccessToken() throws Auth0Exception { AuthAPI authAPI = new AuthAPI(domain, clientId, clientSecret); TokenHolder tokenHolder = authAPI.requestToken("https://" + domain + "/api/v2/"); return tokenHolder.getAccessToken(); } /** * 根据用户 ID 获取用户信息 * * @param userId 用户 ID * @return 用户信息 * @throws Auth0Exception Auth0 异常 */ public User getUserById(String userId) throws Auth0Exception { ManagementAPI managementAPI = new ManagementAPI(domain, getAccessToken()); return managementAPI.users().get(userId, null).execute(); } } ``` 7. 配置拦截器和认证管理器 在 Spring 配置文件中配置拦截器和认证管理器。 ```xml <!-- 配置拦截器 --> <mvc:interceptor> <mvc:mapping path="/api/**"/> <bean class="com.example.demo.interceptor.Auth0Interceptor"/> </mvc:interceptor> <!-- 配置认证管理器 --> <bean class="org.springframework.security.authentication.AuthenticationManager"/> ``` 8. 创建登录和登出接口 在控制器中创建登录和登出接口,用于生成和验证 JWT。 ```java import com.auth0.exception.Auth0Exception; import com.auth0.json.mgmt.users.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private Auth0Client auth0Client; /** * 登录接口 * * @param username 用户名 * @param password 密码 * @return token * @throws Auth0Exception Auth0 异常 */ @PostMapping("/login") public String login(@RequestParam String username, @RequestParam String password) throws Auth0Exception { // 根据用户名和密码验证用户身份,并获取用户 ID String userId = "user123"; return JWTUtils.createToken(userId); } /** * 登出接口 */ @PostMapping("/logout") public void logout() { // 清除 token } } ``` 以上就是使用自定义拦截器和 Auth0 JWT 实现登录、登出的步骤。通过这种方式,可以实现简单、安全、可靠的用户身份验证,有效地保护用户的数据安全。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值