1、认证与授权
-
认证(Authentication):验证用户身份
-
授权(Authorization):允许用户访问那些资源,验证用户权限
2、JWT无状态认证
JWT(JSON Web Token):无状态认证,是一种紧凑安全的开放标准,用于在网络应用环境间传递声明(claims)。
JWT = Header +Payload + Signature
- Header:头部,包含令牌类型(通常是 "JWT")和签名算法(如 HMAC SHA256)。
- Payload:有效载荷,包含用户信息和其他元数据(如过期时间等)。
- Signature:签名部分,用于验证token的完整性。
服务端不保存Session,Token包含用户信息,客户端每次请求携带。
- Token 通常指的是整个 JWT 字符串,它是由头部、负载和签名组成,经过编码并用点 (.) 分隔开
- Http请求中:
(授权)Authorization: Bearer <token>
3、Spring Security + JWT 集成
3.1 添加依赖 pom.xml
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT 相关 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
3.2 JWT 相关配置
JWT 工具类:生成和解析Token
package com.example.utils;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class JwtUtil {
@Value("${jwt.secret:mySecretKey12345678901234567890123456789012}")
private String secret;
@Value("${jwt.expiration:86400000}")
private Long expiration; // 默认1天
private Key getSigningKey() {
return Keys.hmacShaKeyFor(secret.getBytes()); //生成 HMAC SHA 密钥
}
/**
* 生成JWT Token
*/
public String generateToken(Integer userId, String username) {
Map<String, Object> claims = new HashMap<>();
//claims映射,存储用户信息
claims.put("userId", userId);
claims.put("username", username);
return Jwts.builder()
.setClaims(claims)
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
}
/**
* 从Token中获取用户ID
*/
public Integer getUserIdFromToken(String token) {
//创建解析器,设置签名密钥,解析token,获取claims
Claims claims = Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token) //解析Token
.getBody();
return claims.get("userId", Integer.class);
}
/**
* 从Token中获取用户名
*/
public String getUsernameFromToken(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
/**
验证Token是否有效
* 验证给定的JWT是否有效,通过解析和验证 Token 的签名来实现这一点。
*/
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(getSigningKey()).build().parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
}
3.3 Spring Security 相关配置
自定义UserDetailsService:实现基于数据库的用户认证与授权。
允许应用程序灵活地从任何数据源(如数据库)加载用户信息,并确保在用户登录时能够正确地验证身份及授权访问权限。
package com.example.security;
import com.example.entity.User;
import com.example.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 根据用户名查询用户
User user = userMapper.selectOne(
new LambdaQueryWrapper<User>()
.eq(User::getUsername, username)
);
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
// 返回Spring Security的User对象
return org.springframework.security.core.userdetails.User
.withUsername(username)
.password(user.getPassword()) // 注意:密码应为加密后的
.authorities(user.getRole()) // 权限
.build();
}
}
3.4 Spring Security + JWT 集成
创建JWT认证过滤器(JwtAuthenticationFilter):JWT认证通常是通过Authorization header传递的,需要自定义过滤器来拦截请求并验证JWT
package com.example.security;
import com.example.utils.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private CustomUserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
//从请求中获取token
String token = getTokenFromRequest(request);
if (token != null && jwtUtil.validateToken(token)) {
//提取用户名并加载用户信息
String username = jwtUtil.getUsernameFromToken(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
//创建认证对象并设置到安全上下文
//创建认证token
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
//设置认证详情
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
//设置Security上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String getTokenFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
配置Spring Security:SecurityConfig
- 认证管理器AuthenticationManager:处理认证请求,登录接口使用
- 安全过滤器链SecurityFilterChain:配置请求权限,CSRF设置
- 密码加密PasswordEncoder:使用Bcrypt加密
package com.example.config;
import com.example.security.CustomUserDetailsService;
import com.example.security.JwtAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private CustomUserDetailsService userDetailsService;
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
//安全过滤器链
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
//禁用CSRF(跨站请求伪造)保护
.csrf().disable()
//配置为无状态会话
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
//配置请求的授权规则
.authorizeRequests()
.antMatchers("/api/users/login", "/api/users/register").permitAll()
.antMatchers("/doc.html", "/webjars/**", "/swagger-resources/**", "/v2/api-docs", "/v3/api-docs").permitAll()
.anyRequest().authenticated() //其他接口需要认证
.and()
.userDetailsService(userDetailsService) //指定自定义的用户信息服务
//将自定义的 JWT 过滤器添加到 UsernamePasswordAuthenticationFilter 之前
// 确保在处理用户名和密码的认证之前先进行 JWT 的验证。
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
//认证管理器AuthenticationManager:负责处理身份验证逻辑
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception{
return authenticationConfiguration.getAuthenticationManager();
}
//密码加密
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
修改UserServiceImpl,密码加密
// 在UserServiceImpl中添加
@Autowired
private PasswordEncoder passwordEncoder;
@Override
@Transactional
public boolean register(User user) {
// 检查用户名是否已存在
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername, user.getUsername());
if (userMapper.selectCount(wrapper) > 0) {
return false;
}
// 密码加密
user.setPassword(passwordEncoder.encode(user.getPassword()));
int rows = userMapper.insert(user);
return rows > 0;
}
修改控制器controller:处理用户登录、注册等请求并生成JWT
// 在UserController中添加
//Spring Security 认证管理器,验证用户的凭证(如用户名和密码)是否有效
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtUtil jwtUtil;
@PostMapping("/login")
public Result<Map<String, Object>> login(@RequestBody Map<String, String> loginInfo) {
String username = loginInfo.get("username");
String password = loginInfo.get("password");
// 使用Spring Security进行认证
//基于用户名,密码的身份认证请求
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(username, password);
//验证用户凭据
Authentication authentication = authenticationManager.authenticate(authToken);
//设置认证上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
// 生成JWT令牌
User user = userService.findByUsername(username); // 需要新增该方法
String token = jwtUtil.generateToken(user.getId(), username);
Map<String, Object> data = new HashMap<>();
data.put("token", token);
data.put("userId", user.getId());
data.put("username", user.getUsername());
return Result.success(data);
}