JWT令牌、过滤器Filter、拦截器Interceptor

SpringBootWeb登录认证

为什么在登陆时需要JWT令牌、过滤器Filter、拦截器Interceptor:看代码

登录功能

LoginController

@RestController
public class LoginController {

    @Autowired
    private EmpService empService;

    @PostMapping("/login")
    public Result login(@RequestBody Emp emp){
        Emp e = empService.login(emp);
	    return  e != null ? Result.success():Result.error("用户名或密码错误");
    }
}

EmpService / EmpServiceImpl
EmpService:

/**
* 登录
* @param emp
*/
Emp login(Emp emp);
@Override
public Emp login(Emp emp) {
    Emp e = empMapper.getByUsernameAndPassword(emp);
    return e;
}

EmpServiceImpl:

//根据用户名及密码查询员工信息
@Select("select * from emp where username = #{username} and password = #{password}")
Emp getByUsernameAndPassword(Emp emp);

EmpServiceImpl:

//根据用户名及密码查询员工信息
@Select("select * from emp where username = #{username} and password = #{password}")
Emp getByUsernameAndPassword(Emp emp);

测试

功能开发完毕后,我们就可以启动服务,打开postman进行测试了。 发起POST请求,访问:

在这里插入图片描述
在这里插入图片描述

问题分析

在未登录的情况下,我们可以直接在浏览器地址栏访问部门管理、员工管理等功能。
因为我们在实现实现的代码逻辑中,我们根据用户名和密码查询用户,查询到了用户信息,就判定登录成功,我们并没有在服务器或客户端记录任何用户登录成功的标志,而http协议又是无状态协议,那在下一次请求时,我们也无法判断员工是否已经登录。

要解决上述问题,我们需要做两件事
在员工登录成功后,需要将用户登录成功的信息,存起来,记录用户登录成功的标记。
在浏览器发送请求时,放行请求,如果发现登录标记中没有成功的标记,则给前端返回错误的信息,如果有登录成功的信息,就说明用户登录成功,放行请求,如果发现登录标记中没有登录成功的标记则给前端返回错误的信息,跳转至登录界面。

其中:

统一拦截:可以使用两种技术实现,Filter过滤器以及Interceptor拦截器。
登录标记:就需要用户登录成功后,在每一次的请求中,都可以获取到该标记。

此标记需要在用户登录成功之后,每一个请求资源中,都可以获取到,也就是说可以在多次请求间共享。但是我们之前介绍过,HTTP是无状态的,不能在多次请求间共享数据,所以,此处需要使用会话跟踪技术来解决。

对于会话跟踪这四个词,我们需要拆开来进行解释,首先要理解什么是会话,然后再去理解什么是会话跟踪:

会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。
从浏览器发出请求到服务端,响应数据给前端之后,一次会话(在浏览器和服务器之间)就建立了。
会话被建立后,如果浏览器或服务端都没有被关闭,则会话就会持续建立着。
浏览器和服务器就可以继续使用该会话进行请求发送和响应,上述的整个过程就被称之为会话。
用实际场景来理解下会话,比如在我们访问京东的时候,当打开浏览器进入京东首页后,浏览器和京东的服务器之间就建立了一次会话,后面的搜索商品,查看商品的详情,加入购物车等都是在这一次会话中完成。

在现今前前后端分离开发模式下,Cookie、Session这种会话技术已很少使用,而且在服务器集群环境下 以及 客户端多样化的情况下,传统的Cookie、Session的会话方案就显得力不从心了,其主要问题,体现在两个方面:

● 服务端集群环境下Session的共享问题。
● 移动端APP端无法使用Cookie。

JWT令牌

JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种简洁的、自包含的协议格式,用于在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。
官网: https://jwt.io/
标准: https://tools.ietf.org/html/rfc7519
优点:
使用 json 作为数据传输,有广泛的通用型,并且体积小,便于传输;
不需要在服务器端保存相关信息;
jwt 载荷部分可以存储业务相关的信息(非敏感的),例如用户信息、角色等;
在这里插入图片描述
JWT令牌由Header、Payload、Signature三部分组成,每部分中间使用点(.)分隔,比如:xxxxx.yyyyy.zzzzz

第一部分:Herder(头),作用:记录令牌类型,签名算法

{
	"alg":"HS256",
	"type":"JWT"
}

第二部分:Payload(有效载荷)作用:携带一些用户信息及过期时间

{
	"id":"1",
	"username":"Tom"
}

第三部分:Signature(签名)作用:防止Token被篡改、确保安全性

HMACSHA256(
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    secret
)

在这里插入图片描述

生成

在 pox.xml引入依赖

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

生成JWT代码实现

public class JwtDemo {

    @Test
    public void genJwt(){
        Map<String,Object> claims = new HashMap<>();
        claims.put("id",1);
        claims.put("username","Tom");

        String jwt = Jwts.builder()
                .setClaims(claims) //执行第二部分负载, 存储的数据
                .signWith(SignatureAlgorithm.HS256, "itheima") //签名算法及秘钥
                .setExpiration(new Date(System.currentTimeMillis() + 12*3600*1000)) //设置令牌的有效期
                .compact();
        System.out.println(jwt);
    }

}

校验

    @Test
    public void parseJwt(){
        Claims claims = Jwts.parser()
                .setSigningKey("itheima")
            				.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjU5OTk1NTE3LCJ1c2VybmFtZSI6IlRvbSJ9.EUTfeqPkGslekdKBezcWCe7a7xbcIIwB1MXlIccTMwo")
                .getBody();
        System.out.println(claims);
    }

注意:JWT下校验时使用的签名密钥,必须和生成JWT令牌时使用的密钥是配套的
如果JWT令牌解析校验时报错,则说明JWT令牌被篡改或失效了,令牌非法。

代码实现

引入JWT工具类

public class JwtUtils {
	
    private static String signKey = "itheima";
    private static Long expire = 43200000L;
	
    /**
     * 生成JWT令牌
     * @param claims JWT第二部分负载 payload 中存储的内容
     * @return
     */
    public static String generateJwt(Map<String, Object> claims){
        String jwt = Jwts.builder()
                .addClaims(claims)
                .signWith(SignatureAlgorithm.HS256, signKey)
                .setExpiration(new Date(System.currentTimeMillis() + expire))
                .compact();
        return jwt;
    }
	
    /**
     * 解析JWT令牌
     * @param jwt JWT令牌
     * @return JWT第二部分负载 payload 中存储的内容
     */
    public static Claims parseJWT(String jwt){
        Claims claims = Jwts.parser()
                .setSigningKey(signKey)
                .parseClaimsJws(jwt)
                .getBody();
        return claims;
    }
}

登录成功,生成JWT令牌并返回

@RestController
public class LoginController {
	
    @Autowired
    private EmpService empService;
	
    @PostMapping("/login")
    public Result login(@RequestBody Emp emp){
        Emp e = empService.login(emp);
        if(e != null){ //用户名密码正确
            Map<String,Object> claims = new HashMap<>();
            claims.put("id",e.getId());
            claims.put("username",e.getUsername());
            claims.put("name",e.getName());

            //生成JWT令牌
            String jwt = JwtUtils.generateJwt(claims);
            return Result.success(jwt);
        }
        return Result.error("用户名或密码错误");
    }
}

这样在登录成功后,服务端将生成的令牌已经相应给了我们。通过接口文档的描述,我们也可以看出,登录成功之后,前端会在后面每一次请求中蒋令牌携带过来,那接下来,我么需要做的就是需要在服务端统一拦截校验JWT令牌。
那统一拦截请求,在服务端,我们可以通过两种手段实现过滤器Filter、拦截器Interceptor

过滤器Filter

Filter 过滤器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。
过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。
过滤器一般完成一些通用的操作,比如:登陆鉴权、统一编码处理、敏感字符处理等等…

引入json数据处理的工具 .

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.39</version>
</dependency>

登录校验过滤器

@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
		
        String url = request.getRequestURL().toString();
        //如果是login, 直接放行
        if(url.contains("login")){
            System.out.println("登录操作, 直接放行...");
            filterChain.doFilter(req, res);
            return;
        }
		
        //如果不是 login ,需要校验 token
        String token = request.getHeader("token");
        if(!StringUtils.hasLength(token)){ //如果没有JWT令牌
            System.out.println("获取到token为空 , 返回错误信息...");
            //返回 未登录 提示信息
            String result = JSONObject.toJSONString(Result.error("NOT_LOGIN"));
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().write(result);
            return ;
        }
		
        //解析jwt令牌, 如果解析失败, 则说明令牌无效 , 返回 未登录 提示信息
        try {
            JwtUtils.parseJWT(token);
            System.out.println("令牌解析成功, 直接放行 ...");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("令牌解析失败 , 返回错误信息...");
			
            //返回 未登录 提示信息
            String result = JSONObject.toJSONString(Result.error("NOT_LOGIN"));
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().write(result);
            return ;
        }
		
        //如果校验通过放行
        filterChain.doFilter(req, res);
    }

}

未登录情况下,服务端响应回来了错误信息 NOT_LOGIN。

拦截器Interceptor

拦截器:(Interceptor)是一种动态拦截方法调用的机制,类似于过滤器。在SpringMVC中动态拦截控制器方法的执行
作用:在指定的方法调用前后执行预先设定的代码,完成功能增强

在这里插入图片描述
代码实现

//@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
    //目标资源方法执行前执行 , true : 放行 ; false : 不放行,拦截 ;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String url = request.getRequestURL().toString();
        //如果是login, 直接放行
        if(url.contains("login")){
            System.out.println("登录操作, 直接放行...");
            return true;
        }

        //如果不是 login ,需要校验 token
        String token = request.getHeader("token");
        if(!StringUtils.hasLength(token)){ //如果没有JWT令牌
            System.out.println("获取到token为空 , 返回错误信息...");
            //返回 未登录 提示信息
            String result = JSONObject.toJSONString(Result.error("NOT_LOGIN"));
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().write(result);
            return false;
        }

        //解析jwt令牌, 如果解析失败, 则说明令牌无效 , 返回 未登录 提示信息
        try {
            JwtUtils.parseJWT(token);
            System.out.println("令牌解析成功, 直接放行 ...");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("令牌解析失败 , 返回错误信息...");

            //返回 未登录 提示信息
            String result = JSONObject.toJSONString(Result.error("NOT_LOGIN"));
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().write(result);
            return false;
        }

        //如果校验通过放行
        return true;
    }


    //目标资源方法执行后执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle ....");
    }

     //请求处理完成后调用
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion ....");
    }

}

Filter 与 Interceptor 区别

接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口。
拦截范围不同:过滤器Filter会拦截所有的资源,而Interceptor只会拦截Spring环境中的资源。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值