Springboot3.X+security6.5+jdk21

1.引入依赖

java 复制代码
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <version>3.5.7</version>
    	</dependency>
    	        <!-- JWT (兼容 JDK 21) -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.12.3</version>
        </dependency>

2.yml配置

java 复制代码
app:
  jwt:
    secret: "mySuperSecretKeyForJWTGenerationInSpringBoot3WithJDK21"
    expiration: 86400000 # 24小时
    refresh-expiration: 604800000 # 7天

3.代码

3.1 GrantedAuthority

java 复制代码
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
@Data
@JsonSerialize
@NoArgsConstructor
@AllArgsConstructor
public class CustomGrantedAuthority implements GrantedAuthority {

	private String authority;

	@Override
	public String getAuthority() {
		return this.authority;
	}
}

3.2 UserDetails实现

java 复制代码
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
@Data
public class MyUserDetails implements UserDetails {

	private String id;

	private String userName;

	private String password;
	private String role;

	private Collection<CustomGrantedAuthority> authorities;

	public MyUserDetails(User user) {
		this.id = user.getId();
		this.userName = user.getAccountName();
		this.password = user.getPassword();
	}
	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		return this.authorities;
	}

	@Override
	public String getPassword() {
		return this.password;
	}

	@Override
	public String getUsername() {
		return this.userName;
	}
}

3.3 UserDetailsService实现

java 复制代码
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class MyUserService implements UserDetailsService {
	private final IUserService iUserService;
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		User one = iUserService.lambdaQuery()
				.eq(User::getAccountName, username)
				.one();
		if (one == null) {
			throw new UsernameNotFoundException("用户不存在: " + username);
		}
		one.setPassword(new BCryptPasswordEncoder().encode(one.getPassword()));
		MyUserDetails myUserDetails = new MyUserDetails(one);

		//角色
		List<CustomGrantedAuthority> authorities = new ArrayList<>();
		authorities.add(new CustomGrantedAuthority("TEST"));
		//权限--多个
		authorities.add(new CustomGrantedAuthority("menu:ecu:list"));

		myUserDetails.setAuthorities(authorities);
		return myUserDetails;
	}
}

3.4 JwtTokenUtil

java 复制代码
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;


@Component
public class JwtTokenUtil {

	@Value("${app.jwt.secret}")
	private String secret;

	@Value("${app.jwt.expiration}")
	private Long expiration;

	@Value("${app.jwt.refresh-expiration}")
	private Long refreshExpiration;

	private SecretKey getSigningKey() {
		return Keys.hmacShaKeyFor(secret.getBytes());
	}

	public String extractUsername(String token) {
		return extractClaim(token, Claims::getSubject);
	}

	public Date extractExpiration(String token) {
		return extractClaim(token, Claims::getExpiration);
	}

	public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
		final Claims claims = extractAllClaims(token);
		return claimsResolver.apply(claims);
	}

	private Claims extractAllClaims(String token) {
		return Jwts.parser()
				.verifyWith(getSigningKey())
				.build()
				.parseSignedClaims(token)
				.getPayload();
	}

	private Boolean isTokenExpired(String token) {
		return extractExpiration(token).before(new Date());
	}

	public String generateToken(UserDetails userDetails) {
		Map<String, Object> claims = new HashMap<>();
		// 可以在这里添加额外信息到 token 中
		if (userDetails instanceof MyUserDetails myUserDetails) {
			claims.put("userId", myUserDetails.getId());
			claims.put("role", myUserDetails.getRole());
			claims.put("authorities",userDetails.getAuthorities().stream()
					.map(GrantedAuthority::getAuthority)
					.collect(Collectors.toList()));
		}

		return createToken(claims, userDetails.getUsername(), expiration);
	}

	public String generateRefreshToken(UserDetails userDetails) {
		Map<String, Object> claims = new HashMap<>();
		return createToken(claims, userDetails.getUsername(), refreshExpiration);
	}

	private String createToken(Map<String, Object> claims, String subject, Long expiration) {
		return Jwts.builder()
				.claims(claims)
				.subject(subject)
				.issuedAt(new Date(System.currentTimeMillis()))
				.expiration(new Date(System.currentTimeMillis() + expiration))
				.signWith(getSigningKey())
				.compact();
	}

	public Boolean validateToken(String token, UserDetails userDetails) {
		final String username = extractUsername(token);
		return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
	}

	// 新增:从 token 中提取用户ID
	public String extractUserId(String token) {
		final Claims claims = extractAllClaims(token);
		return claims.get("userId", String.class);
	}

	// 新增:从 token 中提取角色
	public String extractRole(String token) {
		final Claims claims = extractAllClaims(token);
		return claims.get("role", String.class);
	}

	public Authentication getAuthentication(String token) {
		Claims claims = extractAllClaims(token);

		Collection<? extends GrantedAuthority> authorities =
				Arrays.stream(claims.get("authorities").toString().split(","))
						.map(CustomGrantedAuthority::new)
						.collect(Collectors.toList());

		UserDetails userDetails = new org.springframework.security.core.userdetails.User(
				claims.getSubject(), "", authorities);

		return new UsernamePasswordAuthenticationToken(userDetails, "", authorities);
	}
}

3.5 OncePerRequestFilter

java 复制代码
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

	private final JwtTokenUtil jwtTokenUtil;
	private final MyUserService userService;

	@Override
	protected void doFilterInternal(HttpServletRequest request,
	                                HttpServletResponse response,
	                                FilterChain chain) throws ServletException, IOException {

		final String requestTokenHeader = request.getHeader("Authorization");

		String username = null;
		String jwtToken = null;

		if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
			jwtToken = requestTokenHeader.substring(7);
			try {
				username = jwtTokenUtil.extractUsername(jwtToken);
			} catch (Exception e) {
				logger.warn("JWT Token 解析失败: " + e.getMessage());
			}
		}

		if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
			MyUserDetails userDetails = (MyUserDetails) this.userService.loadUserByUsername(username);

			if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
				UsernamePasswordAuthenticationToken authToken =
						new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
				authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
				SecurityContextHolder.getContext().setAuthentication(authToken);
			}
		}
		chain.doFilter(request, response);
	}
}

3.6 SecurityConfig

java 复制代码
import lombok.RequiredArgsConstructor;
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.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
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
@EnableMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class SecurityConfig {
	private final UserDetailsService userDetailsService;
	private final JwtAuthenticationFilter jwtAuthenticationFilter;

	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}

	public static void main(String[] args) {
		BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
		String encode = bCryptPasswordEncoder.encode("123456");
		System.out.println(bCryptPasswordEncoder.matches("123456", encode));

	}
	/**
	 * Spring Security 6 推荐的方式
	 * 框架会自动配置 DaoAuthenticationProvider
	 */
	@Bean
	public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
		return config.getAuthenticationManager();
	}

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
				.csrf(AbstractHttpConfigurer::disable)
				.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
				.authorizeHttpRequests(auth -> auth
						.requestMatchers(
								"/api/auth/**",
								"/v3/api-docs/**",
								"/swagger-ui/**",
								"/swagger-ui.html"
						).permitAll()
						.requestMatchers("/api/admin/**").hasRole("ADMIN")
						.requestMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
						.anyRequest().authenticated()
				)
				.userDetailsService(userDetailsService) // 设置 UserDetailsService
				.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

		return http.build();
	}
}

4. 登录验证

4.1 DTO

java 复制代码
@Data
public class LoginRequest {

	@NotBlank(message = "用户名不能为空")
	@Size(min = 3, max = 50, message = "用户名长度必须在3-50个字符之间")
	private String username;

	@NotBlank(message = "密码不能为空")
	@Size(min = 6, max = 100, message = "密码长度必须在6-100个字符之间")
	private String password;
}
java 复制代码
@Data
public class RegisterRequest {

	@NotBlank(message = "用户名不能为空")
	@Size(min = 3, max = 50, message = "用户名长度必须在3-50个字符之间")
	private String username;

	@NotBlank(message = "邮箱不能为空")
	@Email(message = "邮箱格式不正确")
	private String email;

	@NotBlank(message = "密码不能为空")
	@Size(min = 6, max = 100, message = "密码长度必须在6-100个字符之间")
	private String password;

	// 可选:确认密码字段
	private String confirmPassword;
}
java 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
public class JwtResponse {

	private String token;
	private String refreshToken;
	private String type = "Bearer";
	private String username;
	private String email;
	private String role;
	private Long userId;
	private List<String> authorities;

	public JwtResponse(String token, String refreshToken, String username, String role,List<String> authorities) {
		this.token = token;
		this.refreshToken = refreshToken;
		this.username = username;
		this.role = role;
		this.authorities = authorities;
	}

	public JwtResponse(String token, String refreshToken, String username, String email, String role, Long userId) {
		this.token = token;
		this.refreshToken = refreshToken;
		this.username = username;
		this.email = email;
		this.role = role;
		this.userId = userId;
	}
}

4.2 controller

java 复制代码
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;

import java.util.stream.Collectors;

@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
	private final AuthenticationManager authenticationManager;
	private final MyUserService userService;
	private final JwtTokenUtil jwtTokenUtil;

	@PostMapping("/login")
	public RespResult<?> login(@Valid @RequestBody LoginRequest request) {
		Authentication authentication = authenticationManager.authenticate(
				new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
		);

		SecurityContextHolder.getContext().setAuthentication(authentication);
		MyUserDetails userDetails = (MyUserDetails) authentication.getPrincipal();

		String token = jwtTokenUtil.generateToken(userDetails);
		String refreshToken = jwtTokenUtil.generateRefreshToken(userDetails);

		return RespResult.success(new JwtResponse(
				token, refreshToken,
				userDetails.getUsername(),
				userDetails.getRole(),
				userDetails.getAuthorities().stream()
						.map(GrantedAuthority::getAuthority)
						.collect(Collectors.toList())
		));
	}
/*	@PostMapping("/register")
	public ResponseEntity<?> register(@Valid @RequestBody RegisterRequest request) {
		User user = new User();
		user.setUsername(request.getUsername());
		user.setEmail(request.getEmail());
		user.setPassword(request.getPassword());

		User savedUser = userService.register(user);

		return ResponseEntity.ok("用户注册成功,ID: " + savedUser.getId());
	}*/

	@PostMapping("/refresh")
	public ResponseEntity<?> refreshToken(@RequestHeader("Authorization") String refreshToken) {
		if (refreshToken.startsWith("Bearer ")) {
			refreshToken = refreshToken.substring(7);
		}

		String username = jwtTokenUtil.extractUsername(refreshToken);
		MyUserDetails userDetails = (MyUserDetails) userService.loadUserByUsername(username);

		if (jwtTokenUtil.validateToken(refreshToken, userDetails)) {
			String newToken = jwtTokenUtil.generateToken(userDetails);
			return ResponseEntity.ok(new JwtResponse(newToken, refreshToken, username, userDetails.getRole(),userDetails.getAuthorities().stream()
					.map(GrantedAuthority::getAuthority)
					.collect(Collectors.toList())));
		}

		return ResponseEntity.badRequest().body("Refresh Token 无效");
	}

}

4.3 Postman

相关推荐
jtymyxmz2 小时前
1.1.4 Spring的下载及目录结构
java·后端·spring
Tipriest_2 小时前
C++ 图形中间件库Magnum详细介绍
开发语言·c++·magnum
robch2 小时前
Spring 的 DelegatingFilterProxy 用法
java·后端·spring
tryxr2 小时前
Java 不同创建线程的方式什么时候才可以使用 this 来获取线程的引用
java·开发语言·多线程
代码程序猿RIP2 小时前
【C 语言面试】高频考点深度解析
java·面试·职场和发展
ashane13142 小时前
Springboot 启动过程及源码分析
java·spring boot·后端
caron42 小时前
c++ -- 循环依赖解决方案
java·c++·算法
七夜zippoe3 小时前
深入理解Java泛型:类型擦除、通配符PECS原则与实践
java·泛型·通配符·类型擦除·pecs
消失的旧时光-19433 小时前
Kotlin JSON 序列化库选型指南:Kotlinx.serialization vs Gson
开发语言·kotlin·json