目录
案例-登录认证
在前一天的案例基础上实现
需求
在登录界面中,我们可以输入用户的用户名以及密码,然后点击 “登录” 按钮就要请求服务器,服务端判断用户输入的用户名或者密码是否正确。如果正确,则返回成功结果,前端跳转至系统首页面,若未登陆则跳转登陆页面进行登陆。
代码实现
pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.9</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.itheima</groupId>
<artifactId>day10-SpringBoot-Case</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>day10-SpringBoot-Case</name>
<description>day10-SpringBoot-Case</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--引入分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.6</version>
</dependency>
<!--阿里云OSS依赖-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<!-- no more than 2.3.3-->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<!--jwt依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!--用于json和对象转换的工具类-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
工具类
JwtUtils
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 = "shifan";
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;
}
}
传统会话技术测试
SessionController
package com.itheima.controller;
import com.itheima.pojo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* 基于Cookie的会话技术测试
*/
@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);
}
}
登录功能实现
LoginController
package com.itheima.controller;
import com.itheima.pojo.Emp;
import com.itheima.pojo.Result;
import com.itheima.service.EmpService;
import com.itheima.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 loginEmp= empService.login(emp);
//使用jwt令牌技术实现会话内数据共享
if (loginEmp!=null){
Map<String,Object> claims = new HashMap<>();
claims.put("id",loginEmp.getId());
claims.put("username",loginEmp.getUsername());
claims.put("name",loginEmp.getName());
return Result.success(JwtUtils.generateJwt(claims));
}
return Result.error("用户名或密码错误");
}
}
过滤器
过滤器测试
DemoFilter
package com.itheima.filter;
import javax.servlet.*;
import java.io.IOException;
//@WebFilter(urlPatterns = "/*")//配置过滤器要拦截的请求路径(/*表示拦截浏览器所有请求)
public class DemoFilter implements Filter {
@Override//初始化方法,只执行一次
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("初始化方法执行了");
}
@Override//拦截到请求后就调用
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
System.out.println("拦截到了请求");
System.out.println("DemoFilter 放行前逻辑");
//放行
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("DemoFilter 放行后逻辑");
}
@Override//销毁方法,只调用一次
public void destroy() {
System.out.println("destroy销毁方法执行了");
}
}
AFilter
package com.itheima.filter;
import javax.servlet.*;
import java.io.IOException;
/**
* 过滤器链
* 存在多个过滤器时,就会形成过滤器链以及过滤器链的执行顺序
* 以注解方式配置的Filter过滤器,它的执行优先级是按时过滤器类名的自动排序确定的,
* 类名排名越靠前,优先级越高。
* 所以AFilter过滤器先于DemoFilter过滤器执行放行前逻辑,但后于DemoFilter过滤器执行放行后逻辑
*/
//@WebFilter(urlPatterns = "/*")//配置过滤器要拦截的请求路径(/*表示拦截浏览器所有请求)
public class AFilter implements Filter {
@Override//初始化方法,只执行一次
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("初始化方法执行了");
}
@Override//拦截到请求后就调用
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
System.out.println("拦截到了请求");
System.out.println("AFilter 放行前逻辑");
//放行
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("AFilter 放行后逻辑");
}
@Override//销毁方法,只调用一次
public void destroy() {
System.out.println("destroy销毁方法执行了");
}
}
过滤器实现登录功能
LoginCheckFilter
package com.itheima.filter;
import com.alibaba.fastjson.JSONObject;
import com.itheima.pojo.Result;
import com.itheima.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
@WebFilter(urlPatterns = "/*")//拦截所有请求
public class LoginCheckFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
//前置:强制转换为http协议的请求对象,响应对象(原因:要使用子类中的独有方法)
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//1.获取请求url
String url = request.getRequestURL().toString();
log.info("请求路径:{}"+url);
//2.判断请求路径中是否包含login
if (url.contains("/login")){
//放行
filterChain.doFilter(request,response);
return;
}
//3.获取请求头中的令牌token
String token = request.getHeader("token");
log.info("从请求头中获取令牌token:{}"+token);
//4.判断令牌是否存在,若不存在,返回错误结果(未登录)
if (!StringUtils.hasLength(token)){
log.info("令牌不存在");
final Result responseResult = Result.error("NOT_LOGIN");
//把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)
final String json = JSONObject.toJSONString(responseResult);
response.setContentType("applicaton/json:charset=utf-8");
//响应
response.getWriter().write(json);
return;
}
//5.解析token,若解析失败,返回错误结果(未登录)
try {
JwtUtils.parseJWT(token);
} catch (Exception e) {
log.info("解析失败");
final Result responseResult = Result.error("NOT_LOGIN");
//把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)
final String json = JSONObject.toJSONString(responseResult);
response.setContentType("applicaton/json:charset=utf-8");
//响应
response.getWriter().write(json);
return;
}
//6.放行
filterChain.doFilter(request,response);
}
}
引导类Day10SpringBootCaseApplication
package com.itheima;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
@ServletComponentScan//开启springboot项目对Servlet组件的支持(使用过滤器需要)
@SpringBootApplication
public class Day10SpringBootCaseApplication {
public static void main(String[] args) {
SpringApplication.run(Day10SpringBootCaseApplication.class, args);
System.out.println("http://localhost:90");
}
}
拦截器实现登录功能
自定义拦截器LoginCheckInterceptor
package com.itheima.interceptor;
import com.alibaba.fastjson.JSONObject;
import com.itheima.pojo.Result;
import com.itheima.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**自定义拦截器
* 过滤器和拦截器之间的区别:
* - 接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口。
* - 拦截范围不同:过滤器Filter会拦截所有的资源,而Interceptor只会拦截Spring环境中的资源。
* 故过滤器拦截优先级高于拦截器
*/
@Slf4j
@Component//当前拦截器对象由Spring创建和管理
public class LoginCheckInterceptor implements HandlerInterceptor {
//目标资源方法执行前执行,true:放行,false:不放行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception{
System.out.println("prehandle...");
//1.获取请求url
//2.判断请求url中是否包含login,如果包含,说明是登录操作,放行
//3.获取请求头中的令牌(token)
String token = request.getHeader("token");
log.info("从请求头中获取的令牌:{}",token);
//4.判断令牌是否存在,如果不存在,返回错误结果(未登录)
if(!StringUtils.hasLength(token)){
log.info("Token不存在");
//创建响应结果对象
Result responseResult = Result.error("NOT_LOGIN");
//把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)
String json = JSONObject.toJSONString(responseResult);
//设置响应头(告知浏览器:响应的数据类型为json、响应的数据编码表为utf-8)
response.setContentType("application/json;charset=utf-8");
//响应
response.getWriter().write(json);
return false;//不放行
}
//5.解析token,如果解析失败,返回错误结果(未登录)
try {
JwtUtils.parseJWT(token);
}catch (Exception e){
log.info("令牌解析失败!");
//创建响应结果对象
Result responseResult = Result.error("NOT_LOGIN");
//把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)
String json = JSONObject.toJSONString(responseResult);
//设置响应头
response.setContentType("application/json;charset=utf-8");
//响应
response.getWriter().write(json);
return false;
}
//6.放行
return true;
}
/* //目标资源方法执行后执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView)throws Exception{
System.out.println("posthandle...");
}
//视图渲染完毕后执行,最后执行
public void afterCompletion(HttpServletRequest request,HttpServletResponse response,
Object handler,Exception ex)throws Exception{
System.out.println("afterCompletion...");
}*/
}
注册配置拦截器
package com.itheima.interceptor;
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;
/**
* 注册配置拦截器
* | 拦截路径 | 含义 | 举例 |
* | --------- | -------------------- | --------------------------------------------------- |
* | /* | 一级路径 | 能匹配/depts,/emps,/login,不能匹配 /depts/1 |
* | /** | 任意级路径 | 能匹配/depts,/depts/1,/depts/1/2 |
* | /depts/* | /depts下的一级路径 | 能匹配/depts/1,不能匹配/depts/1/2,/depts |
* | /depts/** | /depts下的任意级路径 | 能匹配/depts,/depts/1,/depts/1/2,不能匹配/emps/1 |
*
*/
@Configuration//定义配置类
public class WebConfig implements WebMvcConfigurer {
//自定义的拦截器对象
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry){
//注册自定义拦截器,设置拦截器拦截的请求路径,/**表示拦截所有请求,
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**")
.excludePathPatterns("/login");//设置不拦截的路径
}
}
全局异常处理器
GlobalExceptionHandler
package com.itheima.excepion;
import com.itheima.pojo.Result;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常处理器
* eg:处理唯一字段重复添加异常
* 处理异常的方法返回值会转换为json后再响应给前端
*/
@RestControllerAdvice//@ControllerAdvice+@ResponseBody表示当前类为全局异常处理类
public class GlobalExceptionHandler {
//处理异常
@ExceptionHandler(Exception.class)//指定能处理的异常类型
public Result ex(Exception e){
e.printStackTrace();//打印堆栈中的异常信息
//捕获到异常之后,响应一个标准的Result
return Result.error("对不起,操作失败,请联系管理员");
}
}