#SpringSecurity #SpringBoot3 #安全认证 #JWT
组件 | 版本 | 必要性说明 |
---|---|---|
Spring Boot | 3.2.0+ | 要求JDK17+ |
Spring Security | 6.2.0+ | 全新配置链式API |
Java | 17+ | Records特性优化DTO封装 |
Lombok | 1.18.30 | 简化实体类开发 |
org.springframework.boot
spring-boot-starter-security
com.baomidou
mybatis-plus-spring-boot3-starter
{version}
com.mysql
mysql-connector-j
runtime
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtUtil jwtUtil;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(authorizeRequests ->
authorizeRequests
.requestMatchers("/api/auth/login").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(new JwtAuthenticationFilter(jwtUtil, userDetailsService),
UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("http://localhost:8080/"); // 允许的源
config.setAllowCredentials(true); // 是否允许发送凭证(如 cookies)
config.addAllowedMethod("*"); // 允许的方法
config.addAllowedHeader("*"); // 允许的头
config.addExposedHeader("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
@Getter
@Setter
@ToString
public class User implements Serializable, UserDetails {
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
private String id;
/**
* 用户名称
*/
private String username;
/**
* 密码
*/
private String password;
@TableField(exist = false)
private List<String> roleIdList;
/**
* 账户是否未过期(1未过期 0已过期)
*/
private boolean isAccountNonExpired = true;
/**
* 账户是否未锁定(1未锁定 0已锁定)
*/
private boolean isAccountNonLocked = true;
/**
* 密码是否未过期(1未过期 0已过期)
*/
private boolean isCredentialsNonExpired = true;
/**
* 账户是否可用(1可用 0不可用)
*/
private boolean isEnabled = true;
// 实现UserDetails接口方法
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles.stream()
.map(SimpleGrantedAuthority::new)
.toList();
}
}
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private IUserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.selectUserByUserName(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
return user;
}
}
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.blog.common.enums.ResultCode;
import com.blog.common.exception.CommonException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
@Component
public class JwtUtil {
@Value("${jwt.issuer}")
private String issuer;
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private int expiration;
public String generateToken(Map<String, String> map) {
//设置令牌的过期时间
Calendar instance = Calendar.getInstance();
instance.add(Calendar.MINUTE, expiration);
JWTCreator.Builder builder = JWT.create();
map.forEach(builder::withClaim);
return builder.withIssuer(issuer)
.withIssuedAt(new Date())
.withExpiresAt(instance.getTime())
.sign(Algorithm.HMAC256(secret));
}
/**
* 解析领陪
*
* @param token
* @return
*/
public DecodedJWT jwtDecode(String token) {
try {
return JWT.require(Algorithm.HMAC256(secret)).build().verify(token);
} catch (SignatureVerificationException e) {
throw new CommonException(ResultCode.TOKEN_SIGNATURE_ERROR);
} catch (AlgorithmMismatchException e) {
throw new CommonException(ResultCode.TOKEN_ALGORITHM_MISMATCH);
} catch (TokenExpiredException e) {
throw new CommonException(ResultCode.TOKEN_EXPIRED);
} catch (Exception e) {
throw new CommonException(ResultCode.TOKEN_ERROR);
}
}
/**
* 校验令牌
*
* @param token
* @return
*/
public boolean validateToken(String token) {
try {
JWT.require(Algorithm.HMAC256(secret)).build().verify(token);
} catch (JWTVerificationException e) {
return false;
}
return true;
}
// 验证 Token 并提取用户名
public String getUsernameFromToken(String token) {
DecodedJWT decodedJWT = jwtDecode(token);
return decodedJWT.getClaim("username").asString();
}
}
import com.blog.common.utils.JwtUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final UserDetailsService userDetailsService;
public JwtAuthenticationFilter(JwtUtil jwtUtil, UserDetailsService userDetailsService) {
this.jwtUtil = jwtUtil;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = extractToken(request);
if (token != null && jwtUtil.validateToken(token)) {
String username = jwtUtil.getUsernameFromToken(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String extractToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class LoginController {
@GetMapping("/login")
public LoginVo login(@RequestBody LoginDto loginDto) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginDto.getUsername(), loginDto.getPassword());
// 认证
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
// 保存认证状态
SecurityContextHolder.getContext().setAuthentication(authenticate);
//获取用户信息
User user = (User) authenticate.getPrincipal();
LoginVo loginVo = new LoginVo();
loginVo.setId(user.getId());
Map<String, String> map = new HashMap<>();
map.put("id", user.getId());
map.put("username", user.getUsername());
String token = jwtUtil.generateToken(map);
loginVo.setToken(token);
return loginVo;
}
}
# application.properties
logging.level.org.springframework.security=DEBUG
logging.level.org.springframework.web=INFO
server.ssl.enabled=true
http.headers(headers -> headers
.contentSecurityPolicy(csp -> csp.policyDirectives("default-src 'self'"))
.httpStrictTransportSecurity(hsts -> hsts.includeSubDomains(true))
);