目录
一、登录功能
1.1 思路
1.2 LoginController
1.3 EmpService
1.4 EmpServiceImpl
1.5 EmpMapper
1.6 启动服务-测试
1.7 前后端联调
二、登录校验(重点)
2.1 问题
2.2 问题分析
2.3 登录校验编辑
2.4 会话技术
2.4.1 会话技术
2.4.2 会话跟踪方案对比
2.5 JWT令牌
2.5.1 简介
2.5.2 应用场景
2.5.3 jwt-生成
5.5.3.1 引入包
5.5.3.2 生成JWT
5.5.3.3 解析JWT
2.6 案例实现JWT
2.6.1 新建JwtUtils工具类
2.6.2 LoginController
2.6.3 启动服务-测试
2.6.4 前后端联调
2.7 过滤器Filter
2.7.1 概述
2.7.2 快速入门
2.7.2.1 新增DemoFilter
2.7.2.2 SpringBootProjectTestApplication
2.7.2.3 启动服务-测试
2.7.2.4 总结
2.7.3 详解(执行流程、拦截路径、过滤器链)
2.7.3.1 执行流程
2.7.3.2 Filter-拦截路径 编辑
2.7.3.3 过滤器链(优先级按过滤器名自然排序)
2.7.3.4 总结
2.7.4 案例-登录校验-Filter
2.7.4.1 思路
2.7.4.2 pom.xml引入依赖fastjson
2.7.4.3 新建工具类拦截器LoginCheckFilter
2.7.4.4 启动服务-测试
2.8 拦截器Interceptor
2.8.1 简介
2.8.2 快速入门
2.8.2.1 新建LoginCheckInterceptor
2.8.2.2 WebConfig
2.8.2.3 启动服务测试
2.8.3 详解
2.8.3.1 拦截路径
2.8.3.2 拦截器- 执行流程
2.8.4 案例-登录校验-Interceptor
2.8.4.1 LoginCheckInterceptor
2.8.4.2 WebConfig
2.8.4.3 启动服务-测试
三、异常处理
3.1 异常现象--新增重复名称部门
3.2 思考如何处理
3.3 全家异常处理
3.3.1 异常类GlobalExceptionHandler
3.3.2 启动服务-测试
3.3.3 总结
前言:实现案例的登录功能、登录校验和异常处理
package com.runa.controller;
import com.runa.pojo.Emp;
import com.runa.pojo.Result;
import com.runa.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.RestController;
@Slf4j
@RestController
public class LoginController {
@Autowired
private EmpService empService;
@PostMapping("/login")
public Result login(@RequestBody Emp emp){
log.info("登录的用户:{}",emp);
Emp e = empService.login(emp);
return e != null ? Result.success():Result.error("用户名或密码错误");
}
}
package com.runa.service;
import com.runa.pojo.Emp;
import com.runa.pojo.PageBean;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
import java.util.List;
public interface EmpService {
/**
* 分页查询 不带条件
* @param page
* @param pageSize
* @return
*/
// PageBean page(Integer page, Integer pageSize);
/**
* 分页查询 带条件
* @param page
* @param pageSize
* @return
*/
PageBean page(Integer page, Integer pageSize,String name, Short gender, LocalDate begin, LocalDate end);
/**
* 批量删除员工
* @param ids
*/
void delete(List ids);
/**
* 新增员工
* @param emp
*/
void save(Emp emp);
/**
* 根据id查询员工
* @param id
* @return
*/
Emp getById(Integer id);
/**
* 修改员工
* @param emp
*/
void update(Emp emp);
/**
* 登录
* @param emp
* @return
*/
Emp login(Emp emp);
}
package com.runa.service.impl;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.runa.mapper.EmpMapper;
import com.runa.pojo.Emp;
import com.runa.pojo.PageBean;
import com.runa.service.EmpService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
@Service
public class EmpServiceImpl implements EmpService {
@Autowired
private EmpMapper empMapper;
/**
* 分页查询 pagehelper+组合查询
* @param page
* @param pageSize
* @param name
* @param gender
* @param begin
* @param end
* @return
*/
@Override
public PageBean page(Integer page, Integer pageSize, String name, Short gender, LocalDate begin, LocalDate end) {
// 1 设置分页参数
PageHelper.startPage(page, pageSize);
// 2 执行查询
List empList = empMapper.list(name, gender, begin, end);
Page p = (Page) empList;
// 3 封装PangeBean对象
PageBean pageBean = new PageBean(p.getTotal(),((Page) empList).getResult());
return pageBean;
}
/**
* 批量删除员工
* @param ids
*/
@Override
public void delete(List ids) {
empMapper.delete(ids);
}
/**
* 新增员工
* @param emp
*/
@Override
public void save(Emp emp) {
emp.setCreateTime(LocalDateTime.now());
emp.setUpdateTime(LocalDateTime.now());
empMapper.insert(emp);
}
/**
* 根据ID查询员工
* @param id
* @return
*/
@Override
public Emp getById(Integer id) {
return empMapper.getByID(id);
}
/**
* 修改员工
* @param emp
*/
@Override
public void update(Emp emp) {
emp.setUpdateTime(LocalDateTime.now());
empMapper.update(emp);
}
/**
* 登录
* @param emp
* @return
*/
@Override
public Emp login(Emp emp) {
return empMapper.getByUsernameAndPassword(emp);
}
/**
* 分页查询 加pagehelper
* @param page
* @param pageSize
* @return
*/
// @Override
// public PageBean page(Integer page, Integer pageSize) {
// // 1 设置分页参数
// PageHelper.startPage(page, pageSize);
//
// // 2 执行查询
// List empList = empMapper.list();
// Page p = (Page) empList;
//
// // 3 封装PangeBean对象
// PageBean pageBean = new PageBean(p.getTotal(),((Page) empList).getResult());
// return pageBean;
// }
/**
* 分页查询
* @param page
* @param pageSize
* @return
*/
// @Override
// public PageBean page(Integer page, Integer pageSize) {
// // 1 获取总记录数
// Long count = empMapper.count();
//
// // 2 获取分页查询结果列表
// Integer start = (page - 1) * pageSize;
// List empList = empMapper.page(start, pageSize);
//
// // 3 封装PangeBean对象
// PageBean pageBean = new PageBean(count,empList);
// }
}
package com.runa.mapper;
import com.runa.pojo.Emp;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.time.LocalDate;
import java.util.List;
/**
* 员工管理
*/
@Mapper
public interface EmpMapper {
/**
* 查询总记录数
* @return
*/
// @Select("select count(*) from emp")
// public Long count();
/**
* 分页查询 获取列表数据
* @param start
* @param pageSize
* @return
*/
// @Select("select * from emp limit #{start}, #{pageSize}")
// public List page(Integer start, Integer pageSize);
/**
* 使用pagehelper的员工信息查询
* @return
*/
// @Select("select * from emp")
// public List list();
/**
* 使用pagehelper的员工信息查询(带条件)--动态sql
* 使用xml注解sql
* @return
*/
public List list(String name, Short gender, LocalDate begin, LocalDate end);
/**
* 批量删除员工
* @param ids
*/
void delete(List ids);
/**
* 新增员工
* @param emp
*/
@Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time)" +
" values(#{username}, #{name}, #{gender}, #{image}, #{job}, #{entrydate}, #{deptId}, #{createTime}, #{updateTime})")
void insert(Emp emp);
/**
* 根据id查询员工
* @param id
* @return
*/
@Select("select * from emp where id = #{id}")
Emp getByID(Integer id);
/**
* 修改员工
* @param emp
*/
void update(Emp emp);
/**
* 登录
* @param emp
* @return
*/
@Select("select * from emp where username = #{username} and password = #{password}")
Emp getByUsernameAndPassword(Emp emp);
}
Cookies与Session例子,启动服务http://localhost:8080/c1
package com.runa.controller;
import com.runa.pojo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
/**
* HttpSession演示
*/
@Slf4j
@RestController
public class SessionController {
//设置Cookie
@GetMapping("/c1")
public Result cookie1(HttpServletResponse response){
response.addCookie(new Cookie("login_username","itheima")); //设置Cookie/响应Cookie
return Result.success();
}
//获取Cookie
@GetMapping("/c2")
public Result cookie2(HttpServletRequest request){
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
if(cookie.getName().equals("login_username")){
System.out.println("login_username: "+cookie.getValue()); //输出name为login_username的cookie
}
}
return Result.success();
}
@GetMapping("/s1")
public Result session1(HttpSession session){
log.info("HttpSession-s1: {}", session.hashCode());
session.setAttribute("loginUser", "tom"); //往session中存储数据
return Result.success();
}
@GetMapping("/s2")
public Result session2(HttpServletRequest request){
HttpSession session = request.getSession();
log.info("HttpSession-s2: {}", session.hashCode());
Object loginUser = session.getAttribute("loginUser"); //从session中获取数据
log.info("loginUser: {}", loginUser);
return Result.success(loginUser);
}
}
io.jsonwebtoken
jjwt
0.9.1
记得注释他//@SpringBootTest
package com.runa;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
//@SpringBootTest
class SpringBootProjectTestApplicationTests {
// @Test
// void contextLoads() {
// }
/**
* 测试JWT令牌的生成
*/
@Test
public void testGenJwt(){
Map claims = new HashMap<>();
claims.put("id",1);
claims.put("name","bocai");
String jwt = Jwts.builder()
.signWith(SignatureAlgorithm.HS256, "runa") // 签名算法 算法有哪些上官网
.setClaims(claims) //自定义的内容(载荷)
.setExpiration(new Date(System.currentTimeMillis() + 3600* 1000)) // 设置有效期为1h
.compact();
System.out.println(jwt);
}
}
生成的jwt令牌上官网
package com.runa;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
//@SpringBootTest
class SpringBootProjectTestApplicationTests {
// @Test
// void contextLoads() {
// }
/**
* 生成Jwt令牌
*/
@Test
public void testGenJwt(){
Map claims = new HashMap<>();
claims.put("id",1);
claims.put("name","bocai");
String jwt = Jwts.builder()
.signWith(SignatureAlgorithm.HS256, "runa") // 签名算法 算法有哪些上官网
.setClaims(claims) //自定义的内容(载荷)
.setExpiration(new Date(System.currentTimeMillis() + 3600* 1000)) // 设置有效期为1h
.compact();
System.out.println(jwt);
}
/**
* 解析Jwt
*/
@Test
public void testPareJwt(){
Claims claims = Jwts.parser()
.setSigningKey("runa") //runa要与前面生成一致
.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiYm9jYWkiLCJpZCI6MSwiZXhwIjoxNjkyNzc3MzAwfQ.KPqgKc5JS8j7GN7aPQ0GwQnUaGm78WWbzf2N7LGq34g")
.getBody();
System.out.println(claims);
}
}
package com.runa.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 = "runa";
private static Long expire = 43200000L; // 12h
/**
* 生成JWT令牌
* @param claims JWT第二部分负载 payload 中存储的内容
* @return
*/
public static String generateJwt(Map 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;
}
}
package com.runa.controller;
import com.runa.pojo.Emp;
import com.runa.pojo.Result;
import com.runa.service.EmpService;
import com.runa.utils.JwtUtils;
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.RestController;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestController
public class LoginController {
@Autowired
private EmpService empService;
@PostMapping("/login")
public Result login(@RequestBody Emp emp){
log.info("登录的用户:{}",emp);
Emp e = empService.login(emp);
// 登录成功,生成令牌,下发令牌
if(e != null){
Map claims = new HashMap<>();
claims.put("id",e.getId());
claims.put("name", e.getName());
claims.put("username", e.getUsername());
String jwt = JwtUtils.generateJwt(claims);
return Result.success(jwt);
}
// 登录失败,返回错误信息
return Result.error("用户名或密码错误");
}
}
package com.runa.filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter(urlPatterns = "/*")
public class DemoFilter implements Filter {
@Override // 初始化方法,只调用一次
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init初始化方法执行了");
}
@Override // 拦截到请求之后调用,调用多次
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("拦截到请求了~~~");
// 放行
filterChain.doFilter(servletRequest,servletResponse);
}
@Override // 销毁方法,只调用一次
public void destroy() {
System.out.println("destroy方法执行了");
}
}
package com.runa;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
@ServletComponentScan //开启了对servlet组件支持
@SpringBootApplication
public class SpringBootProjectTestApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootProjectTestApplication.class, args);
}
}
略
package com.runa.filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter(urlPatterns = "/*")
public class SecondFilter implements Filter {
@Override // 拦截到请求之后调用,调用多次
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("SecondFilter 拦截---2到请求了~~~放行之前逻辑");
// 放行
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("SecondFilter拦截---2到请求了~~~放行之后逻辑");
}
}
com.alibaba
fastjson
1.2.76
package com.runa.filter;
import com.alibaba.fastjson.JSONObject;
import com.runa.pojo.Result;
import com.runa.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
//@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
//1.获取请求url。
String url = req.getRequestURL().toString();
log.info("请求的url: {}",url);
//2.判断请求url中是否包含login,如果包含,说明是登录操作,放行。
if(url.contains("login")){
log.info("登录操作, 放行...");
chain.doFilter(request,response);
return;
}
//3.获取请求头中的令牌(token)。
String jwt = req.getHeader("token");
//4.判断令牌是否存在,如果不存在,返回错误结果(未登录)。
if(!StringUtils.hasLength(jwt)){
log.info("请求头token为空,返回未登录的信息");
Result error = Result.error("NOT_LOGIN");
//手动转换 对象--json --------> 阿里巴巴fastJSON
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return;
}
//5.解析token,如果解析失败,返回错误结果(未登录)。
try {
JwtUtils.parseJWT(jwt);
} catch (Exception e) {//jwt解析失败
e.printStackTrace();
log.info("解析令牌失败, 返回未登录错误信息");
Result error = Result.error("NOT_LOGIN");
//手动转换 对象--json --------> 阿里巴巴fastJSON
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return;
}
//6.放行。
log.info("令牌合法, 放行");
chain.doFilter(request, response);
}
}
记得将 DemoFilter 与SecondFilter 的@WebFilter(urlPatterns = "/*") 注释掉
package com.runa.interceptor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
@Override //目标资源方法运行前运行, 返回true: 放行, 放回false, 不放行
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, 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...");
}
}
package com.runa.config;
import com.runa.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("/**");
}
}
将过滤器 LoginCheckFilter @WebFilter(urlPatterns = "/*") 注释掉
package com.runa.interceptor;
import com.alibaba.fastjson.JSONObject;
import com.runa.pojo.Result;
import com.runa.utils.JwtUtils;
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;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@Slf4j
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
@Override //目标资源方法运行前运行, 返回true: 放行, 放回false, 不放行
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
//1.获取请求url。
String url = req.getRequestURL().toString();
log.info("请求的url: {}",url);
//2.判断请求url中是否包含login,如果包含,说明是登录操作,放行。
if(url.contains("login")){
log.info("登录操作, 放行...");
return true;
}
//3.获取请求头中的令牌(token)。
String jwt = req.getHeader("token");
//4.判断令牌是否存在,如果不存在,返回错误结果(未登录)。
if(!StringUtils.hasLength(jwt)){
log.info("请求头token为空,返回未登录的信息");
Result error = Result.error("NOT_LOGIN");
//手动转换 对象--json --------> 阿里巴巴fastJSON
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return false;
}
//5.解析token,如果解析失败,返回错误结果(未登录)。
try {
JwtUtils.parseJWT(jwt);
} catch (Exception e) {//jwt解析失败
e.printStackTrace();
log.info("解析令牌失败, 返回未登录错误信息");
Result error = Result.error("NOT_LOGIN");
//手动转换 对象--json --------> 阿里巴巴fastJSON
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return false;
}
//6.放行。
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...");
}
}
package com.runa.config;
import com.runa.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("/**").excludePathPatterns("/login");
}
}
注释Filter
package com.runa.exception;
import com.runa.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("对不起,操作失败,请联系管理员");
}
}
新建重复部门