Javaweb学习笔记——后端实战6登录功能1

1、登录功能分析

需求:

首先需要有一个对象可以存放以下登陆数据:

两种方法:一个是在emp实体类中再添加一个属性,另一个是新建一个类封装以上属性

本文选用第二种,在pojo包中新建一个登录的实体类:

复制代码
package com.itheima.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginInfo {
    private Integer id;
    private String username;
    private String name;
    private String token;
}

三层架构如下:

同样的新建一个控制层类并添加如下代码:

复制代码
package com.itheima.controller;

import com.itheima.mapper.EmpMapper;
import com.itheima.pojo.Emp;
import com.itheima.pojo.LoginInfo;
import com.itheima.pojo.Result;
import com.itheima.service.EmpService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
public class LoginController {

    @Autowired
    private EmpService empservice;
    @PostMapping("/login")
    public Result login(@RequestBody Emp emp){
        log.info("员工登录:{}",emp);
        LoginInfo logininfo=empservice.Login(emp);
        if(logininfo!=null){//判断返回值是否存在数据!!!
            return Result.success(logininfo);
        }
        return Result.error("用户名或密码错误!!!");
    }
}

使用了if进行判断,如果为空代表数据库中并没有该用户名和密码的数据,如果有值才代表登陆成功!

之后调用emoservice层:

复制代码
LoginInfo Login(Emp emp);

对应的实现类添加如下代码,此处将token先赋值为空字符!

复制代码
@Override
public LoginInfo Login(Emp emp) {
    Emp e=empMapper.LoginDatabyup(emp);
    if(e!=null){
        log.info("员工信息:{}",e);
        return new LoginInfo(e.getId(),e.getUsername(),e.getName(),"");
    }
    return null;
}

最后在empmapper新增查询功能:

复制代码
@Select("select id,username,name from emp where username=#{username} and password=#{password}")
Emp LoginDatabyup(Emp emp);

通过用户名和密码实现员工信息查询!

2、登录校验

登录功能实现之后,发现不论是否进行登录,都可以通过网址进入到主页面并进行部门和员工的增删改查,这意味登录的作用没有任何意义,因此需要进行校验!!!

(1)会话技术

首先是方案一:cookie

虽然在http协议中没有cookie,但是它是http所支持的技术,并且带了请求头和响应头,从而可以帮助其自动进行获取信息并校验!

简单来说:第一次访问c1时,将数据存储在cookie中

之后访问c2时,将cookie存储的值拿出来使用

移动端譬如安卓ios不能使用

存储在cookie中,客户端可以自己删除并且浏览器也可以禁用第三方的cookie

由于前后端是分开开发的,服务器有两台,但是访问前端页面时将信息存储,后端的服务器是没有的,因此返回报错!

总结:

方案二:session

这种方法主要使用了集群的方法,中间的服务器是负载均衡服务器,第一次登录请求之后,浏览器返回负载服务器,之后随机分给一台服务器a。当第二次登录的时候,负载服务器可能将这个相应分配给服务器b,以此类推,在集群情况下,无法使用session!!!

总结:

方案三:令牌(主流)

a.JWT令牌

在第二部分末尾如果出现"=",表示是一个补位符号!!!

令牌长度不固定!!!

首先在pom文件中引入依赖:

复制代码
<!-- JWT依赖-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

之后在test包下继续新建一个类:JwtTest:

在属signWith的时候选择第三个,参数为字符串!

以下是完整的测试代码:

复制代码
@Test
public void testGenerateJwt(){
    Map<String, Object> dataMap=new HashMap<>();
    dataMap.put("id",1);
    dataMap.put("username","admin");//使用dataMap存储自定义信息
    String jet=Jwts.builder().signWith(SignatureAlgorithm.HS256, "itheima")//指定令牌签名的算法和签名的密钥(密钥如果使用二进制编码,可以先将密钥使用base64编码输入即可)
            .addClaims(dataMap)//添加自定义信息
            .setExpiration(new Date(System.currentTimeMillis()+1000*60*60))//指定令牌有限期为1小时,从当前时间+3600000毫秒后过期
            .compact();//构建令牌
    System.out.println(jet);
}

运行程序如下所示:

可以通过Base64进行解码:

其中exp表示令牌的有效时间:

之后添加解析令牌的代码进行验证,一定要在令牌的有限期内解析令牌,不然也会报错!

复制代码
@Test
public void testParaseJwt(){//解析令牌
    //输入上面生成的令牌
    String token="eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsImV4cCI6MTc2ODU0ODA1M30.aZ_bc3ZK84UjkNpLZshlRJoMLJG9nRu90I2uj2zQ3lk";
    Claims claims=Jwts.parser().setSigningKey("itheima")//将密钥输入!
            .parseClaimsJws(token)
            .getBody();
    System.out.println(claims);
}

结果如下:

总结:

之后生成令牌:

之后在工具类utils中新建一个类并导入以下内容:

复制代码
package com.itheima.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 signKey = "itheima";//密钥
    private static long expire = 60*60*1000*12;//12小时后令牌过期

    /**
     * 生成JWT令牌
     * @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生成代码以及相应数据:

复制代码
@Override
public LoginInfo Login(Emp emp) {
    Emp e=empMapper.LoginDatabyup(emp);
    if(e!=null){
        log.info("员工信息:{}",e);
        Map<String,Object> claims=new HashMap<>();
        claims.put("id",e.getId());
        claims.put("username",e.getUsername());
        String jwt=JwtUtils.generateJwt(claims);
        return new LoginInfo(e.getId(),e.getUsername(),e.getName(),jwt);
    }
    return null;
}

运行项目之后,在apifox发送信息,响应数据:

在项目中实践登录功能,登陆成功后显示 宋江

b.Filter过滤器

目前过滤器基本只用Filter

只有Filter校验通过请求才能通过,不然直接拦截!!!

开启过滤器的步骤:

首先新建一个软件包:filter并在包下新创建一个类:

之后引入filter的标准接口,是servlet下的filter!!!

之后重写接口中引入的三个方法(官方提供的接口中init和destroy是空参实现的,一般可以自己不定义),对于入门程序来说这里三个方法都实现:

在类中的代码如下所示:

复制代码
package com.itheima.filter;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;

@WebFilter(urlPatterns = "/*")//拦截到所有请求
@Slf4j
public class DemoFilter implements Filter {
    //初始化方法,只会在web服务器启动时,执行一次!
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("初始化过滤器...");
//        Filter.super.init(filterConfig);
    }

    //过滤方法,拦截到请求,都会执行此方法
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info("拦截到请求...");

    }

    //销毁方法,只会在web服务器关闭时,执行一次!
    @Override
    public void destroy() {
        log.info("销毁过滤器...");
        Filter.super.destroy();
    }
}

之后在启动类中添加以下注解:

复制代码
@ServletComponentScan//开启springboot对servlet组件的扫描

之后启动项目,在apifox发送登录请求,拦截成功:

但是目前代码中只是拦截到请求,之后需要"放行"

在doFilter中继续添加以下代码:

复制代码
//放行
filterChain.doFilter(servletRequest,servletResponse);

之后登录成功相应数据:

停止项目之后自动调用dertroy方法:

总结:

登录注意事项:

由于目前系统还没有涉及到注册功能,因此目前只有一个例外:登录!

当请求路径中有/login,表示是登录请求直接放行,否则需要对其携带的令牌进行检验!!!

之后在filter软件包中新建一个类:TokenFilter:

根据以上内容进行编写代码:

复制代码
package com.itheima.filter;

import com.itheima.utils.JwtUtils;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;

@Slf4j
@WebFilter(urlPatterns = "/*")//拦截所有请求进行判断
public class TokenFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info("拦截到请求...");
        HttpServletRequest request=(HttpServletRequest) servletRequest;
        HttpServletResponse response=(HttpServletResponse) servletResponse;
        //获取请求路径
        String url=request.getRequestURI();
        //判断请求路径是否为登录请求
        if(url.contains("/login")){
            filterChain.doFilter(request,response);
            return ;
        }

        //获取请求头的token
        String token=request.getHeader("token");
        //判断token是否存在,不存在返回401状态码
        if(token==null||token.isEmpty()){
            response.setStatus(401);
            return ;
        }

        //检验令牌,如果不通过响应401
        try{
            JwtUtils.parseJWT(token);
        }catch(Exception e){
            response.setStatus(401);
            return ;
        }

        //校验通过则放行
        log.info("令牌校验通过");
        filterChain.doFilter(request,response);
    }
}

之后启动项目在项目中进行实践,如果输入页面内的网址会显示以下操作(除了index页面,这可能和前端代码有关,不过就算进入index页面,一旦点击任何与数据相关的页面都会直接退出并显示如下内容)

详解:

拦截路径:

过滤器链:命名排序越靠前表示过滤器越先执行!:譬如一个是AbcFilter,一个是DFilter,这样第一个过滤器就会先执行!!!

之后复制DemoFilter到同样的软件包下,命名为AbcFilter

之后启动项目随机选取一个接口发送,结果如下所示:类似于先拦截后放行

总结:

c.interceptor

步骤:其中/** 表示拦截所有!

新建一个软件包:interceptor,并在包中新建一个类:DemoInterceptor(入门程序,都实验一次)

之后实现接口HandlerInterceptor,并使用ctrl+o重写这个类的所有实现方法(3个):

复制代码
package com.itheima.interceptor;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.Interceptor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

@Slf4j
@Component
public class DemoInterceptor implements HandlerInterceptor {
    @Override
    //在目标方法执行之前执行,true表示放行,false表示不放行
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("preHandle...");
        return true;
    }

    @Override
    //在目标方法执行之后执行
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle...");
    }

    @Override
    //在目标方法执行完成之后,视图渲染后执行
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion...");
    }
}

之后新建一个软件包:config,并新建一个类为:WebConfig

复制代码
package com.itheima.config;

import com.itheima.interceptor.DemoInterceptor;
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 DemoInterceptor demoInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(demoInterceptor).addPathPatterns("/**");//指定拦截路径
    }
}

在启动项目之前要确保所有的过滤器已经注释:

之后启动项目,从控制台可得:

总结:

之后在实际中,需要通过令牌检验interceptor,在interceptor包下新建一个类:TokenInterceptor

实现接口HandlerInterceptor,只需要重写第一个方法即可!

以下是这个类的代码:

复制代码
package com.itheima.interceptor;

import com.itheima.utils.JwtUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Slf4j
@Component
public class TokenInterceptor implements HandlerInterceptor {
    @Override//只重写这一个方法
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("拦截到请求...");
        //获取请求路径
        String url=request.getRequestURI();
        //判断请求路径是否为登录请求
        if(url.contains("/login")){
            return true;
        }

        //获取请求头的token
        String token=request.getHeader("token");
        //判断token是否存在,不存在返回401状态码
        if(token==null||token.isEmpty()){
            response.setStatus(401);
            return false;
        }

        //检验令牌,如果不通过响应401
        try{
            JwtUtils.parseJWT(token);
        }catch(Exception e){
            response.setStatus(401);
            return false;
        }

        //校验通过则放行
        log.info("令牌校验通过");
        return true;
    }
}

之后对webconfig进行修改:

复制代码
package com.itheima.config;

import com.itheima.interceptor.DemoInterceptor;
import com.itheima.interceptor.TokenInterceptor;
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 DemoInterceptor demoInterceptor;

    @Autowired
    private TokenInterceptor tokenInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(tokenInterceptor).addPathPatterns("/**");//指定拦截路径
    }
}

之后启动项目,不登录直接访问里边的某一页网址(除了index),出现如下效果:

拦截路径中关于/*和/**的区别:

整体流程:

之后实践,将demofilter(打开)、demoInterceptor和webconfig(将拦截器进行更改)进行配置。

启动项目,使用apifox发送任意一个接口请求,控制台结果如下:

总结:

之后只要让令牌生效的过滤器或者拦截器某一个打开即可,其余的都关闭!

这里保留了TokenFilter,其余均关闭!!!(都打开也可以,不过性能会降低!)

相关推荐
Yu_Lijing2 小时前
基于C++的《Head First设计模式》笔记——组合模式
c++·笔记·设计模式·组合模式
Engineer邓祥浩2 小时前
设计模式学习(17) 23-15 访问者模式
学习·设计模式·访问者模式
好奇龙猫2 小时前
【日语学习-日语知识点小记-日本語体系構造-JLPT-N2前期阶段-第一阶段(6):单词语法】
学习
C++实习生2 小时前
Visual Studio 2017 Enterprise 组件目录
后端·python·flask
举手2 小时前
UDP Echo Server(学习版)
linux·服务器·网络·网络协议·学习·udp
YCL大摆子2 小时前
Agent学习——1 day
学习
王柏龙2 小时前
ASP.NET Core 框架原生健康检查服务详解
后端·asp.net
ouliten2 小时前
C++笔记:std::tuple
c++·笔记
炽烈小老头2 小时前
【每天学习一点算法 2026/01/20】汉明距离
学习·算法