spring-boot-starter-oauth2-resource-server
package com.example.demo.api.component;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
@Service
public class JwtService {
private final JwtEncoder jwtEncoder;
public JwtService(JwtEncoder jwtEncoder) {
this.jwtEncoder = jwtEncoder;
}
public String generateToken(String username) {
Instant now = Instant.now();
Instant expiry = now.plus(1, ChronoUnit.HOURS); // 1 小时过期
JwtClaimsSet claims = JwtClaimsSet.builder()
.subject(username)
.claim("name", username)
.claim("roles", List.of("USER"))
.issuedAt(now)
.expiresAt(expiry)
.build();
return jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
}
}
package com.example.demo.api.component;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.io.FileOutputStream;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.util.Base64;
@Component
public class KeyGenerator {
private final static Logger logger = LoggerFactory.getLogger(KeyGenerator.class);
@PostConstruct
public void generateKeys() throws Exception {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyPair keyPair = keyGen.generateKeyPair();
// 保存私钥
try (FileOutputStream fos = new FileOutputStream("src/main/resources/private.key")) {
fos.write("-----BEGIN PRIVATE KEY-----\n".getBytes());
fos.write(Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded()).getBytes());
fos.write("\n-----END PRIVATE KEY-----\n".getBytes());
}
// 保存公钥
try (FileOutputStream fos = new FileOutputStream("src/main/resources/public.key")) {
fos.write("-----BEGIN PUBLIC KEY-----\n".getBytes());
fos.write(Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded()).getBytes());
fos.write("\n-----END PUBLIC KEY-----\n".getBytes());
}
logger.info("公钥私钥生成完毕!");
}
}
package com.example.demo.api.config;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import java.io.InputStream;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.List;
@Configuration
public class JwtConfig {
@Bean
public JwtEncoder jwtEncoder() {
try {
// 1. 读取私钥文件(PKCS#8 格式)
ClassPathResource resource = new ClassPathResource("private.key");
InputStream is = resource.getInputStream();
byte[] keyBytes = is.readAllBytes();
is.close();
String privateKeyPem = new String(keyBytes)
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s", "");
byte[] decoded = Base64.getDecoder().decode(privateKeyPem);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decoded);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey privateKey = kf.generatePrivate(spec);
// 2. 读取公钥文件(用于 JWK)
ClassPathResource pubResource = new ClassPathResource("public.key");
InputStream pubIs = pubResource.getInputStream();
byte[] pubKeyBytes = pubIs.readAllBytes();
pubIs.close();
String publicKeyPem = new String(pubKeyBytes)
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s", "");
byte[] pubDecoded = Base64.getDecoder().decode(publicKeyPem);
X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(pubDecoded);
PublicKey publicKey = kf.generatePublic(pubSpec);
// 3. 构建 RSAKey(用于 JWK)
RSAKey rsaKey = new RSAKey.Builder((java.security.interfaces.RSAPublicKey) publicKey)
.privateKey(privateKey)
.build();
// 4. 创建 JWK Source
JWKSource<SecurityContext> jwkSource = (keySelectorParameters, context) -> List.of(rsaKey);
// 5. 返回 JwtEncoder
return new NimbusJwtEncoder(jwkSource);
} catch (Exception e) {
throw new RuntimeException("无法加载私钥或公钥", e);
}
}
}
package com.example.demo.api.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
@Configuration
@EnableWebFluxSecurity
public class SecurityConfiguration {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http
.authorizeExchange(authorize ->
authorize.pathMatchers("/auth/login").permitAll()
.pathMatchers("/api/**").authenticated()
.anyExchange().denyAll()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.jwtDecoder(jwtDecoder()))
)
.build();
}
@Bean
public ReactiveJwtDecoder jwtDecoder() {
return NimbusReactiveJwtDecoder.withPublicKey(loadPublicKey()).build();
}
private RSAPublicKey loadPublicKey() {
try (InputStream is = new FileInputStream("src/main/resources/public.key")) {
ClassPathResource resource = new ClassPathResource("public.key");
byte[] keyBytes = resource.getInputStream().readAllBytes();
String publicKeyPem = new String(keyBytes)
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s", "");
byte[] decoded = Base64.getDecoder().decode(publicKeyPem);
X509EncodedKeySpec spec = new X509EncodedKeySpec(decoded);
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey publicKey = kf.generatePublic(spec);
// 强制转换为 RSAPublicKey
return (RSAPublicKey) publicKey;
} catch (Exception e) {
throw new RuntimeException("Failed to load public key", e);
}
}
}