java后端之登录认证

  • 基础登录功能:根据提供的用户名和密码判断是否存在于数据库
    LoginController.java
java 复制代码
@RestController
@Slf4j
public class LoginController {

    @Autowired
    private UserService userService;

    @PostMapping("/login")
    public Result login(@RequestBody User user) {
        log.info("user: {}", user);
        User u = userService.login(user);
        return u != null ? Result.success(u) : Result.error("用户名或密码错误");
    }
}

UserController.java

java 复制代码
@Service
public class UserServiceImpl implements com.diaryback.Service.UserService {

    @Autowired
    private UserMapper userMapper;
    public User login(User user) {
        return userMapper.getUserByIdAndPassword(user);
    }
}

登录校验

在用户未登录情况下访问需要登录才能使用的业务,会跳转到登录界面

会话技术

  • 会话:用户通过浏览器访问服务器资源时建立会话,直到一方断开连接会话结束。一次会话中可以包含多次请求和响应
  • 会话跟踪:服务器需要识别多个请求是否来自同一个浏览器,以便在同一个会话的多个请求之间共享数据

服务器端存储Cookie,响应时自动加上Cookie到客户端,客户端下次请求会自动加上cookie

不允许跨域请求

Session

Session技术基于cookie,首次访问服务端时创建session,并将sessionID附加到cookie

令牌

令牌中存储用户的身份信息以及需要共享的数据,存储在客户端

JWT令牌

jwt:JSON Web Token,将原始JSON数据进行安全封装

jwt三组成部分:

  • Header:记录令牌类型,签名算法
  • Payload:有效载荷,携带自定义信息,默认信息和有效期等
  • Signature:签名,确保安全性。将header和payload加密计算而来

引入依赖:

java 复制代码
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.12.3</version>
        </dependency>

生成jwt字符串:需要指定签名算法、密钥以及自定义内容和过期时间

java 复制代码
    /**
     * 测试生成JWT
     */
    @Test
    public void testGenJWT(){
        Map<String, Object> claims = new HashMap<>();
        claims.put("id", 1);
        claims.put("name", "maria");
        String jwt = Jwts.builder().signWith(SignatureAlgorithm.HS256, "AlexandarHamiltonWeAreWaitingInTheWingsForYou")//设置加密方式和加密密钥
                .claims(claims)//设置自定义内容
                .expiration(new Date(System.currentTimeMillis() + 3600 * 1000))//设置有效期为一个小时
                .compact();//生成字符串

        System.out.println(jwt);
    }

解析时需要指定签名的密钥

java 复制代码
    /**
     * 测试解析JWT
     */
    @Test
    public void testParseJWT() {
        Claims claims = Jwts.parser().setSigningKey("AlexandarHamiltonWeAreWaitingInTheWingsForYou")//设置解密密钥
                .build().parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoibWFyaWEiLCJpZCI6MSwiZXhwIjoxNzM3NzExMjQ3fQ.AD2hdUzHdzq9qA0ZulvOyWA857tuRUWChnEX2P1ebcI")
                .getBody();
        System.out.println(claims);
    }

JWT工具类:jwtUtil

java 复制代码
package com.diaryback.Utils;


import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;
import java.util.Map;

public class JwtUtil {

    private static String signKey = "AlexandarHamiltonWeAreWaitingInTheWingsForYou";
    private static Long expire = 432000000L;

    /**
     * 生成jwt令牌
     */
    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令牌
     */
    public static Claims parseJwt(String jwt){
        Claims claims = Jwts.parser()
                .setSigningKey(signKey)
                .build()
                .parseClaimsJws(jwt)
                .getBody();
        return claims;
    }
}

Filter过滤器

可以拦截对资源的请求,通常用于登录校验、统一编码等

  • 定义Filter:定义一个类,实现Filter接口,重写所有方法(init(), destroy(), doFilter())
  • 配置Filter:Filter类加上@WebFilter注解,配置拦截资源的路径,同时启动类加上@ServletComponentScan开启Servlet组件支持

拦截路径的配置:

过滤器链:一个web应用中可以配置多个过滤器行程过滤器链

java 复制代码
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.annotation.WebFilter;

import javax.servlet.*;
import java.io.IOException;

@WebFilter(urlPatterns = "/*")
public class DemoFilter implements jakarta.servlet.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("Demo 执行过滤操作...放行前逻辑");
        //放行
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("Demo 执行过滤操作...放行后逻辑");
    }

    @Override
    public void destroy() {
//        Filter.super.destroy();
        System.out.println("销毁过滤器");
    }
}

登录校验的Filter流程:

LoginFilter.java

java 复制代码
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONPObject;
import com.diaryback.Pojo.Result;
import com.diaryback.Utils.JwtUtil;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.http.HttpRequest;

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

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) servletRequest;//强制类型转换,将ServletRequest转换为其子类HttpServletRequest
        HttpServletResponse resp = (HttpServletResponse) servletResponse;

        String url = req.getRequestURL().toString();
        log.info("请求的url:{}", url);

        // 如果是登录请求则放行
        if(url.contains("login")){
            log.info("登录请求,放行");
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }

        //获取请求头中的令牌

        String jwt = req.getHeader("token");
        //判断请求头中是否有令牌
        if(!StringUtils.hasLength(jwt)){
            log.info("未登录,不允许访问");
            Result error = Result.error("NOT_LOGIN");

            //将Result转换为JSON格式传递给前端,使用fastJson包
            String notLogin = JSON.toJSONString(error);
            resp.getWriter().write(notLogin);
            return;
        }

        //解析token校验令牌
        try {
            JwtUtil.parseJwt(jwt);
        } catch (Exception e) {//jwt解析失败
            e.printStackTrace();
            log.info("解析失败,不允许访问");
            Result error = Result.error("NOT_LOGIN");
            String notLogin = JSON.toJSONString(error);
            resp.getWriter().write(notLogin);
            return;
        }

        //放行
        log.info("已登录,放行");
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

Interceptor拦截器

Interceptor:作用类似于Filter,拦截请求,在指定方法调用前后执行预先设定的代码

  • 定义拦截器,实现HandlerInterceptor接口,并重写所有方法(preHandler, postHandle, afterCompletion)
  • 注册拦截器,需要在配置类中注册

LoginCheckInterceptor.java

java 复制代码
package com.diaryback.Interceptor;


import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

@Component //将拦截器交给Spring容器管理
public class LoginCheckInterceptor implements HandlerInterceptor {
    @Override //请求处理前调用,返回false则请求不会被处理
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle执行");
        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执行");
    }
}

WebConfig.java

java 复制代码
package com.diaryback.config;


import com.diaryback.Interceptor.LoginCheckInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration //声明是配置类
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //添加拦截器
        registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");
    }
}

拦截路径:addPathPatterns()定义需要拦截哪些资源,excludePathPatterns()定义不需要拦截哪些资源

拦截器执行流程:

登录校验拦截器

java 复制代码
```java
package com.diaryback.Interceptor;


import com.alibaba.fastjson.JSONObject;
import com.diaryback.Pojo.Result;
import com.diaryback.Utils.JwtUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

@Slf4j
@Component //将拦截器交给Spring容器管理
public class LoginCheckInterceptor implements HandlerInterceptor {
    @Override //请求处理前调用,返回false则请求不会被处理
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String url = request.getRequestURL().toString();
        log.info("preHandle执行,url: {}", url);

        if(url.contains("login")){
            log.info("放行");
            return true;
        }

        String jwt = request.getHeader("token");

        //检查是否有令牌
        if(!StringUtils.hasLength(jwt)){
            log.info("未登录");
            Result error = Result.error("NOT_LOGIN");
            String notLogin = JSONObject.toJSONString(error);
            response.getWriter().write(notLogin);
            return false;
        }

        //解析令牌
        try {
            JwtUtil.parseJwt(jwt);
        } catch (Exception e) {
            e.printStackTrace();
            log.info("令牌无效");
            Result error = Result.error("NOT_LOGIN");
            String notLogin = JSONObject.toJSONString(error);
            response.getWriter().write(notLogin);
            return false;
        }

        //放行
        log.info("放行");
        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执行");
    }
}

异常处理

定义全局异常处理器

java 复制代码
import com.diaryback.Pojo.Result;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * 全局异常处理
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)//指定处理的异常类型
    public Result ex(Exception ex){
        ex.printStackTrace();
        return Result.error("服务器异常");
    }
}
相关推荐
superman超哥12 分钟前
仓颉语言中基本数据类型的深度剖析与工程实践
c语言·开发语言·python·算法·仓颉
不爱吃糖的程序媛31 分钟前
Ascend C开发工具包(asc-devkit)技术解读
c语言·开发语言
bu_shuo32 分钟前
MATLAB奔溃记录
开发语言·matlab
韩立学长34 分钟前
【开题答辩实录分享】以《自助游网站的设计与实现》为例进行选题答辩实录分享
java·mysql·spring
ss27337 分钟前
线程池:任务队列、工作线程与生命周期管理
java·后端
不像程序员的程序媛41 分钟前
Spring的cacheEvict
java·后端·spring
Learner__Q42 分钟前
每天五分钟:滑动窗口-LeetCode高频题解析_day3
python·算法·leetcode
————A1 小时前
强化学习----->轨迹、回报、折扣因子和回合
人工智能·python
SAP小崔说事儿1 小时前
在数据库中将字符串拆分成表单(SQL和HANA版本)
java·数据库·sql·sap·hana·字符串拆分·无锡sap
凌云若寒1 小时前
半导体代加工企业标签模板痛点的全景式解决方案
java