SpringBoot:SpringBoot:实战项目TILAS智能学习辅助系统1.3
登录认证
需求:输入登录请求服务器判断用户的用户名和密码
//控制层 @PostMapping("/login") public Result login(@RequestBody Emp emp); @Override public Result login(Emp emp) { Emp emp1 = empService.selectLogin(emp); if(emp1 == null){ System.out.println("用户名或密码错误"); return Result.error("登录失败"); }else{ System.out.println("登录成功"); Map map = new HashMap(); map.put(emp1.getUsername(),emp1.getPassword()); String secret = JWTUtils.generateJwt(map); return Result.success(secret); } } //业务层 Emp selectLogin(Emp emp); @Override public Emp selectLogin(Emp emp) { return empMapper.selectLogin(emp); } //持久层 @Select("select * from emp where username = #{username} and password = #{password}") Emp selectLogin(Emp emp);
过滤:
如果没有过滤,用户可以直接通过链接直接访问功能,绕过登录.
所以我们需要
将登录成功的信息进行保存和封装记录成为登录成功的标记.
进行判断和拦截
使用
Filter过滤器和Interceptor拦截器
但是因为HTTP是无状态的,不能在多次请求之间共享数据,所以我们需要使用会话跟踪技术解决
会话跟踪技术解决
会话:
用户打开浏览器,访问web服务器的资源,会话建立,直到一方断开连接结束会话.
在一次会话中可以包含多次请求和响应
从浏览器发出请求到服务端,服务端再响应数据给前端,就完成了一次会话的建立
如果建立会话后,浏览器和服务端都没有被关闭,就会持续会话直到一方结束
中途可以一直使用该会话进行请求的发送和响应
会话跟踪:
一种维护浏览器的方法,服务器需要识别多次请求是否来自于同一浏览器,以便于在同一次会话的多次请求间共享数据
服务器会收到多个请求,多个请求可能来自于多个浏览器
所以浏览器需要进行以下操作
使用会话跟踪来进行识别
识别请求是否来自于同一个浏览器
识别浏览器后在同一个会话中多次请求间共享数据
实现会话跟踪
客户端会话跟踪技术:Cookie
服务端会话跟踪技术:Session
都可以实现会话跟踪,但Cookie存储在浏览器端,而Session是存储在服务端
请求时创建唯一id的session保存在服务端,响应时封装成cookie在响应头中返回给客户端
传统会话跟踪的问题(服务器集群,客户端多样化)
服务器集群:
服务器的并发访问量有限,需要通过代理服务器来分配访问给多个服务器(集群中数据无法共享)
问题主要体现在两个方面
服务器集群环境下Session的共享问题
移动端APP端无法使用Cookie
所以我们使用
令牌技术
登录请求时,如果登录成功可以给前端响应一个令牌(一个特殊的字符串,代表每个用户合法的身份凭证)
前端将登录返回的令牌记录下来保存在自己的客户端
在后续的请求中每次请求都会携带该令牌,在之后服务端使用Filter或Interceptor对所有请求进行拦截并校验,获取请求中携带的令牌进行判断,如果合法就放行,如果不合法就返回错误信息并跳转到登录页面.
解决了集群环境下的认证问题,减轻服务器的存储压力
支持PC端,移动端
JWT令牌
JSON Web Token
一个开发的行业标准,定义了一种简介的,自包含的协议格式,用于在通信双方传递JSON对象,传递的信息经过数字签名可以被验证和信任.
分为三个部分
Header(头)
记录令牌类型,签名算法等,使用Base64编码
Payload(有效载荷)
携带用户信息和过期信息,使用Base64编码
Signature(签名)
防止Token被篡改,确保安全性,是一个字符串
使用秘钥加密
校验时的签名秘钥必须与生成令牌时的秘钥一致.
如果JWT令牌解析校验时报错,说明JWT令牌被篡改或失效了,令牌非法.
使用json进行数据传输,通用型广泛,体积小,便于传输
无需在服务器端保存相关信息
jwt载荷部分可以存储业务相关信息(不能是敏感信息)
生成令牌
public class JWTUtils { private static String signKey = "cfjg"; // private static Long expire = 43200000L; public static String generateJwt(Map claims){//生成令牌 String jwt = Jwts.builder().addClaims(claims) //添加数据 .signWith(SignatureAlgorithm.HS256,signKey) //设置算法 .setExpiration(new Date(System.currentTimeMillis() + 1000L*3*60)) //设置过期时间 .compact(); //生成 return jwt; } public static Claims parseJWT(String jwt){ Claims claims = Jwts.parser() .setSigningKey(signKey) //设置秘钥 .parseClaimsJws(jwt) //解析令牌 .getBody(); //获取数据 return claims; } }
过滤器Filter
JavaWeb三大组件之一(Servlet,Filter,Listener)
底层使用动态代理,对请求进行拦截和处理
对token进行判断和解析,如果失败就跳转回login,如果成功就正常访问
public class Filter implements javax.servlet.Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { //请求对象,响应对象,拦截对象 HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest; HttpServletResponse httpServletResponse = (HttpServletResponse)servletResponse; String url = httpServletRequest.getRequestURI(); System.out.println(url); if(url.matches(".?login.?")){ System.out.println("login:登录页面"); filterChain.doFilter(httpServletRequest,httpServletResponse); return; }else{ String token = httpServletRequest.getHeader("token"); if(token == null || token.equals("")){ httpServletResponse.getWriter().write(JSONObject.toJSONString(Result.error("NOT_LOGIN"))); System.out.println("令牌错误"); return; } try{ JWTUtils.parseJWT(token); System.out.println("令牌正确"); filterChain.doFilter(httpServletRequest,httpServletResponse); return; }catch (Exception e){ // filterChain.doFilter(httpServletRequest,httpServletResponse); httpServletResponse.getWriter().write(JSONObject.toJSONString(Result.error("NOT_LOGIN"))); System.out.println("令牌错误"); return; } } } }
Interceptor拦截器
注册拦截器
@Configuration public class InterceptorRegist implements WebMvcConfigurer {//注册拦截器 @Autowired LoginInterceptor loginInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor).addPathPatterns("/**"); } }
使用拦截器
package com.example.tlias.Interceptor; import com.alibaba.fastjson.JSONObject; import com.example.tlias.pojo.Result; import com.example.tlias.util.JWTUtils; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Component public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception {//过滤前 String url = httpServletRequest.getRequestURI(); System.out.println(url); if(url.matches(".?login.?")){ System.out.println("interceptor running"); return true;//放行 }else{ String token = httpServletRequest.getHeader("token"); if(token == null || token.equals("")){ httpServletResponse.getWriter().write(JSONObject.toJSONString(Result.error("NOT_LOGIN"))); System.out.println("interceptor running"); return false;//拦截 } try{ JWTUtils.parseJWT(token); System.out.println("interceptor running"); return true; }catch (Exception e){ // filterChain.doFilter(httpServletRequest,httpServletResponse); httpServletResponse.getWriter().write(JSONObject.toJSONString(Result.error("NOT_LOGIN"))); System.out.println("interceptor running"); return false; } } } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {//过滤后 System.out.println("方法执行结束"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//请求结束 System.out.println("请求结束"); } }
Filter和Interceptor的区别
接口规范的不同:过滤器要实现Filter接口,拦截器需要实现HandlerInterceptor接口
拦截范围不同:过滤器Filter会拦截所有资源,但Interceptor只会拦截Spring环境中的资源
异常处理
在SpringBoot项目中的异常如果从控制层向上抛出会暴露给用户,这是不被允许的.
处理异常的方案
1,在Controller中使用trycatch进行处理(过于冗长)
2,全局异常处理器(推荐)
由SpringMVC提供,接收所有Controller中产生的异常,一般在exception包下定义
@RestControllerAdvice//相当于@ResponseBody+@ControllerAdvice public class GlobalExceptionHandler {//全局异常处理器 @ExceptionHandler(Exception.class)//接收需要处理的异常的字节码文件 public Result exceptionHandler(Exception exception){ return Result.error(exception.getMessage()); } }