Author:
在 Spring Web 后端开发中,登录认证是确保系统安全性的重要环节。以下是关于 Spring Web 后端开发中登录认证的介绍:
一、为什么需要登录认证
二、常见的登录认证方式
基于用户名和密码的认证
基于令牌(Token)的认证
单点登录(SSO)
三、Spring Security 实现登录认证
Spring Security 是一个强大的安全框架,用于在 Spring 应用中实现安全功能,包括登录认证。
配置用户存储
配置认证方式
配置授权规则
集成其他安全功能
四、登录认证的流程
用户提交登录请求
后端验证用户信息
返回认证结果
后续请求的认证
总之,在 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 协议是无状态的,即服务器无法区分不同的请求是否来自同一个用户。为了解决这个问题,会话技术应运而生。
会话技术的作用
跟踪用户状态
维持用户与服务器之间的交互
目前主流的会话技术主要有以下几种:
工作原理:
优点:
缺点:
Cookie
纠正:3->请求头非请求体
设置Cookie和获取Cookie的代码展示
以及请求响应过程中传递Cookie的过程展示
优缺点:
跨域:跨域在 Web 开发中是指当一个网页的脚本试图访问来自不同源(不同的域名、协议或端口)的资源时的情况。
Sesssion
工作原理:
优点:
缺点:
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)。
Base64 是一种编码方式,主要用于将二进制数据编码成 ASCII 字符集的可打印字符,以便在不同的系统和协议中进行传输和存储。
二、优点
三、应用场景
运作过程:
通过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("用户名或密码错误");
}
}
响应后,令牌会下放到前端
定义和作用:
工作原理:
实现方式:
javax.servlet.Filter
接口来创建过滤器。特点:
代码案例
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>
定义和作用:
工作原理:
实现方式:
HandlerInterceptor
接口或继承 HandlerInterceptorAdapter
类来创建拦截器。特点:
拦截器类
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("/**");
}
}
拦截器->拦截路径
过滤器和拦截器的区别
实现方式:
作用范围:
执行顺序:
Ordered
接口或使用 @Order
注解来指定。与业务逻辑的耦合度:
四、使用场景
过滤器的使用场景:
拦截器的使用场景:
过滤器和拦截器都是在 Web 开发中非常有用的技术手段,它们可以帮助我们实现一些通用的和与业务相关的功能。
/**
* 全局异常处理器
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)//捕获所有的异常
public Result ex (Exception ex){
ex.printStackTrace();
return Result.error("操作失败");
}
}