目录
一、登录功能
(1)Controller层
(2)Service层
(3)Mapper层
二、登录校验
1、会话技术概述
2、会话跟踪方案
(1)Cookie
(2)Session —— 基于Cookie
(3)令牌技术
3、JWT令牌
(1)简介
(2)生成及校验
① 引入JWT令牌依赖
② 生成令牌
③ 解析令牌
(3)登录后下发令牌
① 引入JWT令牌操作工具类
② LoginController 生成令牌
4、Filter过滤器
(1)过滤器快速入门
(2)过滤器执行流程
(3)拦截路径
(4)过滤器链
(5)案例——用Filter实现登录校验
① 手动转换JSON——fastJSON
5、Iterceptor拦截器
(1)拦截器快速入门
① 定义拦截器
② 配置拦截器
(2)拦截器执行流程
(3)拦截路径
(4)过滤器与拦截器的区别
(5)案例——用Interceptor实现登录校验
三、全局异常处理器
一、登录功能
接口信息:
请求参数:
响应数据:
(1)Controller层
@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("用户名或密码错误!");
}
}
(2)Service层
由于是校对员工用户名和密码,所以用empservice层和empmapper层就好
//员工登录
Emp login(Emp emp);
//员工登录
@Override
public Emp login(Emp emp) {
return empmapper.getByUsernameAndPassword(emp);
}
(3)Mapper层
//员工登录
@Select("select * from emp where username = #{username} and password = #{password}")
Emp getByUsernameAndPassword(Emp emp);
二、登录校验
1、会话技术概述
- 会话:一次会话中包含多次请求和响应
- 会话跟踪:服务器需要识别多次请求是否来自同一浏览器,以便在同一次会话的多次请求中共享数据
2、会话跟踪方案
(1)Cookie
- 优点:HTTP协议支持的技术
- 缺点:
- 移动端APP不支持
- 不安全,用户可以禁用Cookie
- Cookie不可以跨域
(2)Session —— 基于Cookie
- 优点:存储在服务端,安全
- 缺点:
- 服务器集群环境下无法直接使用Session
- Cookie的缺点
(3)令牌技术
- 优点:
- 支持PC端,移动端
- 解决集群环境下的认证问题
- 减轻服务器端存储压力
- 缺点:
- 需要自己实现
3、JWT令牌
(1)简介
- 概念:JWT就是将json格式的数据以安全的形式封装
- 组成:
- 第一部分——头:记录令牌类型、签名算法等。【eg:"alg":"HS256","type":"Tom"】
- 第二部分——有效载荷:携带一些自定义信息、默认信息。【eg:"id":"1"】
- 第三部分——签名:防止Token被篡改,保证安全性。将头、有效载荷并加入指定密钥,提高指定签名算法计算得来。
(2)生成及校验
① 引入JWT令牌依赖
<!--JWT令牌-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
② 生成令牌
// 测试jwt令牌生成
@Test
public void testGenJwt()
{
Map<String,Object> claims = new HashMap<>();
claims.put("id",1);
claims.put("name","Tom");
String jwt = Jwts.builder()
.signWith(SignatureAlgorithm.HS256,"itheima") //签名算法
.setClaims(claims) //自定义内容
.setExpiration(new Date(System.currentTimeMillis()+3600*1000)) //设置有效期为1h
.compact();
System.out.println(jwt);
}
生成令牌
eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiVG9tIiwiaWQiOjEsImV4cCI6MTY5ODA2OTAxNX0.hjkFgUORQs1DOnaZa4A2n_5jDVLxkLO89i3Ri5D7LqA
在官网可以查到令牌的原数据
JSON Web Tokens - jwt.io
③ 解析令牌
//解析jwt令牌
@Test
public void testParseJwt()
{
Claims claims = Jwts.parser()
.setSigningKey("itheima") //密钥
.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiVG9tIiwiaWQiOjEsImV4cCI6MTY5ODA2OTAxNX0.hjkFgUORQs1DOnaZa4A2n_5jDVLxkLO89i3Ri5D7LqA")
.getBody();
System.out.println(claims);
}
(3)登录后下发令牌
① 引入JWT令牌操作工具类
package com.itroye.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 = 43200000L; //规定过期时间为12h
/**
* 生成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;
}
}
② LoginController 生成令牌
@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<String,Object> claims = new HashMap<>();
claims.put("id",e.getId());
claims.put("name",e.getName());
claims.put("username",e.getUsername());
String jwt = JwtUtils.generateJwt(claims); //jwt令牌包含当前登录的员工信息
return Result.success(jwt);
}
//登录失败,返回错误信息
return Result.error("用户名或密码错误!");
}
}
4、Filter过滤器
(1)过滤器快速入门
step 1:新建filter包+类
step 2:过滤器代码编写
注意是:选javax.servlet这个!
选中接口名并alt+enter自动生成接口中的三个方法
@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("拦截到了请求!"); filterChain.doFilter(servletRequest,servletResponse); // 放行 } @Override //销毁的方法 只使用一次 public void destroy() { System.out.println("销毁方法执行了"); } }
其中注解@WebFilter(urlPatterns = "/*") 是拦截路径,而要使用它,需要在启动类上添加
@ServletComponentScan //开启对Servlet组件的支持
(2)过滤器执行流程
浏览器发出请求——过滤器执行放行前的逻辑——放行——login、depts(访问web资源)——放行之后的逻辑——响应给浏览器
(3)拦截路径
拦截路径 url值 含义 拦截具体路径 /login 只有访问/login路径,才会被拦截 目录拦截 /emps/* 访问/emps下所有资源时,都会被拦截 拦截所有 /* 访问所有资源都会被拦截
(4)过滤器链
可以设置多个过滤器,形成过滤器链
- 过滤器的执行先后顺序是根据过滤器类名(字符串)的自然排序,也就是说AbcFilter在DemoFilter过滤器之前
- 第一个过滤器放行后进入第二个过滤器
(5)案例——用Filter实现登录校验
- 登录成功后,生成一个jwt令牌并返回给前端,前端将该jwt令牌记录(在请求头token中携带令牌),并在后续每一次请求中携带到服务端
- 过滤器校验jwt令牌的有效性,进行拦截放行操作
① 手动转换JSON——fastJSON
注意:之前在Controller里直接返回Result.success(),因为在注释@RestController下,会自动将返回值转换为json格式,而在过滤器类中,没有该注解,因此需要手动转换成json格式
这里我们用到阿里巴巴提供的fastJSON
首先在pom.xml文件中引入fastJSON依赖
<!--fastJSON--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.76</version> </dependency>
@Slf4j
@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse resp = (HttpServletResponse) servletResponse;
//1、获取请求url
String url = req.getRequestURL().toString();
log.info("请求url为:{}",url);
//2、判断url是否包含login,如果包含则放行
if(url.contains("login"))
{
log.info("登录操作,放行");
filterChain.doFilter(servletRequest,servletResponse);
return;
}
//3、获取请求头中的令牌(token)
String jwt = req.getHeader("token");
//4、判断令牌是否有效
if(!StringUtils.hasLength(jwt))
{
log.info("请求头为空,返回为登录信息");
Result error = Result.error("NOT_LOGIN");
// 手动转换成JSON --阿里巴巴fastJSON
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
//上面的流程:将错误信息封装在Result中-->转换为json格式-->response的write()方法将错误信息返回给浏览器
return;
}
//5、解析token,如果解析失败,返回错误结果(未登录)
try{
JwtUtils.parseJWT(jwt); //解析报错 说明令牌错误
}catch (Exception e)
{
e.printStackTrace();
log.info("解析令牌失败,返回错误信息");
// 返回错误信息丝滑小连招
Result error = Result.error("NOT_LOGIN");
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return;
}
//6、放行
log.info("令牌合法,放行!");
filterChain.doFilter(servletRequest,servletResponse);
}
}
此时,当我们在网页访问depts、emps时,如果没有登录,页面会强制跳转到登录页面,只有合法登录,获取到合法令牌,才能访问后台
5、Iterceptor拦截器
(1)拦截器快速入门
step 1:新建interceptor包+类
step 2:拦截器代码编写
ctrl+o调出面板,选中三个默认方法
① 定义拦截器
@Component //交给ioc容器管理 public class LoginCheckInterceptor implements HandlerInterceptor { @Override // 目标资源方法运行【前】运行 true:放行 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运行了"); } }
② 配置拦截器
- addPathPatterns("/**") ———— 拦截
- excludePathPatterns("/login") ———— 不拦截
首先连包带类创建一个配置类
@Configuration //配置类 public class WebConfig implements WebMvcConfigurer { @Autowired private LoginCheckInterceptor loginCheckInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**"); } }
(2)拦截器执行流程
先执行过滤器,然后再执行拦截器
(3)拦截路径
拦截路径 含义 举例 /* 一级路径 能匹配/depts、/emps、/login,不能匹配/depts/1 /** 任意路径 都可以 /depts/* /depts下的一级路径 可以匹配/depts/1,不能匹配/depts/1/2
/depts/** /depts下的任意路径 不能匹配/emps/1
(4)过滤器与拦截器的区别
- 过滤器会拦截所有资源
- 拦截器只会拦截spring环境的资源
(5)案例——用Interceptor实现登录校验
@Slf4j
@Component //交给ioc容器管理
public class LoginCheckInterceptor implements HandlerInterceptor {
@Override // 目标资源方法运行【前】运行 true:放行 false:拦截
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
System.out.println("preHandle运行了");
//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("请求头为空,返回为登录信息");
Result error = Result.error("NOT_LOGIN");
// 手动转换成JSON --阿里巴巴fastJSON
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
//上面的流程:将错误信息封装在Result中-->转换为json格式-->response的write()方法将错误信息返回给浏览器
return false;
}
//5、解析token,如果解析失败,返回错误结果(未登录)
try{
JwtUtils.parseJWT(jwt); //解析报错 说明令牌错误
}catch (Exception e)
{
e.printStackTrace();
log.info("解析令牌失败,返回错误信息");
// 返回错误信息丝滑小连招
Result error = Result.error("NOT_LOGIN");
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运行了");
}
}
三、全局异常处理器
step1:连包带类创建异常类
step2:编写代码
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) //定义捕获所有类型的异常 public Result ex(Exception ex) { ex.printStackTrace(); return Result.error("对不起,操作失败,请联系管理员"); } }