8-5登录校验和全局异常处理

登录校验

服务端或客户端无记录任何用户登录成功的标识,HTTP协议是无状态协议,第一次请求响应登录成功后,下一次请求我们无法判断员工是否已经登录。为了解决这个问题,在登录成功后,将用户登录成功的信息,存起来,记录用户已经登录成功的标记,判断这个标记来判断是否登录成功,我们使用两种技术

  • 统一拦截:可以使用两种技术实现,Filter过滤器 以及 Interceptor 拦截器。

  • 登录标记:就需要用户登录成功之后,每一次请求中,都可以获取到该标记。

HTTP是无状态的,不能在多次请求间共享数据,需要使用会话跟踪技术来解决

 

会话技术

会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。

会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据

会话跟踪具体的实现方式有:

1). 客户端会话跟踪技术:Cookie

2). 服务端会话跟踪技术:Session

会话技术的缺点:

  • 服务端集群环境下Session的共享问题。

  • 移动端APP端无法使用Cookie。

模拟客户端和服务端会话跟踪技术

1.Session

​
@RestController
public class HttpSessionTest01 {
    @Autowired
    private HttpSession session; //依赖中的类 注入
​
    @GetMapping("/loginTest01")
    public Result loginTest01(String username) {
        session.setAttribute("isLongin",true);
        return Result.success();
    }
​

2.Cookie

@RestController
public class HttpSessionTest02 {
    @Autowired
    private HttpSession session; //依赖中的类 注入
​
    @GetMapping("/loginTest02")
    public Result loginTest02() {
        Boolean isLogin = (Boolean) session.getAttribute("isLogin");
        if (isLogin == null || isLogin == false) {
            return Result.error("未登录成功,不能访问资源");
        } else {
            return Result.success("登录成功,可以访问资源");
        }
    }

JWT 令牌技术

令牌技术优势:

  • 解决了集群环境下的认证问题,减轻服务器端的存储压力

  • 支持PC端、移动端

JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种简洁的、自包含的协议格式,用于在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。

JWT的精髓在于:“去中心化”,数据是保存在客户端的。优点很多:

  • 可以通过参数或者请求头进行发送,数据量小,速度快

  • token负载中包含了很多其他信息,避免查询数据库

  • jwt跨语言,任何web形式都支持

  • 不在服务端保存session信息, 对多服务支持友好

JWT的工作原理

1.是在服务器身份验证之后,将生成一个JSON对象并将其发送回用户,示例如下: {"UserName": "Chongchong","Role": "Admin","Expire": "2018-08-08 20:15:56"}

  1. 之后,当用户与服务器通信时,客户在请求中发回JSON对象JWT

  2. 为了防止用户篡改数据,服务器将在生成对象时添加签名,并对发回的数据进行验证

验证原理(发送过去的数据和密匙解析出来的数据是否一致)

它验证的方法其实很简单,只要把header做base64url解码,就能知道JWT用的什么算法做的签名,然后用这个算法,再次用同样的逻辑对header和payload做一次签名, 并比较这个签名是否与JWT本身包含的第三个部分的串是否完全相同,只要不同,就可以认为这个JWT是一个被篡改过的串,自然就属于验证失败了。接收方生成签名的时候必须使用跟JWT发送方相同的密钥

 

idea里怎么操作

1.导入依赖

<!--令牌依赖-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

2.

@Component
​
//生成
public class JwtUtils {
    private static String singKey = "itheima";
    private  static Long expire =43200000L;
​
    public static String  generateJwt(Map<String,Object> clamis) {
        String jwt = Jwts.builder()
                .addClaims(clamis)
                .signWith(SignatureAlgorithm.ES256, singKey)
                .setExpiration(new Date(System.currentTimeMillis() + expire))
                .compact();
        return jwt;
​
    }
    
    //解析
    public static Claims parseJwt(String jwt) {
        Claims claims = Jwts.parser()
                .setSigningKey(singKey)
                .parseClaimsJws(jwt)
                .getBody();
        return  claims ;
    }
​

Claims是Map的实现类可以通过键获取值

3.令牌技术工具类集成

导入依赖 ,创建utils包,创建jwtUitls工具类 在controller层创建LoginController 完成登录给令牌功能

1.工具类引入

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;
    }
}

2.使用

@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("用户名或密码错误");
    }
}

统一拦截:

统一拦截请求,在服务端,我们可以通过两种手段实现:过滤器Filter、拦截器Interceptor。

过滤器与拦截器的区别

触发顺序不一样, 过滤器的触发顺序在拦截器前,也就是先执行过滤器,在执行拦截器, 过滤前 - 拦截前 - Action处理 - 拦截后 - 过滤后。 过滤器之间的执行顺序嵌套的, 但是拦截器执行顺序也是嵌套执行的但是,因为有三个方法,会先执行pre方法,再一起执行post方法,最后一起执行after方法 实现方式不同,过滤器基于函数回调实现,拦截器基于动态代理(反射)实现 拦截器是基于java的反射机制的,而过滤器是基于函数回调。 拦截器不依赖与servlet容器,过滤器依赖与servlet容器。 拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。 拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。 在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次 拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。

aop与过滤器,拦截器的区别

过滤器,拦截器拦截的是URL。AOP拦截的是类的元数据(包、类、方法名、参数等)。 过滤器并没有定义业务用于执行逻辑前、后等,仅仅是请求到达就执行。 拦截器有三个方法,相对于过滤器更加细致,有被拦截逻辑执行前、后等。 AOP针对具体的代码,能够实现更加复杂的业务逻辑。 三者功能类似,但各有优势,从过滤器 -> 拦截器 -> 切面,拦截规则越来越细致。 执行顺序依次是过滤器、拦截器、切面。

1.过滤器
  • 概念:Filter 过滤器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。

  • 过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。

  • 过滤器一般完成一些通用的操作,比如:登陆鉴权、统一编码处理、敏感字符处理等等…

1.快速入门

 

1.Servlet 三大组件 Servlet、Filter、Listener 在传统项目中需要在 web.xml 中进行相应的配置。Servlet 3.0 开始在 javax.servlet.annotation 包下提供 3 个对应的 @WebServlet、@WebFilter、@WebListener 注解来简化操作。

2、@WebServlet、@WebFilter、@WebListener 写在对应的 Servlet、Filter、Listener 类上作为标识,从而不需要在 web.xml 中进行配置了。

3.Spring Boot 应用中这三个注解默认是不被扫描的,需要在项目启动类上添加 @ServletComponentScan 注解, 表示对 Servlet 组件扫描。

总结

Filter是依赖于Servlet的,需要导入Servlet的依赖 过滤器会在容器启动时被初始化,并且只会初始化一次 doFilter()方法在目标请求被拦截前执行,放行调用filterChain.doFilter(servletRequest,servletResponse);具体变量名根据方法体而变 destroy()方法在容器销毁时执行,只执行一次 Filter可以拦截所有请求,包括静态资源, 过滤器基于函数回调实现,

扩展

过滤器,监听器和Servlet是JavaWeb三大组件之一,它们的在SpringBoot中的使用方式都差不多区别如下 使用注解方式使用时都需要在启动类加上@ServletComponentScan注解 三个注解为@WebSrvlet,@WebFilter和@Weblistener Servlet类继承的是HttpServlet,Filter实现的是Filter类,Listener实现ServletContextListener类 使用Bean注入方式使用时,Filter组件方法返回类型是FilterRegistrationBean,Listener组件方法返回类型为ServletListenerRefistrationBean,Servlet组件返回类型为ServletRefistrationBean

Filter 拦截路径
拦截路径urlPattern值含义
拦截具体路径/login只有访问 /login 路径时,才会被拦截
目录拦截/emps/*访问/emps下的所有资源,都会被拦截
拦截所有/*访问所有资源,都会被拦截

实现登录拦截

流程图

 

1.过滤器登录拦截代码实现

@WebFilter(urlPatterns = "/*") //拦截所有资源路径
public class loginCheckFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }
​
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
​
        System.out.println("你被拦截了");
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        //1.获取请求路径
        String url = request.getRequestURI().toString();
        //2.判断是否是登录请求
        if (url.contains("login")) {
            //放行
            filterChain.doFilter(servletRequest,servletResponse);
            return;
        }
        //3.获取请求头
        String jwt = request.getHeader("token");
        //4.判断是否存在token
        
        if (jwt==null) {
            notLoginResponse(servletResponse);//抽取成方法
            //拦截
            return;
        }
        //解析令牌
        try {
            Claims claims = JwtUtils.parseJWT(jwt);
            //放行
            filterChain.doFilter(servletRequest,servletResponse);
        } catch (Exception e) {
            e.printStackTrace();
            //令牌失效
            notLoginResponse(servletResponse);
        }
​
    }
    
   
    private void notLoginResponse(ServletResponse servletResponse) throws IOException {
        //返回统一Not_LOGIN结果给前端
        Result  errorResult=Result.error("Not_LOGIN");
        //怎么把errorResult对象转换未JOSN对象
        //java 对象 JOSN之间的映射
        ObjectMapper objectMapper = new ObjectMapper();
        //把java对象变成josn字符串
        String json = objectMapper.writeValueAsString(errorResult);
        //返回到前端
        servletResponse.getWriter().write(json);
    }
​
​
    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

拦截器

使用方式

1.编写拦截器类并实现HandlerInterceptor 接口 2.实现HandlerInterceptor 接口的preHandle,postHandle和afterHandle方法 3.编写配置类,注意加上@configuration注解,并继承WebMvcConfigurer接口

总结

拦截器依赖于SpringMvc的,需要导入Mvc的依赖 preHandle() 在目标请求完成之前执行。有返回值Boolean类型,true:表示放行 postHandle() 在目标请求之完成后执行。 afterCompletion() 在整个请求完成之后【modelAndView已被渲染执行】。 拦截器只能拦截action请求,不包括静态资源(有待验证) 基于java反射机制实现

拦截范围 :

拦截一级目录/*

拦截所有/**

拦截路径 :

拦截路径urlPattern值含义
拦截具体路径/login只有访问 /login 路径时,才会被拦截
目录拦截/emps/*访问/emps下的下一级资源,如: /emps/1 ,但是 不会拦截 /emps/list/1,/emps/list/1/2
目录拦截/emps/**访问/emps下的所有资源,都会被拦截
拦截所有/**访问所有资源,都会被拦截
1.快速入门

1.定义拦截器,实现HandlerInterceptor接口,重写所有方法

@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
    //目标资源方法执行前执行 , true : 放行 ; false : 不放行,拦截 ;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle ....");
        //如果校验通过放行
        return false;
    }
​
    //目标资源方法执行后执行
    @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 ....");
    }
​
}

2.注册拦截器

​
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;
​
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");
    }
}

登录拦截器代码实现

@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("我是拦截器,你被拦截了");
​
        //1.获取请求路径
        String url = request.getRequestURI().toString();
        //2.判断是否是登录请求
        if (url.contains("login")) {
            //放行
            return true;
        }
        //3.获取请求头
        String jwt = request.getHeader("token");
        //4.判断是否存在token 如果存在解析令牌
        if (jwt==null) {
            notLoginResponse(response);
            //拦截
            return false;
        }
        //解析令牌
        try {
            Claims claims = JwtUtils.parseJWT(jwt);
            //放行
           return true;
        } catch (Exception e) {
            e.printStackTrace();
            //令牌失效
            notLoginResponse(response);
            return false;
        }
​
    }
​
    private void notLoginResponse(ServletResponse servletResponse) throws IOException {
        //返回统一Not_LOGIN结果给前端
        Result errorResult=Result.error("Not_LOGIN");
        //怎么把errorResult对象转换未JOSN对象
        //java 对象 JOSN之间的映射
        ObjectMapper objectMapper = new ObjectMapper();
        //把java对象变成josn字符串
        String json = objectMapper.writeValueAsString(errorResult);
        //返回到前端
        servletResponse.getWriter().write(json);
    }
​
​
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }
​
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

异常处理

两种方案:

1.Controller 层进行异常捕捉;

2.全局异常处理

创建异常包 创建类接入@ExceptionHandler(Exception.class) @Slf4j 实现方法

 @Slf4j
 public class GlobalExceptionHandler {
 @ExceptionHandler(Exception.class)
    public Result ex(Exception ex) {
        log.error("全局处理异常"+ex);
        return Result.error("系统繁忙,请稍后重试...");
    }
​
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值