ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

第8章 后端Web实战(登录认证)

第8章 后端Web实战(登录认证) 一、登录功能-实现创建pojo实体类LoginInfopublic class LoginInfo { private Integer id; private String username; private String name; private String token; }创建控制层LoginControllerSlf4j RestController public class LoginController { ​ Autowired private EmpService empService; ​ PostMapping(/login) public Result login(RequestBody Emp emp){ log.info(登录:{},emp); LoginInfo info empService.login(emp); if (info ! null) { return Result.success(info); } return Result.error(用户名或密码错误); } }在EmpService业务层中Override public LoginInfo login(Emp emp) { //1.调用数据访问层根据用户名和密码查询员工信息 Emp e empMapper.selectByUsernameAndPassword(emp); ​ //2.判断员工信息是否存在 if (e ! null) { return new LoginInfo(e.getId(), e.getUsername(), e.getName(), ); } return null; }在EmpMapper层中/** * 根据用户名和密码查询员工 * param emp * return */ Select(select id,username,name from emp where username #{username} and password #{password}) Emp selectByUsernameAndPassword(Emp emp);此时可以访问前端登录界面并成功登录问题在未登录情况下我们也可以直接访问部门管理、员工管理等功能。需求只有员工登录成功才可以访问后台系统中的数据。二、登录校验-会话技术-介绍登录标记用户登录成功之后在后续的每一次请求中都可以获取到该标记。【会话技术】统一拦截过滤器Filter、拦截器Interceptor会话用户打开浏览器访问web服务器的资源会话建立直到有一方断开连接会话结束。在一次会话中可以包含多次请求和响应会话跟踪一种维护浏览器状态的方法服务器需要识别 多次请求是否来自于同一浏览器以便在同一次会话的多次请求间共享数据。会话跟踪方案客户端会话跟踪技术Cookie服务端会话跟踪技术Session令牌技术三、登录功能-会话技术1、Cookie优点HTTP协议中支持的技术缺点移动端APP无法使用Cookie不安全用户可以自己禁用Cookie浏览器设置禁用F12-应用-cookie-右键删除浏览器设置-隐私-阻止第三方cookieCookie不能跨域跨域的三个维度不全相同1Cookie案例新建控制层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(); }a、请求Cookie数据Cookie: name value)访问localhost:8080/c1服务器端会创建c1对象并响应给浏览器浏览器会将Cookie的值存储在浏览器中b、响应Cookie数据(Set-Cookie:name value)访问localhost:8080/c2将浏览器的所有的Cookie都会携带到服务器端服务器端接收到了Cookie的值服务器端报错是未找到资源错误NoResourceFoundException未找到网页图标不影响2、SessionSession的底层是基于Cookie的(Set-Cookie,Cookie)登录成功后在服务器端创建Session并通过Cookie响应给浏览器端浏览器端通过Cookie请求服务器端服务器端工具ID找到浏览器所对应的Session优点存储在服务端安全缺点服务器集群环境下无法直接使用Session负载均衡为浏览器分配服务器时每个服务器保存Session不相通浏览器在每次被分配到不同服务器时都要登录Cookie的所有缺点1Session案例//存值 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(HttpSession session){ log.info(HttpSession-s2: {}, session.hashCode()); ​ Object loginUser session.getAttribute(loginUser); //从session中获取数据 log.info(loginUser: {}, loginUser); return Result.success(loginUser); }a、请求Session数据Cookie: JSESSIONID 1)访问localhost:8080/s1服务器端会创建S1对象并响应给浏览器b、响应Session数据Set-Cookie: JSESSIONID 1)访问localhost:8080/s2服务端两个hashCode的值相同代表是同一个会话对象Session3、令牌方案主流方案优点支持PC端、移动端解决集群环境下的认证问题减轻服务器端存储压力缺点需要自己实现三种会话跟踪方案对比四、JWT令牌1、生成与校验全称JSON Web Tokenhttps://jwt.io/)定义了一种简洁的、自包含的格式用于在通信双方以json数据格式安全的传输信息。组成第一部分Header(头记录令牌类型、签名算法等。例如{alg:HS256,type:JWT}第二部分Payload(有效载荷携带一些自定义信息、默认信息等。例如{id:1, username:Tom} 结尾的“”是补全的占位码第三部分Signature(签名防止Token被篡改、确保安全性。将header、payload融入并加入指定秘钥通过指定签名算法计算而来。Base64是一种基于64个可打印字符 A-Z a-z 0-9 / 来表示二进制数据的编码方式。2Java中使用引入依赖!--JWT-- dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency新建测试类JwtTest使用秘钥生成和解析令牌public class JwtTest { /** * 生成JWT令牌 - Jwts.builder() */ Test public void testGenerateJwt(){ MapString, Object dataMap new HashMap(); dataMap.put(id, 1); dataMap.put(username, admin); ​ String jwt Jwts.builder() .signWith(SignatureAlgorithm.HS256, aXRoZWltYQ) //指定加密算法, 秘钥 .addClaims(dataMap) //添加自定义信息 .setExpiration(new Date(System.currentTimeMillis() 60 * 1000)) //设置过期时间 .compact();//生成令牌 System.out.println(jwt); } ​ /** * 解析JWT令牌 */ Test public void testParseJWT(){ String token eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsImV4cCI6MTczMjQzOTc3OH0.6VO9nIGo7UaqzEfQ3F9CT7M1JvSTD1r3Gfp_Lj41a3Q;//生成的JWT令牌 Claims claims Jwts.parser() .setSigningKey(aXRoZWltYQ) //指定秘钥 .parseClaimsJws(token) //解析令牌 .getBody(); //获取自定义信息 System.out.println(claims); } }JWT令牌解析校验时什么情况会报错JWT令牌被篡改或过期失效了注意事项JWT校验时使用的签名秘钥必须和生成JWT令牌时使用的秘钥是配套的2、登录成功后下发令牌在主代码区的utils类中创建JwtUtilspackage 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 final String SECRET_KEY aXRoZWltYQ; // 秘钥 private static final long EXPIRATION_TIME 12 * 60 * 60 * 1000; // 12小时 /** * 生成JWT令牌 * param claims 令牌中包含的信息 * return 生成的JWT令牌字符串 */ public static String generateToken(MapString, Object claims) { return Jwts.builder() .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .addClaims(claims) .setExpiration(new Date(System.currentTimeMillis() EXPIRATION_TIME)) .compact(); } /** * 解析JWT令牌 * param token 要解析的JWT令牌字符串 * return 包含令牌信息的Claims对象 * throws Exception 如果令牌无效或已过期则抛出异常 */ public static Claims parseToken(String token) throws Exception { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody(); } }业务层创建jwt令牌并返回给前端if (e ! null) { log.info(登录成功:{}, e); //生成JWT令牌 MapString,Object claims new HashMap(); claims.put(id, e.getId()); claims.put(username, e.getUsername()); String jwt JwtUtils.generateJwt(claims); return new LoginInfo(e.getId(), e.getUsername(), e.getName(),jwt); }浏览器登录查看传递给前端的JSON前端会将该令牌存储起来每一次请求都会将该令牌携带到服务器端前端在应用程序存储中存储token信息用户登录成功后系统会自动下发JWT令牌然后在后续的每次请求中都需要在请求头header中携带到服务端请求头的名称为token值为登录时下发的JWT令牌。如果检测到用户未登录则直接响应401状态码。用户在发起请求时会携带token值为令牌五、过滤器-Filter1、入门概念Filter过滤器是Javaweb三大组件(Servlet、Filter、Listener)之一。其他两大组件企业开发中不再使用过滤器可以把对资源的请求拦截下来从而实现一些特殊的功能。过滤器一般完成一些通用的操作比如登录校验、统一编码处理、敏感字符处理等。定义Filter定义一个类实现Filter接口并实现其所有方法。在主包下创建filter包DemoFilter类配置FilterFilter类上加WebFilter注解配置拦截路径。启动类上加ServletComponentScan开启Servlet组件支持。大部分初始化方法和销毁方法是不需要写方法内容的注意事项如果过滤器中不执行放行操作过滤器拦截到请求之后就不会访问对应的资源。 放行chain.doFilter(requestresponseWebFilter(urlPatterns /*) Slf4j public class DemoFilter implements Filter { //初始化方法web服务器启动时执行只执行一次 Override public void init(FilterConfig filterConfig) throws ServletException { log.info(DemoFilter init 初始化方法...); } //拦截到请求之后执行过滤方法每次请求都会执行 Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { log.info(DemoFilter doFilter 拦截到请求...); //放行 filterChain.doFilter(servletRequest, servletResponse); } //销毁方法web服务器关闭时执行只执行一次 Override public void destroy() { log.info(DemoFilter destroy 销毁方法...); } }在启动类上加上该注解ServletComponentScan//开启了SpringBoot对Servlet组件的扫描2、令牌校验Filter用户登录成功后系统会自动下发JWT令牌然后在后续的每次请求中都需要在请求头header中携带到服务端请求头的名称为token值为登录时下发的JWT令牌。如果检测到用户未登录则直接响应401状态码。所有的请求拦截到了之后都需要校验令牌吗有一个例外登录请求拦截到请求后什么情况下才可以放行执行业务操作有令牌且令牌校验通过合法否则都返回未登录错误结果过滤器包新建令牌校验过滤器TokenFilterSlf4j WebFilter(urlPatterns /*) public class TokenFilter implements Filter { ​ Override public void init(FilterConfig filterConfig) throws ServletException { Filter.super.init(filterConfig); } ​ Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { //将ServletRequest和ServletResponse强转为HttpServletRequest和HttpServletResponse HttpServletRequest request (HttpServletRequest) servletRequest; HttpServletResponse response (HttpServletResponse) servletResponse; ​ //获取请求路径 String requestURI request.getRequestURI();//获取请求路径 /employee/login ​ //判断是否是登录请求如果路径中包含/login说明是登录操作放行 if (requestURI.contains(/login)) { log.info(放行登录请求); filterChain.doFilter(request, response); return; } ​ //获取请求头中的token String token request.getHeader(token); ​ //判断token是否存在如果不存在说明用户没有登录返回错误信息响应401状态码 if (token null || token.isEmpty()) { log.info(令牌为空响应401状态码); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);//响应401状态码 return; } ​ //如果token存在校验令牌如果校验失败 - 返回错误信息响应401状态码 try { JwtUtils.parseToken(token); } catch (Exception e) { log.info(令牌非法响应401状态码); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);//响应401状态码 return; } //校验通过放行 log.info(令牌合法放行); filterChain.doFilter(request, response); } ​ Override public void destroy() { Filter.super.destroy(); } }当请求头未携带token参数时响应401状态码。携带token时会响应200状态码返回查询结果3、详解1执行流程//拦截到请求之后执行过滤方法每次请求都会执行 Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { log.info(DemoFilter doFilter 拦截到请求...放行前...); //放行 filterChain.doFilter(servletRequest, servletResponse); log.info(DemoFilter doFilter 拦截到请求...放行后...); }问题1放行后访问对应资源资源访问完成后还会回到Filter中吗会问题2如果回到Filter中是重新执行还是执行放行后的逻辑呢执行放行后逻辑2拦截路径WebFilter(urlPatterns /*)3过滤器链介绍一个web应用中可以配置多个过滤器这多个过滤器就形成了一个过滤器链。顺序注解配置的Filter优先级是按照过滤器类名字符串的自然排序。先执行DemoFilter再执行TokenFilter最后执行XyzFilter六、拦截器-Interceptor概念是一种动态拦截方法调用的机制类似于过滤器。Spring框架中提供的主要用来动态拦截控制器方法的执行。作用拦截请求在指定的方法调用前后根据业务需要执行预先设定的代码。1、入门定义拦截器实现HandlerInterceptor接口preHandle、postHandle、afterCompletion在主包下创建interceptor包下创建DemoInterceptor类//由于拦截器是Spring中提供的技术所以要将该类交给IOC容器管理上面加上注解Component Slf4j Component public class DemoInterceptor implements HandlerInterceptor { //在目标资源方法运行之前运行 - 返回值true 放行false 不放行 Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info(preHandle...); return true; } ​ //在目标资源方法运行之后运行 Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, Nullable ModelAndView modelAndView) throws Exception { log.info(postHandle...); } ​ //视图渲染完毕之后运行 Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Nullable Exception ex) throws Exception { log.info(afterCompletion...); } }定义一个配置类实现WebMvcConfigurer接口注册拦截器/**主包下创建config包下创建WebConfigConfiguration //表示当前类是一个配置类 public class WebConfig implements WebMvcConfigurer { ​ Autowired private DemoInterceptor demoInterceptor; ​ Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(demoInterceptor).addPathPatterns(/**);//添加拦截器拦截所有路径/**表示所有路径 } }2、令牌校验Interceptor在interceptor包下创建TokenInterceptor类只需要重写preHandle方法即可内容逻辑根据TokenFilter的doFilter方法修改即可/** * 令牌校验的拦截器 */ Slf4j Component public class TokenInterceptor implements HandlerInterceptor { //在目标资源方法运行之前运行 - 返回值true 放行false 不放行 Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //获取请求路径 String requestURI request.getRequestURI();//获取请求路径 /employee/login ​ //判断是否是登录请求如果路径中包含/login说明是登录操作放行 if (requestURI.contains(/login)) { log.info(放行登录请求); return true; } ​ //获取请求头中的token String token request.getHeader(token); ​ //判断token是否存在如果不存在说明用户没有登录返回错误信息响应401状态码 if (token null || token.isEmpty()) { log.info(令牌为空响应401状态码); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);//响应401状态码 return false; } ​ //如果token存在校验令牌如果校验失败 - 返回错误信息响应401状态码 try { JwtUtils.parseToken(token); } catch (Exception e) { log.info(令牌非法响应401状态码); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);//响应401状态码 return false; } //校验通过放行 log.info(令牌合法放行); return true; } }Webconfig只需注入tokenInterceptor即可Configuration //表示当前类是一个配置类 public class WebConfig implements WebMvcConfigurer { ​ // Autowired // private DemoInterceptor demoInterceptor; ​ Autowired private TokenInterceptor tokenInterceptor; Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(tokenInterceptor).addPathPatterns(/**);//添加拦截器拦截所有路径/**表示所有路径 } }3、详解1排除登录操作的拦截以下的TokenInterceptor的登录操作注释掉//获取请求路径 String requestURI request.getRequestURI();//获取请求路径 /employee/login ​ //判断是否是登录请求如果路径中包含/login说明是登录操作放行 if (requestURI.contains(/login)) { log.info(放行登录请求); return true; }并在WebConfig中添加排除路径Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(tokenInterceptor) .addPathPatterns(/**) .excludePathPatterns(/login);//添加拦截器拦截所有路径/**表示所有路径 }2拦截路径/**与/*的区别3执行流程Filter与Interceptor 区别1.接口规范不同过滤器需要实现Filter接口而拦截器需要实现Handlerlnterceptor接口。2.拦截范围不同过滤器Filter会拦截所有的资源而Interceptor只会拦截Spring环境中的资源。4取舍只需要生效一个TokenFIlter即可将其他的注释掉//WebFilter(urlPatterns /*)还有WebConfig也都注释掉因为只需要一个过滤器即可完成登录校验太多层了会影响性能
返回列表