【学习总结|DAY032】后端Web实战:登录认证
在 Web 后端开发中,登录认证是保障系统安全和用户数据隐私的关键环节。本文将结合实际开发案例,深入探讨登录功能与登录校验的实现思路和技术细节,希望能帮助读者更好地掌握这一重要知识点。
一、登录功能实现
1.1 思路分析
登录功能的核心在于验证用户输入的用户名和密码是否正确。当用户名和密码都匹配时,判定登录成功;反之则登录失败。从技术角度看,登录功能本质上是依据用户名和密码查询员工信息。例如,在 Tlias 智能学习辅助系统中,用户输入用户名和密码,系统在数据库中查找对应员工记录,以此确定登录结果。
1.2 接口设计
- 请求路径:
/login
- 请求方式:
POST
- 请求参数:以
application/json
格式传递,包含必填的username
(用户名,字符串类型)和password
(密码,字符串类型)。请求数据样例:
{
"username": "jinyong",
"password":"123456"
}
- 响应数据:同样采用
application/json
格式,包含必须的code
(响应码,1 表示成功,0 表示失败)、非必须的msg
(提示信息,字符串类型)以及必须的data
(返回数据对象)。data
对象包含员工 ID(id
,数字类型)、用户名(username
,字符串类型)、姓名(name
,字符串类型)和令牌(token
,字符串类型)。响应数据样例:
{
"code":1,
"msg":"success",
"data":{
"id":2,
"username": "songjiang",
"name":"宋江",
"token": "eyJhbGci0iJIUzI1NiJ9.eyJpZCI65hbWUi0iJzb2CJJeHAi0jE20Tg3MDE3NjJ9..."
}
}
1.3 代码实现
在 Java 开发中,以 Spring Boot 和 MyBatis 框架为例,在业务逻辑层实现登录功能:
@Service
public class EmpService {
@Autowired
private EmpMapper empMapper;
public LoginInfo login(Emp emp) {
// 调用mapper,根据用户名密码查询员工信息
Emp e = empMapper.login(emp.getUsername(), emp.getPassword());
// 如果员工存在,组装登录成功信息
if (e != null){
Map<String, Object> claims = new HashMap<>();
claims.put("id", e.getId());
claims.put("username", e.getUsername());
return new LoginInfo(e.getId(), e.getUsername(), e.getName(), JwtUtils.generateJwt(claims));
}
return null;
}
}
上述代码中,EmpMapper
负责数据库查询操作,JwtUtils.generateJwt(claims)
用于生成 JWT 令牌。
二、登录校验技术
在测试登录功能时,常出现未登录也能访问服务端功能接口的问题。为解决该问题,需引入登录校验机制,确保只有登录成功的用户才能访问后台系统数据。实现登录校验主要涉及会话技术、JWT 令牌、过滤器(Filter)和拦截器(Interceptor)。
2.1 会话技术
会话指用户从打开浏览器访问 Web 服务器资源到断开连接的过程,期间包含多次请求和响应。会话跟踪用于识别多次请求是否来自同一浏览器,以实现同一会话内多次请求间的数据共享。常见的会话跟踪方案有:
- Cookie(客户端会话跟踪技术):利用 HTTP 协议中的
Set-Cookie
响应头和Cookie
请求头传递数据。优点是 HTTP 协议原生支持;缺点是移动端 APP 无法使用,安全性低(用户可禁用)且不能跨域。 - Session(服务端会话跟踪技术):底层基于 Cookie,通过
JSESSIONID
标识会话。优点是数据存储在服务端,相对安全;缺点是服务器集群环境下无法直接使用,且存在 Cookie 的固有缺点。 - 令牌技术:支持 PC 端和移动端,能解决集群环境下的认证问题,减轻服务器存储压力,但需要自行实现。
2.2 JWT 令牌
- 介绍:JSON Web Token(JWT)是一种简洁、自包含的格式,用于在通信双方以 JSON 数据格式安全传输信息。它由三部分组成:
- Header(头):记录令牌类型、签名算法等,如
{"alg":"HS256","type":"JWT"}
。 - Payload (有效载荷):携带自定义信息和默认信息,如
{"id":"1","username":"Tom"}
。 - Signature (签名):通过将
header
、payload
和指定秘钥,利用指定签名算法计算生成,用于防止 Token 被篡改,确保安全性。
- Header(头):记录令牌类型、签名算法等,如
- 生成 / 解析:在 Java 项目中,引入
jjwt
依赖后,可借助官方工具类Jwts
生成和解析 JWT 令牌。
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
生成令牌示例:
@Test
public void testGenJwt() {
Map<String, Object> claims = new HashMap<>();
claims.put("id", 10);
claims.put("username", "itheima");
String jwt = Jwts.builder().signWith(SignatureAlgorithm.HS256, "SVRIRUlNQQ==")
.addClaims(claims)
.setExpiration(new Date(System.currentTimeMillis() + 12*3600*1000))
.compact();
System.out.println(jwt);
}
解析令牌示例:
@Test
public void testParseJwt() throws Exception {
String jwtToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...";
Claims claims = Jwts.parser()
.setSigningKey("SVRIRUlNQQ==")
.parseClaimsJws(jwtToken)
.getBody();
System.out.println(claims);
}
2.3 过滤器(Filter)
- 概念:Filter 是 JavaWeb 三大组件之一,能拦截对资源的请求,实现通用操作,如登录校验、统一编码处理等。
- 快速入门:
- 定义 Filter:创建类实现
Filter
接口,并重写其init
、doFilter
和destroy
方法。
- 定义 Filter:创建类实现
@WebFilter(urlPatterns = "/*")
public class DemoFilter implements Filter {
// 初始化方法,服务器启动时调用一次
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init ...");
}
// 拦截到请求时调用,可多次调用
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws Exception{
System.out.println("拦截到了请求...");
chain.doFilter(servletRequest, servletResponse);
}
// 销毁方法,服务器关闭时调用一次
public void destroy() {
System.out.println("destroy ... ");
}
}
- 配置 Filter:在 Filter 类上使用
@WebFilter
注解配置拦截路径,在引导类上加@ServletComponentScan
开启 Servlet 组件支持。
@ServletComponentScan
@SpringBootApplication
public class TliasManagementApplication {
}
- 令牌校验 Filter:
- 流程:获取请求 URL,判断是否为登录请求(包含
login
),若是则放行;否则获取请求头中的令牌,判断令牌是否存在,若不存在或解析失败则响应 401,解析成功则放行。
- 流程:获取请求 URL,判断是否为登录请求(包含
@WebFilter(urlPatterns = "/*")
public class JwtFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestURI = httpRequest.getRequestURI();
if (requestURI.contains("login")) {
chain.doFilter(request, response);
return;
}
String token = httpRequest.getHeader("token");
if (token == null) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
try {
Claims claims = Jwts.parser().setSigningKey("SVRIRUlNQQ==").parseClaimsJws(token).getBody();
chain.doFilter(request, response);
} catch (Exception e) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
}
// init和destroy方法省略
}
- 执行流程:放行前逻辑 -> 放行 -> 访问资源 -> 放行后逻辑。
- 拦截路径:
/*
表示拦截所有资源;/emps/*
表示拦截/emps
目录下的所有资源。 - 过滤器链:一个 Web 应用中可配置多个过滤器形成过滤器链,注解配置的 Filter 优先级按类名字符串自然排序。
2.4 拦截器(Interceptor)
- 概念:拦截器是 Spring 框架提供的动态拦截控制器方法执行的机制,可在方法调用前后执行预设代码。
- 快速入门:
- 定义拦截器:实现
HandlerInterceptor
接口,重写preHandle
(目标资源方法执行前执行,返回true
放行,false
不放行)、postHandle
(目标资源方法执行后执行)和afterCompletion
(视图渲染完毕后执行)方法。
- 定义拦截器:实现
@Component
public class DemoInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
return true;
}
@Override
public void postHandle(HttpServletRequest req, HttpServletResponse resp, Object handler, ModelAndView mv) throws Exception {
System.out.println("preHandle...");
}
@Override
public void afterCompletion(HttpServletRequest req, HttpServletResponse resp, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion...");
}
}
- 注册拦截器:定义配置类实现
WebMvcConfigurer
接口,注册拦截器。
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private DemoInterceptor demoInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(demoInterceptor).addPathPatterns("/**");
}
}
- 令牌校验拦截器:与令牌校验 Filter 流程类似,获取请求 URL 判断是否为登录请求,获取并校验令牌。
@Component
public class JwtInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
if (requestURI.contains("login")) {
return true;
}
String token = request.getHeader("token");
if (token == null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
try {
Claims claims = Jwts.parser().setSigningKey("SVRIRUlNQQ==").parseClaimsJws(token).getBody();
return true;
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
}
// postHandle和afterCompletion方法省略
}
- 拦截路径:
/*
只能拦截一级路径;/**
能拦截任意级路径。 - 执行流程:与过滤器不同,拦截器只拦截 Spring 环境中的资源,且在控制器方法前后执行不同逻辑。
通过上述登录功能和登录校验技术的详细介绍,希望读者对 Web 后端开发中的登录认证机制有更深入的理解和掌握,在实际项目中能够灵活运用这些技术,构建安全可靠的 Web 应用。