Author:Dawn_T17🥥
目录
登录功能
基础登录
登录校验
一、会话技术
1.基于 Cookie 和 Session 的传统会话技术(传统)
2.基于 Token 的会话技术(如 JWT)(主流)
JWT
二、过滤器(Filter)
具体代码展现
三、拦截器(Interceptor)
在 Spring Web 后端开发中,登录认证是确保系统安全性的重要环节。以下是关于 Spring Web 后端开发中登录认证的介绍:
一、为什么需要登录认证
-
保护系统资源
- 防止未经授权的用户访问敏感信息和功能。只有通过合法认证的用户才能访问特定的页面、数据或执行特定的操作。
- 确保系统数据的保密性、完整性和可用性。
-
确保用户身份真实性
- 验证用户的身份,确保与系统交互的是合法的用户,而不是恶意攻击者或冒充者。
- 为用户提供个性化的服务和体验,根据用户的身份和权限展示不同的内容和功能。
二、常见的登录认证方式
-
基于用户名和密码的认证
- 这是最常见的认证方式。用户在登录页面输入用户名和密码,后端系统对其进行验证。
- 通常会对密码进行加密存储,以防止密码泄露。在验证时,将用户输入的密码加密后与存储的密码进行比较。
- 可以结合盐值(salt)来增加密码的安全性,防止彩虹表攻击。
-
基于令牌(Token)的认证
- 当用户成功登录后,后端系统生成一个令牌并返回给客户端。客户端在后续的请求中携带这个令牌,后端系统通过验证令牌来确定用户的身份。
- 令牌可以是 JWT(JSON Web Token)等格式,具有自包含性和可验证性。
- 令牌可以设置有效期,过期后需要用户重新登录获取新的令牌。
-
单点登录(SSO)
- 允许用户在一个系统中登录后,无需在其他相关系统中再次登录。通过共享用户认证信息,实现一次登录,多处访问。
- 常见的实现方式有 CAS(Central Authentication Service)等。
三、Spring Security 实现登录认证
Spring Security 是一个强大的安全框架,用于在 Spring 应用中实现安全功能,包括登录认证。
-
配置用户存储
- 可以使用内存存储、数据库存储或 LDAP 等方式存储用户信息。
- 定义用户的用户名、密码、角色等信息。
-
配置认证方式
- 选择合适的认证方式,如基于表单的认证、基于 HTTP Basic 认证等。
- 配置登录页面、登录处理 URL、错误页面等。
-
配置授权规则
- 根据用户的角色或权限,配置不同的访问规则。
- 可以限制某些 URL 只能被特定角色的用户访问。
-
集成其他安全功能
- 可以与其他安全技术集成,如加密、数字签名、OAuth2 等。
四、登录认证的流程
-
用户提交登录请求
- 用户在客户端输入用户名和密码,然后将登录请求发送到后端服务器。
-
后端验证用户信息
- 后端系统接收到登录请求后,根据配置的用户存储和认证方式,验证用户的用户名和密码是否正确。
- 如果验证通过,生成令牌或设置会话信息,标识用户已登录。
-
返回认证结果
- 如果登录成功,后端系统返回相应的认证结果,如令牌、会话 ID 等。客户端可以将这些信息存储在本地,以便在后续的请求中使用。
- 如果登录失败,返回错误信息,提示用户重新输入用户名和密码。
-
后续请求的认证
- 在用户登录成功后,客户端在后续的请求中携带令牌或会话信息。
- 后端系统接收到请求后,验证令牌或会话信息的有效性,确定用户的身份和权限。
- 根据用户的权限,允许或拒绝用户对特定资源的访问。
总之,在 Spring Web 后端开发中,登录认证是确保系统安全的重要环节。通过选择合适的认证方式,结合 Spring Security 等安全框架,可以实现高效、安全的登录认证功能。
登录功能
基础登录
基础分析
只要根据用户名和密码查询数据库,查询出结果不为空,即为登录成功 。
基本设计流程
因为用户名和密码每个Emp(员工)都有,所以不用创建新的对象存数据,直接用Emp存 传入进来的用户名和密码数据
但是因为请求路径不同,所以Controller要重写
Controller
package com.traingcase.controller;
import com.traingcase.pojo.Emp;
import com.traingcase.pojo.Result;
import com.traingcase.service.EmpService;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
public class LoginController {
@Autowired
private EmpService empService;
@PostMapping("/login")
//因为用户名和密码每个Emp(员工)都有
// 所以不用创建新的对象存数据,直接用Emp存
public Result login(@RequestBody Emp emp){
log.info("员工登录:{}",emp);
Emp e=empService.login(emp);
return e != null? Result.success():Result.error("用户名或密码错误");
}
}
Service
@Override
public Emp login(Emp emp) {
Emp e=empMapper.getByUsernameAndPassword(emp);
return e;
}
Mapper
/**
* 根据用户名和密码查询员工
* @param emp
* @return
*/
@Select("select * from emp where username=#{username} and password= #{password}")
Emp getByUsernameAndPassword(Emp emp);
ApiPost测试
登录校验
经过前后端联调测试 发现Bug ——>退出登录,关闭网页,重新加载网页,直接进入系统,不用登录。
原因:目前设置的每个请求路径都是独立的,登录操作只是空有其表
因为前后端交互基于HTTP协议,而HTTP协议是无状态的。
解决方法
一、会话技术
会话技术是在 Web 应用中用于跟踪用户状态和维持用户与服务器之间交互的技术手段。
在 Web 应用中,由于 HTTP 协议是无状态的,即服务器无法区分不同的请求是否来自同一个用户。为了解决这个问题,会话技术应运而生。
会话技术的作用
-
跟踪用户状态
- 通过会话技术,服务器可以识别不同请求来自同一个用户,并记录用户的状态信息,如登录状态、购物车内容、浏览历史等。
- 这样,用户在不同页面之间切换时,服务器能够根据会话信息为用户提供个性化的服务和体验。
-
维持用户与服务器之间的交互
- 会话技术使得用户与服务器之间的交互可以跨越多个请求。例如,用户在一个页面上提交表单,服务器处理后返回结果,用户可以在另一个页面上继续操作,而服务器能够根据会话信息保持对用户操作的连贯性。
目前主流的会话技术主要有以下几种:
1.基于 Cookie 和 Session 的传统会话技术(传统)
-
工作原理:
- 当用户首次访问服务器时,服务器为该用户创建一个唯一的 Session ID,并通过在响应中设置 Cookie 的方式将 Session ID 返回给客户端。客户端在后续的请求中会自动携带这个 Cookie,服务器根据 Cookie 中的 Session ID 找到对应的 Session 对象,从而获取用户的状态信息。
- Session 对象通常存储在服务器的内存中,可以存储用户的登录状态、购物车信息等各种数据。
-
优点:
- 相对简单易用,被广泛支持。许多编程语言和 Web 框架都提供了对 Session 的内置支持。
- 安全性较高,因为 Session 数据存储在服务器端,不容易被客户端篡改。
-
缺点:
- 服务器资源消耗较大,尤其是在高并发场景下,大量的 Session 对象会占用服务器的内存。
- 如果服务器集群部署,需要进行 Session 共享,增加了系统的复杂性。
Cookie
纠正:3->请求头非请求体
设置Cookie和获取Cookie的代码展示
以及请求响应过程中传递Cookie的过程展示
优缺点:
跨域:跨域在 Web 开发中是指当一个网页的脚本试图访问来自不同源(不同的域名、协议或端口)的资源时的情况。
Sesssion
2.基于 Token 的会话技术(如 JWT)(主流)
-
工作原理:
- 用户在登录成功后,服务器根据用户信息生成一个包含用户身份和权限等信息的 Token(令牌),并将其返回给客户端。客户端在后续的请求中携带这个 Token,服务器通过验证 Token 的有效性来确定用户的身份和状态。
- Token 通常是一个经过加密的字符串,可以使用对称加密或非对称加密算法进行签名,以确保其真实性和完整性。
-
优点:
- 无状态,服务器不需要存储会话信息,减轻了服务器的负担,易于水平扩展。
- 可以在不同的服务器之间共享,适用于分布式系统和微服务架构。
- 具有较好的跨平台性,可以在不同的客户端(如 Web 浏览器、移动应用等)中使用。
-
缺点:
- 一旦 Token 被泄露,可能会导致安全问题,因为 Token 中包含了用户的敏感信息。
- Token 的有效期管理相对复杂,如果 Token 过期时间设置过长,可能会增加安全风险;如果设置过短,可能会影响用户体验。
JWT
JWT(JSON Web Token)是一种用于在网络应用环境中进行安全信息传递的开放标准。
Jwt令牌生成
/**
* 测试Jwt令牌的生成
*/
@Test
public void testGenJet() {
Map<String, Object> claims = new HashMap<>();
claims.put("id", 5201314);
claims.put("name", "THL");
String jwt=Jwts.builder()
.signWith(SignatureAlgorithm.HS256, "Dawn") //签名算法
.setClaims(claims) //自定义内容(载荷部分)
.setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000)) //设置Jwt令牌有效期为一个小时
.compact();
System.out.println(jwt);
}
Jwt令牌解析
/**
* 测试令牌的解析
*/
@Test
public void testParseJwt(){
Claims claims=Jwts.parser()
.setSigningKey("Dawn")
.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiVEhMIiwiaWQiOjUyMDEzMTQsImV4cCI6MTcyNTAwOTA0NH0.2dz_cOXKMw4js0z7xm8B5X4aaT3diHRup_xLx2Mk4KU")
.getBody();
System.out.println(claims);
}
一、工作原理
JWT 由三部分组成,分别是头部(Header)、载荷(Payload)和签名(Signature)。
- 头部:通常包含两部分信息,令牌的类型(即 “JWT”)和所使用的签名算法,如 HMAC SHA256 或 RSA。
- 载荷:包含有关用户的声明,例如用户 ID、角色、过期时间等。这些声明被编码为 JSON 对象。
- 签名:是使用头部中指定的算法对头部和载荷进行签名生成的。签名的目的是确保令牌在传输过程中没有被篡改。
Base64 是一种编码方式,主要用于将二进制数据编码成 ASCII 字符集的可打印字符,以便在不同的系统和协议中进行传输和存储。
二、优点
- 无状态:服务器不需要保存令牌的状态信息,因为所有必要的信息都包含在令牌本身中。这使得服务器可以更容易地进行扩展,并且减少了服务器的存储需求。
- 跨域认证:由于 JWT 是基于 JSON 的开放标准,它可以在不同的域之间进行安全的认证和授权。
- 安全性:使用签名机制确保令牌的完整性和真实性,防止令牌被篡改。
三、应用场景
- 单点登录(SSO):用户在一个系统中登录后,生成的 JWT 可以在多个相关系统中使用,无需再次登录。
- API 授权:在 RESTful API 中,JWT 可以用于验证用户的身份和授权用户对特定资源的访问。
- 移动应用:在移动应用中,JWT 可以用于安全地与服务器进行通信,而不需要在每次请求时都传递用户名和密码。
运作过程:
通过Maven引入依赖
<!-- JWT令牌-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
在测试类中测试生成Jwt令牌
通过链式编程的风格
/**
* 测试Jwt令牌的生成
*/
@Test
public void testGenJet() {
Map<String, Object> claims = new HashMap<>();
claims.put("id", 18);
claims.put("name", "THL");
String jwt=Jwts.builder()
.signWith(SignatureAlgorithm.HS256, "Dawn") //签名算法
.setClaims(claims) //自定义内容(载荷部分)
.setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000)) //设置Jwt令牌有效期为一个小时
.compact();
System.out.println(jwt);
}
生成的Jwt令牌:
eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiVEhMIiwiaWQiOjE4LCJleHAiOjE3MjQ5OTMyMjJ9.98OHK8iRfNxEvPlRuPHO9eAkZKhnqyC6uwRRxB01IXc
在Jwt官网打开解码
Jwt工具类
package com.traingcase.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 = "Dawn";
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;
}
}
实现带令牌登录的Controller
package com.traingcase.controller;
import com.traingcase.pojo.Emp;
import com.traingcase.pojo.Result;
import com.traingcase.service.EmpService;
import com.traingcase.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.RequestMapping;
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")
//因为用户名和密码每个Emp(员工)都有
// 所以不用创建新的对象存数据,直接用Emp存
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.getDeptId());
claims.put("name",e.getName());
claims.put("username",e.getUsername());
String jwt=JwtUtils.generateJwt(claims);
return Result.success(jwt);
}
return Result.error("用户名或密码错误");
}
}
响应后,令牌会下放到前端
二、过滤器(Filter)
-
定义和作用:
- 过滤器是 Servlet 规范中的一部分,它可以对客户端的请求和服务器的响应进行拦截和处理。
- 主要用于实现一些通用的功能,如字符编码转换、日志记录、权限验证、数据压缩等。
-
工作原理:
- 当客户端发送请求时,容器会根据配置的过滤器链依次调用过滤器。每个过滤器可以对请求进行处理,然后将请求传递给下一个过滤器或目标资源。
- 当目标资源处理完请求并生成响应后,响应会沿着过滤器链反向传递,每个过滤器可以对响应进行处理。
-
实现方式:
- 在 Java Web 开发中,可以通过实现
javax.servlet.Filter
接口来创建过滤器。 - 需要在 web.xml 文件或使用注解进行配置,指定过滤器的作用范围和执行顺序。
- 在 Java Web 开发中,可以通过实现
-
特点:
- 过滤器是基于函数回调的方式实现的,它是在容器级别对请求和响应进行处理,与具体的业务逻辑相对独立。
- 可以对所有的请求进行处理,包括静态资源的请求。
代码案例
package com.traingcase.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter(urlPatterns = "/*")
public class DemoFilter 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 {
System.out.println("拦截到了请求");
//放行
filterChain.doFilter(servletRequest,servletResponse);
}
//销毁方法
@Override
public void destroy() {
Filter.super.destroy();
}
}
@WebFilter(urlPatterns = "/*")
注解表明这个过滤器会对所有的请求进行过滤,因为urlPatterns
设置为 “/*”,表示匹配所有的 URL 路径。
servletRequest
:表示传入的 Servlet 请求对象,可以通过这个对象获取请求的信息,如请求参数、请求头、请求体等。servletResponse
:表示传出的 Servlet 响应对象,可以在这个对象上设置响应的信息,如响应头、响应体等。filterChain
:表示过滤器链,通过调用filterChain.doFilter(servletRequest, servletResponse)
可以将请求传递给下一个过滤器或目标资源进行处理。将请求传递给下一个过滤器(如果有)或目标资源进行处理。这一步非常重要,确保请求能够继续在过滤器链中传递或到达最终的目标资源。
过滤器链
具体代码展现
package com.traingcase.filter;
import com.alibaba.fastjson.JSONObject;
import com.traingcase.pojo.Result;
import com.traingcase.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
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 {
HttpServletRequest httpServletRequest=(HttpServletRequest) servletRequest;
HttpServletResponse httpServletResponse= (HttpServletResponse) servletResponse;
//1.获取请求URL
String url = httpServletRequest.getRequestURI().toString();
log.info("URL:{}",url);
//2.判断是不是登录请求,URL是否包含login
if(url.contains("login")){
log.info("登录操作,放行");
filterChain.doFilter(servletRequest,servletResponse);
return;
}
//3.获取请求头中的令牌
String jwt=httpServletRequest.getHeader("token");
//4.判断令牌是否存在,如果不存在,返回错误结果
if(jwt==null||jwt.length()==0){
log.info("请求头token为空");
Result error=Result.error("NOT_LOGIN");
//手动转换 对象->JSON-----基于阿里巴巴fastjson工具包
String notLogin=JSONObject.toJSONString(error);
httpServletResponse.getWriter().write(notLogin);
return;
}
//5.解析token,如果解析失败,返回错误结果
//自己设置try catch 看解析能否成功,失败会有异常
try {
JwtUtils.parseJWT(jwt);
}catch (Exception e){
e.printStackTrace();
log.info("解析令牌失败");
Result error=Result.error("NOT_LOGIN");
//手动转换 对象->JSON-----基于阿里巴巴fastjson工具包
String notLogin=JSONObject.toJSONString(error);
httpServletResponse.getWriter().write(notLogin);
return;
}
//6.放行
log.info("令牌合法,放行");
filterChain.doFilter(servletRequest,servletResponse);
return;
}
}
FASTJSON依赖
<!-- fastJSON-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
三、拦截器(Interceptor)
-
定义和作用:
- 拦截器是在 Spring Web MVC 等框架中提供的一种机制,用于对请求进行拦截和处理。
- 主要用于实现一些与业务相关的功能,如用户认证、权限检查、性能监控、日志记录等。
-
工作原理:
- 当客户端发送请求时,请求会先经过 DispatcherServlet,然后 DispatcherServlet 会根据配置的拦截器链依次调用拦截器。每个拦截器可以对请求进行处理,然后将请求传递给下一个拦截器或控制器。
- 当控制器处理完请求并生成响应后,响应会沿着拦截器链反向传递,每个拦截器可以对响应进行处理。
-
实现方式:
- 在 Spring Web MVC 中,可以通过实现
HandlerInterceptor
接口或继承HandlerInterceptorAdapter
类来创建拦截器。 - 需要在 Spring 配置文件中进行配置,指定拦截器的作用范围和执行顺序。
- 在 Spring Web MVC 中,可以通过实现
-
特点:
- 拦截器是基于 AOP(面向切面编程)的思想实现的,它与具体的业务逻辑结合更紧密。
- 通常只能对控制器的请求进行处理,不能对静态资源的请求进行处理。
拦截器类
package com.traingcase.interceptor;
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 //交给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....");
}
}
配置类
package com.traingcase.config;
import com.traingcase.filter.LoginCheckFilter;
import com.traingcase.interceptor.LoginCheckInterceptor;
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;
@Configuration //配置类
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");
}
}
拦截器->拦截路径
过滤器和拦截器的区别
-
实现方式:
- 过滤器是 Servlet 规范中的一部分,由容器管理;拦截器是特定框架提供的机制,由框架管理。
- 过滤器的实现相对简单,只需要实现一个接口;拦截器的实现相对复杂,需要实现特定的接口并进行配置。
-
作用范围:
- 过滤器可以对所有的请求进行处理,包括静态资源的请求;拦截器通常只能对控制器的请求进行处理。
- 过滤器可以在请求到达目标资源之前和响应返回客户端之后进行处理;拦截器可以在请求到达控制器之前、控制器处理请求之后和响应返回客户端之前进行处理。
-
执行顺序:
- 过滤器的执行顺序是由容器根据配置的过滤器链确定的;拦截器的执行顺序是由框架根据配置的拦截器链确定的。
- 在 Spring Web MVC 中,拦截器的执行顺序可以通过实现
Ordered
接口或使用@Order
注解来指定。
-
与业务逻辑的耦合度:
- 过滤器与业务逻辑相对独立,主要用于实现通用的功能;拦截器与业务逻辑结合更紧密,主要用于实现与业务相关的功能。
四、使用场景
-
过滤器的使用场景:
- 字符编码转换:确保所有的请求和响应都使用统一的字符编码。
- 日志记录:记录请求和响应的详细信息,便于系统的监控和调试。
- 数据压缩:对响应数据进行压缩,减少网络传输的数据量。
- 权限验证:对请求进行权限验证,确保只有授权的用户才能访问特定的资源。
-
拦截器的使用场景:
- 用户认证:在请求到达控制器之前进行用户认证,确保用户已经登录。
- 权限检查:在请求到达控制器之前进行权限检查,确保用户具有访问特定资源的权限。
- 性能监控:记录请求的处理时间,便于系统的性能优化。
- 日志记录:记录与业务相关的日志信息,便于系统的业务分析和监控。
过滤器和拦截器都是在 Web 开发中非常有用的技术手段,它们可以帮助我们实现一些通用的和与业务相关的功能。