JavaWeb(后端实战)
登录功能需求在登录界面中输入用户的用户名以及密码点击 登录 按钮请求服务器服务端判断用户输入的用户名或者密码是否正确如果正确则返回成功结果前端跳转至系统首页面代码实现创建实体类 LoginInfo封装登录成功后返回给前端的数据//登录成功结果封装类 Data NoArgsConstructor AllArgsConstructor public class LoginInfo{ private Integer id;//员工ID private String username;//用户名 private String name;//姓名 private String token;//令牌 }LoginController: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(用户名或密码错误); } }EmpService:public interface EmpService{ /* ... */ //登录 LoginInfo login(Emp emp); }EmpServiceImpl://员工管理 Service public class EmpServiceImpl implements EmpService{ Autowired private EmpMapper empMapper; /* ... */ //登录 Override public LoginInfo login(Emp emp){ Emp empLogin empMapper.getUsernameAndPassword(emp); if(empLogin!null){ LoginInfo loginInfo new LoginInfo(empLogin.getId(),empLogin.getUsername(),empLogin.getName(),null); return loginInfo; } return null; } }EmpMapper:Mapper public interface EmpMapper{ //根据用户名和密码查询员工信息 Select(select * from emp where username #{username} and password #{password}) Emp getUsernameAndPassword(Emp emp); }测试前后端联调测试:存在问题当在浏览器新页面输入地址http://localhost:90 时未登录却仍然可以进入到后端管理系统页面此时就需要登录效验的实现解决该问题登录效验登录效验指的是在服务器端接收到浏览器发送过来的请求之后要对请求进行校验校验用户是否登录用户已经登录则直接执行对应的业务操作若用户没有登录就不允许执行相关的业务操作给前端响应一个错误的结果最终跳转到登录页面要求登录成功之后再来访问对应的数据HTTP 协议是无状态协议所谓无状态指的是每一次请求都是独立的下一次请求并不会携带上一次请求的数据而浏览器与服务器之间进行交互基于 HTTP 协议在通过浏览器来访问登陆这个接口实现了登陆的操作再执行其他业务操作时服务器并不知道该员工是否已登陆因为两次请求之间是独立的实现登录效验在员工登录成功后需要将用户登录成功的信息存起来记录用户已经登录成功的标记在浏览器发起请求时需要在服务端进行统一拦截拦截后进行登录校验涉及到 Web 开发中的两个技术会话技术用户登录成功之后在后续的每一次请求中都可以获取到该标记统一拦截技术过滤器 Filter、拦截器 Interceptor会话技术会话指的就是浏览器与服务器之间的一次连接在用户打开浏览器第一次访问服务器的时候这个会话就建立了直到有任何一方断开连接会话才结束了在一次会话当中可以包含多次请求和响应会话跟踪一种维护浏览器状态的方法服务器需要识别多次请求是否来自于同一浏览器以便在同一次会话的多次请求间共享数据使用会话跟踪技术就是要完成在同一个会话中多个请求之间进行共享数据会话跟踪方案Cookie(客户端会话跟踪技术)数据存储在客户端浏览器Session(服务端会话跟踪技术)数据存储在储在服务端令牌技术会话跟踪方案Cookie:Cookie 是基于 HTTP 协议的客户端会话跟踪技术客户端首次请求如登录后服务器通过响应头 Set-Cookie 携带用户数据如用户名、ID浏览器接收后会自动存储该 Cookie 到本地后续客户端每次请求时浏览器会通过请求头 Cook ie 自动将本地 Cookie 携带至服务器服务器通过判断请求中 Cookie 是否存在可识别客户端是否已登录实现同一会话中不同请求的数据共享优缺点优点HTTP 协议中支持的技术像 Set-Cookie 响应头的解析以及 Cookie 请求头数据的携带都是浏览器自动进行的无需手动操作缺点移动端 APP Android、IOS中无法使用 Cookie不安全用户可以自己禁用 CookieCookie 不能跨域跨域现在项目大部分都是前后端分离的前后端最终也会分开部署前端部署在服务器 192.168.150.200 上端口 80后端部署在 192.168.150.100 上端口 8080打开浏览器直接访问前端工程访问 urlhttp://192.168.150.200/login.html在该页面发起请求到服务端而服务端所在地址不再是 localhost而是服务器的 IP 地址 192.168.150.100假设访问接口地址为http://192.168.150.100:8080/login此时就存在跨域操作因为是在 http://192.168.150.200/login.html 这个页面上访问了http://192.168.150.100:8080/login 接口此时如果服务器设置了一个 Cookie这个 Cookie 是不能使用的因为 Cookie 无法跨域区分跨域的维度三个维度有任何一个维度不同就是跨域操作协议、IP / 协议、端口举例http://192.168.150.200/login.html ---------- https://192.168.150.200/login [协议不同跨域]http://192.168.150.200/login.html ---------- http://192.168.150.100/login [IP不同跨域]http://192.168.150.200/login.html ---------- http://192.168.150.200:8080/login [端口不同跨域]http://192.168.150.200/login.html ---------- http://192.168.150.200/login [不跨域]Sessioin:Session 是基于 Cookie 实现的服务器端会话跟踪技术客户端首次请求时服务器尝试获取 Session若不存在则自动创建每个 Session 对应唯一 IDSession ID服务器通过Set-Cookie 响应头将 Session ID 以名为JSESSIONID的 Cookie 形式响应给浏览器浏览器自动存储该 Cookie客户端后续请求时会自动携带JSESSIONIDCookie 到服务器服务器通过该 ID 从本地找到对应的 Session 对象从而实现同一会话中不同请求的数据共享优缺点优点Session 是存储在服务端的安全缺点服务器集群环境下无法直接使用 Session移动端 APPAndroid、IOS中无法使用 Cookie用户可以自己禁用 CookieCookie 不能跨域令牌技术令牌是用户身份标识的字符串是当前企业最常用的会话跟踪技术客户端发起登录请求登录成功后服务器生成令牌用户合法凭证并响应给前端前端接收令牌后可存储在 Cookie 或 localStorage 等空间后续请求中前端自动携带令牌到服务器服务器校验令牌有效性判断用户是否已登录如需共享数据可直接存储在令牌中优缺点:优点支持PC端、移动端解决集群环境下的认证问题减轻服务器的存储压力无需在服务器端存储缺点需要自己实现包括令牌的生成、令牌的传递、令牌的校验JWT 令牌最典型用于登录认证客户端登录成功后服务器生成 JWT 令牌并返回给前端前端存储 JWT后续每次请求均携带该令牌服务器统一拦截所有请求先判断是否携带令牌无令牌则拒绝访问有令牌则校验有效性有效则放行处理请求JWT 令牌基本概述JWT 全称 JSON Web Token 官网https://jwt.io/ 定义了一种简洁的、自包含的格式用于在通信双方以 JSON 数据格式安全的传输信息由于数字签名的存在这些信息是可靠的JWT 令牌由三个部分组成三个部分之间使用 . 来分割第一部分Header头 记录令牌类型、签名算法等例如{alg:HS256type:JWT}第二部分Payload有效载荷携带一些自定义信息、默认信息等例如{id:1userna me:Zhu}第三部分Signature签名防止 Token 被篡改、确保安全性将 header、payload并加入指定秘钥通过指定签名算法计算而来JWT 通过 Base64 编码将原始 JSON 数据转为字符串Base64 用 64 个可打印字符A-Z、a-z、0-9、、/表示二进制数据原始 JSON 字符串先被转换为二进制如 UTF-8 编码的字节流再按固定规则映射到这 64 个字符形成编码后的字符串若二进制数据长度不是 3 的倍数会用补位如长度为 4 时补 2 个Base64 是可逆的编码方式可通过解码还原原始 JSON仅用于将二进制数据转为字符串格式不具备加密安全性生成和效验使用 JWT 令牌需要先引入 JWT 的依赖!-- JWT依赖-- dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency生成 JWT 令牌public class JwtTest{ //生成JWT令牌 - Jwts.builder() Test public void testGenJwt(){ MapString, Object claims new HashMap(); claims.put(id, 1); claims.put(username, Zhu); String jwt Jwts.builder().signWith(SignatureAlgorithm.HS256, Wmh1)//指定加密算法秘钥 .addClaims(claims) .setExpiration(new Date(System.currentTimeMillis() 12 * 3600 * 1000))//设置过期时间 .compact();//生成令牌 System.out.println(jwt); } }运行测试方法eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJaaHUiLCJleHAiOjE3NjE0NDQyMjl9.TkjYh_BJ7yq1YTSuhagYdDezgmHm4dCBpYmNvZO2k60输出的结果就是生成的 JWT 令牌通过英文的点分割对三个部分进行分割可以将生成的令牌复制到 JWT 的官网将生成的令牌放在 Encoded 位置此时就会自动的将令牌解析出解析 JWT//解析JWT令牌 - Jwts.parser() Test public void testParseJwt(){ Claims claims Jwts.parser().setSigningKey(Wmh1) .parseClaimsJws(eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJaaHUiLCJleHAiOjE3NjE0NDQyMjl9.TkjYh_BJ7yq1YTSuhagYdDezgmHm4dCBpYmNvZO2k60) .getBody(); System.out.println(claims); }运行测试方法{id1, usernameZhu, exp1761444229}注意篡改令牌中的任何一个字符在对令牌进行解析时都会报错JWT令牌过期后令牌就失效了解析的为非法令牌登录时生成创建 org.example.util 包创建 JwtUtils 类public class JwtUtils{ private static String signKey Wmh1;//秘钥 private static Long expire 43200000L; //生成JWT令牌 public static String generateJwt(MapString,Object claims){ String jwt Jwts.builder() .addClaims(claims) .signWith(SignatureAlgorithm.HS256, signKey) .setExpiration(new Date(System.currentTimeMillis() expire)) .compact(); return jwt; } //解析JWT令牌 public static Claims parseJWT(String jwt){ Claims claims Jwts.parser() .setSigningKey(signKey) .parseClaimsJws(jwt) .getBody(); return claims; } }EmpServiceImpl://员工管理 Service public class EmpServiceImpl implements EmpService{ Autowired private EmpMapper empMapper; /* ... */ //登录 Override public LoginInfo login(Emp emp){ Emp empLogin empMapper.getUsernameAndPassword(emp); if(empLogin ! null){ //生成JWT令牌 MapString,Object dataMap new HashMap(); dataMap.put(id, empLogin.getId()); dataMap.put(username, empLogin.getUsername()); String jwt JwtUtils.generateJwt(dataMap); LoginInfo loginInfo new LoginInfo(empLogin.getId(), empLogin.getUsername(), empLogin.getName(), jwt); return loginInfo; } return null; } }测试前后端联调测试JWT令牌已经响应给了前端此时就会将JWT令牌存储在浏览器本地后续的每一次请求都会将这个令牌携带到服务端而服务端需要统一拦截所有的请求从而判断是否携带的有合法的 JWT 令牌可以通过 Filter 过滤器Interceptorf 拦截器过滤器 Filter:Filter 表示过滤器是 JavaWeb 三大组件Servlet、Filter、Listener之一过滤器可以把对资源的请求拦截下来一般完成一些通用的操作比如登录校验、统一编码处理、敏感字符处理等使用过滤器之后要想访问服务器上的资源必须先经过滤器过滤器处理完毕之后才可以访问对应的资源快速入门定义过滤器创建 org.example.filter 包创建 DemoFilter 类实现 Filter 接口public class DemoFilter implements Filter{ //初始化方法, 服务器启动, 创建Filter实例时自动调用, 只调用一次 public void init(FilterConfig filterConfig) throws ServletException{ System.out.println(init ...); } //拦截到请求时,调用该方法,可以调用多次 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException{ System.out.println(拦截到了请求...); } //销毁方法, 服务器关闭时自动调用, 只调用一次 public void destroy(){ System.out.println(destroy ... ); } }配置过滤器在 Filter 类上添加注解WebFilter并指定属性urlPatterns通过这个属性指定过滤器要拦截哪些请求WebFilter(urlPatterns /*)//配置过滤器要拦截的请求路径( /* 表示拦截浏览器的所有请求 ) public class DemoFilter implements Filter{ //初始化方法, 服务器启动, 创建Filter实例时自动调用, 只调用一次 public void init(FilterConfig filterConfig) throws ServletException{ System.out.println(init ...); } //拦截到请求时,调用该方法,可以调用多次 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException{ System.out.println(拦截到了请求...); } //销毁方法, 服务器关闭时自动调用, 只调用一次 public void destroy(){ System.out.println(destroy ... ); } }还需要在启动类上面加上注解ServletComponentScan通过这个注解来开启 SpringBoot 项目对于 Servlet 组件的支持ServletComponentScan//开启对Servlet组件的支持 SpringBootApplication public class TliasManagementApplication{ public static void main(String[] args){ SpringApplication.run(TliasManagementApplication.class, args); } }启动服务并打开浏览器可以看到控制台的输出注意在过滤器 Filter 中如果不执行放行操作将无法访问资源放行操作chain.doFilter(request, response);登录效验过滤器思路分析Filter 过滤器的流程步骤代码实现在 org.example.filter 包下创建 TokenFilter 类//令牌校验过滤器 Slf4j WebFilter(urlPatterns /*) public class TokenFilter implements Filter{ Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req;//转换为HTTP请求对象 HttpServletResponse response (HttpServletResponse) resp;//转换为HTTP响应对象 //获取请求url String url request.getRequestURL().toString(); //判断是否是登录请求 if(url.contains(login)){//登录请求 log.info(登录请求 , 放行); chain.doFilter(request, response);//放行 return; } //获取请求头中的令牌(token) String jwt request.getHeader(token); //判断令牌是否存在 if(!StringUtils.hasLength(jwt)){//jwt为空 log.info(获取到jwt令牌为空, 返回错误结果); response.setStatus(HttpStatus.SC_UNAUTHORIZED);//设置响应状态码为401 return; } //解析token try{ JwtUtils.parseJWT(jwt);//调用工具类解析令牌 }catch(Exception e){//解析失败 e.printStackTrace(); log.info(解析令牌失败, 返回错误结果); response.setStatus(HttpStatus.SC_UNAUTHORIZED);//设置响应状态码为401 return; } //放行 log.info(令牌合法, 放行); chain.doFilter(request , response);//允许请求继续访问目标接口 } }Filter 详解执行流程拦截路径拦截路径urlPatterns 值含义拦截具体路径/login只有访问 /login 路径时才会被拦截目录拦截/emps/*访问 /emps 下的所有资源都会被拦截拦截所有/*访问所有资源都会被拦截过滤器链过滤器链上过滤器的执行顺序注解配置的 Filter优先级是按照过滤器类名(字符串)的自然排序拦截器 Interceptor:拦截器是 Spring 框架中提供的用来动态拦截控制器方法的执行是一种动态拦截方法调用的机制类似于过滤器作用拦截请求在指定方法调用前后根据业务需要执行预先设定的代码快速入门自定义拦截器创建 org.example.interceptor 包创建 DemoInterceptor 类实现 HandlerInterceptor 接口并重写其所有方法//自定义拦截器 Component//标记为 Spring 组件,使其能被 Spring 容器扫描并管理 public class DemoInterceptor implements HandlerInterceptor{ //目标资源方法执行前执行 Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //handler: 被拦截的目标处理器(通常是 Controller 中的方法) System.out.println(preHandle .... ); return true;//true 表示放行 false 不放行 } //目标资源方法执行后执行 Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { //modelAndView: 包含目标方法返回的模型数据(Model)和视图信息(View) System.out.println(postHandle ... ); } //视图渲染完毕后执行最后执行 Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //ex: 目标方法或视图渲染过程中抛出的异常(若有) System.out.println(afterCompletion .... ); } }注册配置拦截器创建 org.example.config 包创建 WebConfig 配置类实现 WebMvcConfigurer 接口并重写 addInterceprors 方法Configuration//标记该类为 Spring 的配置类 public class WebConfig implements WebMvcConfigurer{ //注入自定义的拦截器对象 Autowired private DemoInterceptor demoInterceptor; Override public void addInterceptors(InterceptorRegistry registry){//Spring MVC 提供的拦截器注册器 //注册自定义拦截器对象,并设置拦截器拦截的请求路径 registry.addInterceptor(demoInterceptor).addPathPatterns(/**);// /** 表示拦截所有请求 } }测试若将拦截器中返回值改为 false则没有响应数据说明被拦截了没有放行登录效验拦截器代码实现在 org.example.interceptor 包下创建 TokenInterceptor 类Slf4j Component public class TokenInterceptor implements HandlerInterceptor{ Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{ //获取请求url String url request.getRequestURL().toString(); //判断是否是登录请求 if(url.contains(login)){ //登录请求 log.info(登录请求, 放行); return true; } //获取请求头中的令牌(token) String jwt request.getHeader(token); //判断令牌是否存在 if(!StringUtils.hasLength(jwt)){ //jwt为空 log.info(获取到jwt令牌为空, 返回错误结果); response.setStatus(HttpStatus.SC_UNAUTHORIZED);//设置响应状态码为401 return false; } //解析token try { JwtUtils.parseJWT(jwt); } catch (Exception e) { e.printStackTrace(); log.info(解析令牌失败, 返回错误结果); response.setStatus(HttpStatus.SC_UNAUTHORIZED);//设置响应状态码为401 return false; } //放行 log.info(令牌合法, 放行); return true; } }配置拦截器Configuration//标记该类为 Spring 的配置类 public class WebConfig implements WebMvcConfigurer{ //注入自定义的拦截器对象 Autowired private TokenInterceptor tokenInterceptor; Override public void addInterceptors(InterceptorRegistry registry){//Spring MVC 提供的拦截器注册器 //注册自定义拦截器对象,并设置拦截器拦截的请求路径 registry.addInterceptor(tokenInterceptor).addPathPatterns(/**);// /** 表示拦截所有请求 } }Interceptor 详解拦截路径拦截路径的配置通过 addPathPatterns(要拦截路径) 方法可以指定要拦截哪些资源调用 excludePathPatterns (不拦截路径) 方法指定哪些资源不需要拦截Configuration public class WebConfig implements WebMvcConfigurer { //拦截器对象 Autowired private DemoInterceptor demoInterceptor; Override public void addInterceptors(InterceptorRegistry registry){ //注册自定义拦截器对象 registry.addInterceptor(demoInterceptor) .addPathPatterns(/**)//设置拦截器拦截的请求路径(/** 表示拦截所有请求) .excludePathPatterns(/login);//设置不拦截的请求路径 } }在拦截器中除了可以设置/**拦截所有资源外还有一些常见拦截路径设置:拦截路径含义举例/*一级路径能匹配 /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执行流程浏览器发起请求请求先被自定义过滤器拦截执行 放行前逻辑 后放行请求进入 Spring 环境放行后的请求首先到达 Spring Web 核心的DispatcherServlet (前端控制器)它是 Tomcat 能识别的 Servlet负责请求的分发拦截器预处理DispatcherServlet将请求转发给 Controller 前会触发自定义拦截器的preHandle()方法返回true放行请求继续流向 Controller 的接口方法返回false拦截请求终止不执行 Controller 方法执行 Controller 方法请求到达目标 Controller执行对应的接口业务逻辑拦截器后处理Controller 方法执行完成后依次触发拦截器的postHandle()(视图渲染前)和afterCompletion()视图渲染后方法返回与过滤器收尾拦截器后处理完成请求返回给DispatcherServlet再执行过滤器 放行后逻辑最终将响应数据返回给浏览器过滤器和拦截器的主要区别接口规范不同过滤器需要实现 Filter 接口而拦截器需要实现 HandlerInterceptor 接口拦截范围不同过滤器 Filter 会拦截所有的资源而 Interceptor 只会拦截 Spring 环境中的资源
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2425435.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!