Краткий способ создания токенов JWE?JAVA

Программисты JAVA общаются здесь
Anonymous
Краткий способ создания токенов JWE?

Сообщение Anonymous »

Итак, я должен написать этот API, где пользователь может зарегистрироваться, а затем получает отправку кода на свою электронную почту. Затем он возвращается, дает API код и зарегистрирован. Поэтому, когда он возвращается, он даст не только API код, но и Cookie. В этот момент я решил взглянуть на JWES и вскоре понял, что на самом деле не нашел ничего легкого в использовании для этого. Затем я начал следить за блогом Шайба Шимшека. У меня было большинство из того, что мне было нужно, но также и гораздо больше, и так как я действительно новичок в веб -разработке в целом, я не совсем понял, и, чтобы быть прозрачным, есть много вещей, которые я не понимаю. Также понимайте большинство его частей, кроме части о JWK, но, поскольку я не нашел ничего подобного моему коду, я не уверен, что это хорошо или имеет ли он потенциал для ошибок или злоупотреблений. Любая обратная связь значительно устойчива, особенно в отношении метода Parse () в jweserivce.
org.projectlombok
lombok
true


me.paulschwarz
spring-dotenv
4.0.0


com.nimbusds
nimbus-jose-jwt
9.41.1


org.springframework.security
spring-security-oauth2-jose

< /code>
  • генерирование RSA-Keys < /li>
    < /ul>
    public static void generateRSAKey() throws Exception {
    KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
    generator.initialize(2048);
    KeyPair keyPair = generator.generateKeyPair();
    System.out.println("---Start Public Key ---");
    System.out.println(Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded()));
    System.out.println("---End Public Key ---");
    System.out.println("---Start Private Key ---");
    System.out.println(Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded()));
    System.out.println("---End Private Key ---");
    }
    < /code>

    application.yaml & .env < /li>
    < /ul>
    spring:
    application:
    name: backend
    datasource:
    url: jdbc:mysql://localhost:3306/backend?createDatabaseIfNotExist=true
    username: root
    password: toor
    jpa:
    show-sql: true
    jwe:
    issuer: backend
    registerRequestTokenExpiration: 600 # = 60 * 10 = 10 Minuten
    accessTokenExpiration: 300 # = 60 * 5 = 5 Minuten
    refreshTokenExpiration: 604800 # = 60 * 60 * 24 * 7 = 7 Tage
    signing:
    key-id: signing-key
    public-key: ${JWT_PUBLIC_SIGNING_KEY}
    private-key: ${JWT_PRIVATE_SIGNING_KEY}
    encryption:
    key-id: encryption-key
    public-key: ${JWT_PUBLIC_ENCRYPTION_KEY}
    private-key: ${JWT_PRIVATE_ENCRYPTION_KEY}
    < /code>

    JWT_PUBLIC_SIGNING_KEY=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1FdI02OHl3DqbLipUY9JW4wg8otcEcIyY+lRtdSRRm52PYR2TFPdmcuhHgY5b/is8WWSAtIo8aZOBN8NGWM/OVhtbdi8PB1jV1HZlUjqPgkbXDDmuUF5y2Fqmx2wv1ktvkkFkcAZdxfapKX8sqsfB0zRz009r+YBnQUIdfI4RKIRVLGzwIp1RPRSgJ4cP5D4hjkGtB3bKGana0NyW6VIYPKAvR0+g4CCBhGW49YLcI32evBa9boeSGGYMJu4MY4fLAQxZ3aHFYtEfAIoaymmPW3DZMM6Cj6BFrl5tMdFqurpoXi1LaADRNhSxKjSNWuiENYfCNgyqEu/62sy6cSrCwIDAQAB
    JWT_PRIVATE_SIGNING_KEY=MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDUV0jTY4eXcOpsuKlRj0lbjCDyi1wRwjJj6VG11JFGbnY9hHZMU92Zy6EeBjlv+KzxZZIC0ijxpk4E3w0ZYz85WG1t2Lw8HWNXUdmVSOo+CRtcMOa5QXnLYWqbHbC/WS2+SQWRwBl3F9qkpfyyqx8HTNHPTT2v5gGdBQh18jhEohFUsbPAinVE9FKAnhw/kPiGOQa0HdsoZqdrQ3JbpUhg8oC9HT6DgIIGEZbj1gtwjfZ68Fr1uh5IYZgwm7gxjh8sBDFndocVi0R8AihrKaY9bcNkwzoKPoEWuXm0x0Wq6umheLUtoANE2FLEqNI1a6IQ1h8I2DKoS7/razLpxKsLAgMBAAECggEAIt3s7hPFzAQ/5M0IYIVwEkGW7LzIHX0PMStlaL9tgMHaqzdOWtzajR/MEQC3UGo7sQJwpgNEXd8PqJUdCDItIN7No9/sPiLFO9gLgQvmtXu2q74L5MJE8YnJgOGwiCW98ST5w85mCrw2rBqQEqEusyWAvignckleXR4vyXjrQbmI0V0h3+8k5NGvw3ijH0Oem77E4U1gk2koR4I/kRGzFkTCPMD/dZ/cJFn2nEBDoiE5l3DBR0XB2mGekRCKKyF1fE/KPKyyNYUyxCcARc1ng4bwnMwF0SSU0jV1zfGaMOUPB8AkQ72/bWdN2qZGjHdvDHFd9KfXjLR2xxqnJcQF4QKBgQDVLoxuaQjhFLeym5rDpruXZ42qyAUXRitNL80OJwRTo7MaxrmWIDenOtvwZA89gMc6EfolOVW11+Tu3c3ri3CkNp683284dT1LANNHm15yOmmaOyTVudK3RzohzYqGDCH/dtU1sycNw66B1YkefJl2WE4KX0HOJNkUtLxstEiJnwKBgQD+/X/gg9sp9svyVctleiDTKbTT+Z5ZvCDf+SySoEBOWEJ2g+dZD5h9UHjrFdT0IdbjJ3h+Da2ORrnrRAtZ/o4hN2+DRPgj+buul+jlpUBGzHcrMuWqIWutnB9lBGaU/p/PL8PZuGcOzeuqYZ/hNFld1sf32HzzEzpA/6YX38T/FQKBgChvpuV+eNewxVNUnpuD8cs7Miz1ubl7btU6PuvzTKfMwjlO/n7SZ5wJ1xQW9qkU1zVVmZwb7v0KEXuZiOinz1rmnV94BjGwU0r5whmwB88k80j09PyxPZ3UHduxkBfzDzPUx4ZjEAJVXrFz3olw2u8OuubOwL2oUaDS03bPD39DAoGBAPFhWadYSqTb6s55pvRl+yLKDVLIrrz1mQ1MnIQkzRsc87WrQdqOJ+Ugw4aPASslyXz5BVgWbKANlbv/ittjXHpXNunF5TxxENjDFwYlO8aJkZnEMaKuxnbvHs0KsG0E6JVB+x1kMbLS/e8pQSb33k01n9CgEBAuuGuFVl6tRpfNAoGBAJGm1IKTnvU6k1b8D865A51Oi8GSkSINh8p05l8g/Rs6/zPBwjydtlaTR2c4FPVwqYfDNUtNyzouZMq8LCQrb/Z/9wml9FVuO7hgI+69yv39D3rYgtPf/IVwpa/RfDX2rK41SdG2J4IVdSJuzJ9vMvPNW8lnlEEKCElMv/6kjUW8
    JWT_PUBLIC_ENCRYPTION_KEY=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr4ZRIxaiUlYi+hpEkgx+O+M1LdXTjQ8h6/JuvhCqGq5c5DeGdNuTctUOWPvlP1U0jsRR55qlhJzLfwgy6S9hAoHPWngM7VCmb6KmwhbPZFiB6UH/GYx2r7CdY5pwCpq2eSIFuSDBHaKcMn0oCxQKHnhjoA2Lx7xdbg2tdRc6WaYvIenQuo6M2eLWHzig5XyIHzloCNfa4aH29uwYLge2Sr2hkJZktDMSJKhVyJCWBg5wnzCOTD0RxfmEGVfUFO9G/xWdPLVgf6JJxsNzSiNHqrd5ZAUVC+ALmJuF50KLk8CcnJtR6DFgl25DXSkT8Kj6inBK1eesJGFU/yvxon1h3wIDAQAB
    JWT_PRIVATE_ENCRYPTION_KEY=MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCvhlEjFqJSViL6GkSSDH474zUt1dONDyHr8m6+EKoarlzkN4Z025Ny1Q5Y++U/VTSOxFHnmqWEnMt/CDLpL2ECgc9aeAztUKZvoqbCFs9kWIHpQf8ZjHavsJ1jmnAKmrZ5IgW5IMEdopwyfSgLFAoeeGOgDYvHvF1uDa11FzpZpi8h6dC6jozZ4tYfOKDlfIgfOWgI19rhofb27BguB7ZKvaGQlmS0MxIkqFXIkJYGDnCfMI5MPRHF+YQZV9QU70b/FZ08tWB/oknGw3NKI0eqt3lkBRUL4AuYm4XnQouTwJycm1HoMWCXbkNdKRPwqPqKcErV56wkYVT/K/GifWHfAgMBAAECggEAFLT4UnbjnGoMA0xhBYfSxpbl2avtoqkD2/WESgjmQHKMg+HnC1cyHGx1HltNLr0LD8KqVPPiDfR/b1OUyfysaaLNxwL9p2uJzwa1vJT7T3+/h7ig8y8me1CPKMZ57E5XM5Anc7EhBzF9UtmABVqOXQzf1+xxLpWSwh579aHRYOzvVu55bLG/bLRfl2WUJ3bINpxtSCyDB/UZJB7xneSfXMCHnfAhCno8BkZbu/RskDnMpM/U8wvDI5mTbWiKonokKCF3s3yYiNrKs7vVbtZJh9T43M8a3ENPcrqDlLJ85Y0/9zCMaCxJt4F1kpRfvUEYbTKkbmoAM6f16IgwBIXSzQKBgQDmcH2lHFPR/UZOL9LumXQNI1RMzA0cvGQSwcDhczR6DieiVRWKKTD6c2ns0y3hzaw2C82OdE6pFo3Ywn7teWUCtkCS+rUsGMoMHC3YhLRxkFOuO/P495ArEFpy8+7UduNmWauz1qg6lJEx9vu+HDB0RaHnMmJ/FZDbVJJoXCrLIwKBgQDC/nqLoqCkfyUa9PiLG74OkIbkSTLv7eiTsLiGYmj5AiGqL+f+/rkZP49d59I9IWi01eQ9nd0V6Vu1cE+WOrJtIoSvPILT+7gFjvOrEqhjXlWIYNZU3p3MKuGvyq49C/B7btdTvivtYZKU3cKj3nnTjrEZFjhSIyfnTFQmXUHoFQKBgBhZeJ1SwfSla6FlYkd+BYpB2m2G/je0HGry+DuaXcgr6Lo5fV4s/hTozx+MLQP4JKNNWfochhdN380wuBLFygugUHB3d19iey8OZzXCyAJb+sulYCFFn4E9aCFPb0QaD+tHvGHzY7FU84axD2bGOcR/ex0f8NJ25+iVJidK3ea7AoGAGJ7vNEBljj+rnLq/wzjOh6JCFgMUFm1wx20x723vmTlmrMl9vpnFH2YCITZLOoLEaMj1F76eEs0zUjaLJgnlS5hnLoUyc7e95Z3GMJybfGiF3kFz7qVpQUVM19h8paKjS8KUF7PUchW232scz3og4dCLlgJTPDPKLw9ZNLrjvn0CgYBCMVd+ONz6/n7Es8zHqNlqAYf+An+DNjijXJ/dFBGIJl+Ncm4DfRT6OAeksyivn9TrPFA5L9KVpcCivwqv9rnupVMYh2b7u2KYuKMha8FPXRjqr/E7eX8jUyeYJ0DAvUx7oVRpgshoqbV/w+8MSqImvB8fF5maGENBhG1u1L/GNQ==
    < /code>

    JweConfig - Builds RSA Keys from applications.yaml / .env and holds Experiation Values
@Configuration
@ConfigurationProperties(prefix = "spring.jwe")
@Getter
@Setter
public class JweConfig {

@Data
public static class Pair {
private String keyId;
private String publicKey;
private String privateKey;
}

private String issuer;
private long registerRequestTokenExpiration;
private long refreshTokenExpiration;
private long accessTokenExpiration;

private Pair signing;
private Pair encryption;

public RSAKey signingKey() throws Exception {
return buildRsaKey(
signing.getPublicKey(),
signing.getPrivateKey(),
signing.getKeyId(),
true
);
}

public RSAKey encryptionKey() throws Exception {
return buildRsaKey(
encryption.getPublicKey(),
encryption.getPrivateKey(),
encryption.getKeyId(),
false
);
}

private RSAKey buildRsaKey(String pubKey, String priKey, String keyId, boolean signing) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] decodedPublicKey = Base64.getDecoder().decode(pubKey);
byte[] decodedPrivateKey = Base64.getDecoder().decode(priKey);

X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(decodedPublicKey);
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(decodedPrivateKey);

RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(publicKeySpec);
RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(privateKeySpec);

RSAKey.Builder builder = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(keyId);

if (signing) {
builder.algorithm(JWSAlgorithm.RS256).keyUse(KeyUse.SIGNATURE);
} else {
builder.algorithm(JWEAlgorithm.RSA_OAEP_256).keyUse(KeyUse.ENCRYPTION);
}
return builder.build();
}

@Bean
public JwtEncoder jwtEncoder() throws Exception {
JWKSet jwkSet = new JWKSet(
List.of(
signingKey(),
encryptionKey()
)
);
JWKSource source = (jwkSelector, context) -> jwkSelector.select(jwkSet);
return new NimbusJwtEncoder(source);
}
}
< /code>
  • Jwe - Representation of Jwes
@AllArgsConstructor
public class Jwe {
private final JwtClaimsSet claims;
private final RSAKey signingKey;
private final RSAKey encryptionKey;
private final JwtEncoder jwtEncoder;

public boolean isExpired() {
return claims.getExpiresAt().isBefore(Instant.now());
}

public String getSubject() {
return claims.getSubject();
}

public T get(String name, Class requiredType) {
Object value = claims.getClaim(name);
if (requiredType.isInstance(value)) {
return requiredType.cast(value);
}
return null;
}

@SneakyThrows
public String toString() {
JwsHeader jwsHeader = JwsHeader
.with(SignatureAlgorithm.RS256)
.keyId(signingKey.getKeyID())
.build();

String jws = jwtEncoder
.encode(JwtEncoderParameters.from(jwsHeader, claims))
.getTokenValue();

JWEHeader jweHeader = new JWEHeader
.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A128GCM)
.contentType("JWT")
.keyID(encryptionKey.getKeyID())
.build();

JWEObject jweObject = new JWEObject(jweHeader, new Payload(jws));
jweObject.encrypt(new RSAEncrypter(encryptionKey.toRSAPublicKey()));

return jweObject.serialize();
}
}
< /code>
  • JweService - Generates Jwes and parses tokens in string format
@Service
@AllArgsConstructor
public class JweService {
private JweConfig config;

public Jwe generateRegisterRequestToken(RegisterUserRequest request, String verificationCode) throws Exception {
var claims = JwtClaimsSet.builder()
.subject(request.getEmail())
.claim("firstName", request.getFirstName())
.claim("lastName", request.getLastName())
.claim("password", request.getPassword())
.claim("code", verificationCode)
.issuer(config.getIssuer())
.issuedAt(Instant.now())
.expiresAt(Instant.ofEpochMilli(System.currentTimeMillis() + 1000 * config.getRegisterRequestTokenExpiration()))
.build();
return new Jwe(
claims,
config.signingKey(),
config.encryptionKey(),
config.jwtEncoder()
);
}

public Jwe parse(String token) {
try {
JWEObject jwe = JWEObject.parse(token);
jwe.decrypt(new RSADecrypter(config.encryptionKey().toRSAPrivateKey()));

JWSObject jwsObject = jwe.getPayload().toJWSObject();
if (jwsObject.verify(new RSASSAVerifier(config.signingKey()))) {
Payload payload = jwsObject.getPayload();
JwtClaimsSet.Builder jwtSetBuilder = JwtClaimsSet.builder();
for (Map.Entry entry: payload.toJSONObject().entrySet()) {
jwtSetBuilder.claim(entry.getKey(), entry.getValue());
}
return new Jwe(
jwtSetBuilder.build(),
config.signingKey(),
config.encryptionKey(),
config.jwtEncoder()
);
}
return null;
} catch (Exception e) {
return null;
}
}
}
< /code>
  • UserController - exposes Endpoints
@RestController
@RequestMapping("/users")
@AllArgsConstructor
public class UserController {
private final JweConfig jwtConfig;
private final JweService jweService;
private final UserRepository userRepository;
private final UserService userService;
private final UserMapper userMapper;
private final PasswordEncoder passwordEncoder;

@PostMapping()
public ResponseEntity registerUser(
@Valid @RequestBody RegisterUserRequest request,
HttpServletResponse response,
UriComponentsBuilder uriBuilder
) throws Exception {
var user = userRepository.findByEmail(request.getEmail()).orElse(null);
if (!(user == null || (user.getRole() == Role.EXTERNAL && request.getPassword() != null))) {
return ResponseEntity.badRequest().body(
Map.of("email", "Email is allready registered.")
);
}
if (user == null) {
user = userMapper.toEntity(request);
}
if (request.getPassword() != null) {
// Generate Random Code
IntStream stream = new Random().ints(6L, 0, 10);
String code = Arrays.toString(stream.toArray());
code = code.replaceAll("[^0-9]", "");
System.out.println("Der Code für die Email lautet: " + code);

// Create Token
var registerToken = jweService.generateRegisterRequestToken(request, code);

// Place Token in Response
var cookie = new Cookie("register_request_token", registerToken.toString());
cookie.setHttpOnly(true);
cookie.setPath("/users");
cookie.setMaxAge((int) jwtConfig.getRegisterRequestTokenExpiration());
cookie.setSecure(true);
response.addCookie(cookie);

// Send Email.
// Maybe badRequest is no fitting
return ResponseEntity.badRequest().body(
Map.of(
"token", registerToken.toString(),
"message", "Wir haben Ihnen eine Email mit einem Bestätigungscode and ihre Emailadresse " +
user.getEmail() + " geschickt."
)
);
}
user.setRole(Role.EXTERNAL);
userRepository.save(user);
var uri = uriBuilder.path("/users/{id}").buildAndExpand(user.getId()).toUri();
return ResponseEntity.created(uri).body(userMapper.toDto(user));
}

@PostMapping("/code")
public ResponseEntity confirmCode(
@Valid @RequestBody ConfirmCodeRequest codeRequest,
@CookieValue(value = "register_request_token") String token,
UriComponentsBuilder uriBuilder
) {
var jwt = jweService.parse(token);
if (jwt == null || jwt.isExpired()) {
return ResponseEntity.badRequest().body(
Map.of("message", "Token expired")
);
}
if (!codeRequest.getCode().matches(jwt.get("code", String.class))) {
return ResponseEntity.badRequest().body(
Map.of("message", "Invalid code")
);
}
var user = userRepository.findByEmail(jwt.getSubject()).orElse(null);
if (user == null) {
user = userMapper.toEntity(jwt);
}
user.setPassword(passwordEncoder.encode(jwt.get("password", String.class)));
userRepository.save(user);
var uri = uriBuilder.path("/users/{id}").buildAndExpand(user.getId()).toUri();
return ResponseEntity.created(uri).body(userMapper.toDto(user));
}

@GetMapping("/{id}")
public ResponseEntity getUser(@PathVariable UUID id) {
var user = userRepository.findById(id).orElse(null);
if (user == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(userMapper.toDto(user));
}

@GetMapping()
public Map getAllUsers() {
return userService.getAllUsers();
}
}
< /code>
  • SecurityConfig - controlls request to Endpoints
@Configuration
@EnableWebSecurity
@AllArgsConstructor
public class SecurityConfig {
private final UserDetailsService userDetailsService;
private final JwtAuthenticationFilter jwtAuthenticationFilter;

@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration config
) throws Exception {
return config.getAuthenticationManager();
}

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

@Bean
public AuthenticationProvider authenticationProvider() {
var provider = new DaoAuthenticationProvider(userDetailsService);
provider.setPasswordEncoder(passwordEncoder());
return provider;
}

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.sessionManagement(c ->
c.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
).csrf(c -> c.disable())
.authorizeHttpRequests(c -> c
.requestMatchers("/users/**").permitAll()
.requestMatchers("/registration/**").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.exceptionHandling(c ->
{
c.authenticationEntryPoint(
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)
);
c.accessDeniedHandler((request, response, accessDeniedException) ->
response.setStatus(HttpStatus.FORBIDDEN.value())
);
}
);
return http.build();
}
}


Подробнее здесь: https://stackoverflow.com/questions/796 ... jwe-tokens

Вернуться в «JAVA»