【Day12】登录认证、异常处理

1 登录

先创建一个新的 controller 层:LoginController

java 复制代码
@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("error");
    }
}

在 service 层实现

service 层调用 mapper 层,mapper 层操作数据库

测试:

2 登录校验

刚才的程序有 bug,即只要改一下 url,可以跳过登录直接进入员工管理界面,此时需要校验

2.1 会话技术

会话:

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

会话跟踪

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

会话跟踪方案:

  • 客户端会话跟踪技术:Cookie
  • 服务端会话跟踪技术:Session
  • 令牌技术

Cookie 是一种小型数据存储,通常存储在用户浏览器中,可以由服务器在用户访问网站时发送给用户的浏览器,并由浏览器在随后的请求中发送回服务器

2.1.2 Session

Session 是另一种用于在用户和服务器之间保持状态的技术。与 Cookie 不同,Session 通常不直接存储在用户的浏览器中,而是存储在服务器端

2.1.3 令牌

JWT 令牌(JSON Web Token):

定义了一种简洁的、自包含 的格式,用于在通信双方以 json 数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的

自包含:JWT 包含所有必要的信息,不需要访问服务器来验证

JWT 令牌的组成:

  1. Header(头),记录令牌类型,签名算法等,例如{"alg":"HS256","type":"JWT"}
  2. Payload(有效载荷),携带一些自定义信息、默认信息等,例如{"id":"1","username":"Tom"}
  3. Signature(签名),防止 Token 被篡改、确保安全性。将 header、payload,并加入指定秘钥,通过指定签名算法计算而来

Header,Payload,Signature 字符串通过 Base64 编码变成 JWT 字符串

原理:

  1. 登录成功后,生成令牌
  2. 后续每个请求,都要携带 JWT 令牌,系统在每次请求处理之前,先校验令牌,通过后,再处理

生成 JWT 令牌:

1、引入依赖------在 pom.xml 中

XML 复制代码
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

2、使用 jwts 库下的**jwt.builder()**方法

(在单元测试中写)

java 复制代码
public void testJWT() {
        Map<String, Object> claim = new HashMap<>();
        claim.put("id", 1);
        claim.put("name", "wyn");

        String jwt = Jwts.builder()
                .signWith(SignatureAlgorithm.HS256, "mi_yao") // JWT 第一部分 Signature签名
                .setClaims(claim) // JWT 第二部分 Payload载荷
                .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000)) // 设置令牌有限期为1h
                .compact();// 生成 JWT 令牌(字符串)

        System.out.println(jwt);
        // print
        // eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoid3luIiwiaWQiOjEsImV4cCI6MTcyMTM5NDcwMn0.prh9gbgvW5PTWvogGqkjPE2ofrXz-5FVGkYoDkEaNoI}
    }

链式编程:

.signWith()

第一个参数是加密算法,第二个参数是密钥

.setClaims()

设置自定义的信息,参数是 map 类型

.setExpiration()

设置令牌有效时间

.compact()

最终生成 JWT 令牌的字符串

(注:每次生成的令牌不一样)

JWT 的解析:

java 复制代码
@Test
    public void testParseJWT() {
        Map<String, Object> claims = Jwts.parser()
                .setSigningKey("mi_yao") // 指定签名
                // 解析 JWS(JSON Web Signature),返回一个 JWS 对象
                .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoid3luIiwiaWQiOjEsImV4cCI6MTcyMTM5NDcwMn0.prh9gbgvW5PTWvogGqkjPE2ofrXz-5FVGkYoDkEaNoI")
                .getBody(); // 提取负载部分

        System.out.println(claims); // print {name=wyn, id=1, exp=1721394702}
    }

如果 JWT 输入不正确,就会报错

如果上面设置的 JWT 令牌有效时间过期了,也会报错

2.2 使用 JWT 令牌登录&校验

在 utils 软件包下导入一个工具类 JWTUtils:

java 复制代码
package com.wyn.tlias.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.Map;

public class JwtUtils {

    private static String key = "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, key)
                .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(key)
                .parseClaimsJws(jwt)
                .getBody();
        return claims;
    }
}

2.2.1 登录

在 LoginControlller 里面直接调用 JWTUtils:

claim 信息自己添加给一个 map,然后作为一个 Result 统一响应结果返回

java 复制代码
@RestController
public class LoginController {
    @Autowired
    private EmpService empService;

    /**
     * 登录,示例 username = "jinyong",password = 123456
     * @param emp
     * @return
     */
    @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",emp.getId());
            claims.put("username", emp.getUsername());
            claims.put("name",emp.getName());
//            claims.put("password", emp.getPassword()); // 注意不要封装密码

            String jwt = JwtUtils.generateJwt(claims);
            return Result.success(jwt);
        }
        return Result.error("error");
    }
}

测试,返回了一个 JWT 令牌

2.2.2 校验

现在拿到令牌,需要进行 统一拦截,如果令牌存在,就可以继续;如果没有,就返回登录页面

统一拦截两种解决方案:

  • 过滤器 Filter
  • 拦截器 Interceptor
过滤器 Filter

JavaWeb 三大组件(Servlet、Filter、Listener)之一

Servlet 是一个运行在服务器端的 Java 小程序,它是 Web 应用程序的基石。Servlet 可以响应客户端的请求(通常是 HTTP 请求),并生成响应

Filter 是一个在 Servlet 之前或之后执行的程序,用于在请求到达 Servlet 之前或响应发送给客户端之前进行预处理或后处理

Listener 是一个监听特定事件的对象,它可以在事件发生时接收通知并执行相应的处理逻辑

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

过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等

使用方法:

1.定义 Filter:定义一个类,实现 Filter 接口,并重写其所有方法(就重写一个就行

2.配置 Filter:Filter 类上加 @WebFilter 注解,配置拦截资源的路径。引导类上加@ServletcomponentScan 开启 Seflet 组件支持

在启动类里面加上注解

java 复制代码
@ServletComponentScan

然后启动,发现原来的登录,发送 json 数据,已经没有响应了,被拦截(拦截了没有后续,所以没有响应),仅仅单纯拦截,而没有 放行

放行:

调用方法

java 复制代码
filterChain.doFilter(servletRequest,servletResponse);

过滤器链

一个web应用中,可以配置多个过滤器,这多个过滤器就形成了一个过滤器链

校验

测试:

1、访问 /login,获得一个 jwt 令牌

eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpudWxsLCJpZCI6bnVsbCwidXNlcm5hbWUiOiJqaW55b25nIiwiZXhwIjoxNzIxNTE0NzI3fQ.vS6YA_M-9ptBG01ly0ELjti1QvWzs_tlqeHClhh6o-M

2、访问其他路径时,要 在请求头中携带令牌

如果不带:

携带:

拦截器 Interceptor

拦截器是一种动态拦截方法调用的机制,类似于过滤器。是 Spring 框架中提供的,用来动态拦截控制器方法的执行
作用:拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码

使用方法:

1、定义拦截器,实现 Handlerlnterceptor 接口,并重写其所有方法

注意加上注解 @component

2、注册拦截器

流程

登录校验:

java 复制代码
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
    @Override // 目标资源方法运行前运行,返回 true 代表放行;返回 false 代表不放行
    // 登录校验在这里
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 注意形参直接是 HttpServletRequest 和 HttpServletResponse,不需要强制转换

        // 获取 url,这段可以不用写,因为在配置文件里规定了不拦截/login
        String url = request.getRequestURL().toString();
        if (url.contains("login")){
            // 登录,放行
            return true;
        }

        // 获取令牌
        String jwt = request.getHeader("token");
        if (!StringUtils.hasLength(jwt)) {
            // 令牌不存在,不放行
            Result error = Result.error("NOT_LOGIN");
            String notLogin = JSONObject.toJSONString(error);
            response.getWriter().write(notLogin);
            return false;
        }

        // 验证令牌
        try {
            JwtUtils.parseJWT(jwt);
        } catch (Exception e) {
            e.printStackTrace();

            // 令牌错误,不放行
            Result error = Result.error("NOT_LOGIN");
            String notLogin = JSONObject.toJSONString(error);
            response.getWriter().write(notLogin);
            return false;
        }
        // 令牌正确,放行
        return true;
    }

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

3 异常处理

使用全局异常处理器(GlobalExceptionHandler):

相关推荐
weixin_462428479 分钟前
使用 Caffeine 缓存并在业务方法上通过注解实现每3到5秒更新缓存
java·缓存
程序媛小果10 分钟前
基于java+SpringBoot+Vue的桂林旅游景点导游平台设计与实现
java·vue.js·spring boot
骑鱼过海的猫12312 分钟前
【java】java通过s3访问ceph报错
java·ceph·iphone
杨充18 分钟前
13.观察者模式设计思想
java·redis·观察者模式
Lizhihao_20 分钟前
JAVA-队列
java·开发语言
喵叔哟29 分钟前
重构代码之移动字段
java·数据库·重构
喵叔哟29 分钟前
重构代码之取消临时字段
java·前端·重构
fa_lsyk32 分钟前
maven环境搭建
java·maven
念白44333 分钟前
智能病历xml提取
数据库·sql·oracle
qingy_204637 分钟前
【JavaWeb】JavaWeb入门之XML详解
数据库·oracle