文章目录
什么是鉴权系统
后端开发鉴权系统是一种用于验证和授权用户访问后端资源的系统,在保障系统安全和资源合理访问方面起着关键作用,以下是关于它的详细介绍:
定义与作用
- 定义:鉴权系统主要用于判断用户是否具有访问特定资源或执行特定操作的权限。它通过一系列的规则和流程,对用户的身份和权限进行验证和管理,确保只有经过授权的用户才能访问相应的资源或执行相应的操作。
- 作用:鉴权系统能有效保护后端数据和功能的安全,防止未经授权的访问、数据泄露、恶意操作等安全威胁,保证系统的稳定性和数据的完整性。
主要组成部分
- 用户管理模块:负责用户信息的存储和管理,包括用户的基本信息、账号密码、角色等。
- 认证模块:主要用于验证用户的身份,常见的认证方式有用户名 / 密码认证、令牌认证、OAuth 认证等。
- 授权模块:根据用户的身份和角色,确定用户对不同资源的访问权限。通常通过权限列表、访问控制列表(ACL)、角色基于访问控制(RBAC)等技术实现。
- 令牌生成与管理模块:在一些认证方式中,会生成令牌用于用户后续的访问验证。该模块负责令牌的生成、颁发、刷新和验证等操作。
- 数据存储:用于存储用户信息、权限信息、令牌等数据,常见的存储方式有数据库、缓存等。
工作原理
- 用户认证:用户首先向系统提交身份凭证,如用户名和密码。认证模块接收到用户提交的信息后,对其进行验证。如果验证成功,系统会为用户生成一个唯一的身份标识,如令牌。
- 权限检查:用户在访问受保护的资源或执行特定操作时,系统会根据用户的身份标识,检查其是否具有相应的权限。授权模块会查询权限配置信息,判断用户是否被允许访问该资源或执行该操作。
- 访问控制:根据权限检查的结果,系统决定是否允许用户访问资源或执行操作。如果用户具有相应权限,系统将允许用户访问资源或执行操作,并返回相应的结果;如果用户没有权限,系统将拒绝用户的请求,并返回相应的错误信息。
常用技术和框架
- Spring Security:是一个针对 Spring 框架的安全框架,提供了全面的安全解决方案,包括认证、授权、加密等功能。
- Apache Shiro:是一个功能强大且灵活的 Java 安全框架,提供了认证、授权、加密、会话管理等功能。
- JWT(JSON Web Tokens):是一种用于在网络应用间传递声明的开放标准,常用于用户认证和授权。它可以将用户信息编码为一个 JSON 对象,并通过签名来保证数据的完整性和安全性。
基于SpringBoot + JWT的鉴权系统设计与实现指南
前言
现代Web应用的鉴权挑战在微服务架构盛行的当下,传统Session鉴权机制面临扩展性差、跨域支持复杂等问题。下面将以SpringBoot为基础框架,详细讲解如何通过JWT(JSON Web Token)构建安全高效的鉴权系统,涵盖核心原理、完整实现路径及安全最佳实践。
技术对比
特性 | Session-Cookie | JWT |
---|---|---|
状态管理 | 服务端存储 | 无状态 |
扩展性 | 集群需同步 | 天然支持分布式 |
跨域支持 | 需额外配置 | 原生支持CORS |
移动端适配 | Cookie处理复杂 | Header携带更友好 |
令牌技术
实现:
- 本质是一个字符串
- 登录成功,服务器生成一个令牌作为该用户的合法身份凭证,在响应数据中把令牌传递给前端
- 前端将令牌存储(可以存在Cookie,也可以存在其他空间中)
优点: - 支持PC、移动端
- 解决集群环境下的认证问题
- 减轻服务器存储压力
JWT令牌
JSON Web Token Introduction - jwt.io
- 全称:JSON Web Token
- 功能:定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。
- 组成 :
- 第一部分:Header(头),记录令牌类型,签名算法等,例如:{"alg":""HS256","type":"JWT"}
- 第二部分:PayLoad(有效载荷),携带一些自定义信息,默认信息等。例如:{"id":"1","username":"Tom"}
- 第三部分:Signature(签名),防止Token被篡改,确保安全性。用header、payload,并加入指定秘钥,通过指定签名算法计算而来。
Base64: 是一种基于64个可打印字符(A-Z a-z 0-9 + = /)来表示二进制数据的编码方式
实现全流程
1. 依赖引入
首先,确保你的 pom.xml
中包含了 Spring Boot Web 和 JWT 的相关依赖:
xml
<dependencies>
<!-- Spring Boot Web 模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JWT 处理库 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
</dependencies>
- Java 9 开始对 Java 平台进行了模块化,将 Java EE 的相关模块(包括
javax.xml.bind
)从 Java 标准版中移除了。而javax.xml.bind.DatatypeConverter
就属于被移除的javax.xml.bind
模块。 - 实际运用中发现
jjwt-api
依赖于jaxb-api
,这里我们手动添加
2. JWT 工具类
这个工具类包含了生成和解析 JWT 的方法。我们会使用一个密钥来签名 Token,并支持过期时间的设置。
java
public class JwtUtils {
/**
* 生成jwt
* 使用HS256算法
*
* @param secretKey jwt秘钥
* @param ttlMillis jwt过期时间(毫秒)
* @param claims 设置的信息
* @return
*/
public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
// 指定签名的时候使用的签名算法,也就是header那部分
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 生成JWT的时间
long expMillis = System.currentTimeMillis() + ttlMillis;
Date exp = new Date(expMillis);
// 设置jwt的body
JwtBuilder builder = Jwts.builder()
// 如果有私有声明,一定要先设置这个自己创建的私有的声明
// 这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就会覆盖了那些标准的声明
.setClaims(claims)
// 设置签名使用的签名算法和签名使用的秘钥
.signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
// 设置过期时间
.setExpiration(exp);
return builder.compact();
}
/**
* Token解密
*
* @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
* @param token 加密后的token
* @return
*/
public static Claims parseJWT(String secretKey, String token) {
// 得到DefaultJwtParser
Claims claims = Jwts.parser()
// 设置签名的秘钥
.setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
// 设置需要解析的jwt
.parseClaimsJws(token).getBody();
return claims;
}
}
3. JWT 拦截器(Interceptor)
我们需要一个拦截器来验证请求中的 JWT Token。所有需要认证的请求都会经过这个拦截器检查。
java
//需要引入上面定义的jwt工具类
@Component
@Slf4j
public class JwtTokenInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断当前拦截到的是Controller的方法还是其他资源
if (!(handler instanceof HandlerMethod)) {
//当前拦截到的不是动态方法,直接放行
return true;
}
// 获取Authorization头
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 返回401
response.getWriter().write("缺少有效的认证凭证");
return false;
}
try {
// 去掉 "Bearer " 前缀,获取实际的Token
Claims claims = JwtUtils.parseJWT("Authorization", token); // 解析Token
// 将解析后的用户信息放到request的属性中,以便后续使用
request.setAttribute("userClaims", claims);
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 返回401
response.getWriter().write("Token无效或过期");
return false;
}
return true; // 继续执行下一个拦截器或控制器
}
}
4. 拦截器注册
接下来,我们需要将拦截器注册到 Spring MVC 配置中。
java
@Configuration
@Slf4j
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
@Autowired
private JwtTokenInterceptor jwtTokenInterceptor;
public void addInterceptors(InterceptorRegistry registry) {
log.info("开始注册自定义拦截器...");
registry.addInterceptor(jwtTokenInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/login", "/register");
}
}
5. 登录接口
创建一个简单的登录接口,成功登录后生成 JWT 返回给客户端。
java
@PostMapping("/login")
public Result login(@RequestBody UserDTO userDTO) {
log.info("用户登录");
User user = userService.login(userDTO);
if (user == null) {
return Result.error("用户不存在");
}
// 登录成功,生成jwt令牌
Map<String, Object> claims = new HashMap<>();
claims.put("userId", user.getId());
// 这里可以通过配置类或其他方式传入秘钥
String token = JwtUtils.createJWT("your_secret_key", 60000, claims);
UserVO userVO = new UserVO();
BeanUtils.copyProperties(user, userVO);
// 这里我设计了一个登录响应类
// 中心思想是返回所需数据以及token
LoginResponse loginResponse = new LoginResponse(userVO, token);
return Result.success(loginResponse);
}