Skip to content

Commit

Permalink
Merge pull request #9 from hokkung/hok/jwt
Browse files Browse the repository at this point in the history
jwt part1
  • Loading branch information
hokkung authored Apr 5, 2024
2 parents b39c6b6 + 837cbfc commit 13b38af
Show file tree
Hide file tree
Showing 34 changed files with 696 additions and 97 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,5 @@ build/
.vscode/

application-local.properties

*.pem
40 changes: 40 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
<mockito.version>5.3.1</mockito.version>
<mapstruct.version>1.6.0.Beta1</mapstruct.version>
<lombok.version>1.18.32</lombok.version>
<spring.securty-core.version>6.2.3</spring.securty-core.version>
<jwt.version>0.12.5</jwt.version>
</properties>
<dependencies>
<dependency>
Expand All @@ -43,6 +45,14 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
Expand Down Expand Up @@ -76,6 +86,36 @@
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>${spring.securty-core.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${spring.securty-core.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring.securty-core.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jwt.version}</version>
</dependency>
</dependencies>

<build>
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/leo/user/UserApplication.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package com.leo.user;

import com.leo.user.config.RSAKeyProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

@EntityScan("com.leo.user.domain.user")
@EnableConfigurationProperties(RSAKeyProperties.class)
@SpringBootApplication
public class UserApplication {

Expand Down
5 changes: 4 additions & 1 deletion src/main/java/com/leo/user/common/domain/BaseEmbeddedId.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.leo.user.common.domain;

import com.leo.user.repository.user.UserRoleConverter;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.Embeddable;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
Expand All @@ -16,8 +18,9 @@ public class BaseEmbeddedId<J, K> implements Serializable {
@NotNull
private J primary;

@Column(name = "name")
@Column(name = "role_id")
@NotNull
@Convert(converter = UserRoleConverter.class)
private K secondary;

public static <J, K> BaseEmbeddedId<J, K> create(J primary, K secondary) {
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/com/leo/user/common/domain/Name.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
package com.leo.user.common.domain;


import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Embeddable
public class Name {
@NotNull
@Column(name = "firstname")
private String firstName;

@NotNull
@Column(name = "lastname")
private String lastName;

public static Name create() {
return new Name();
}
}
12 changes: 12 additions & 0 deletions src/main/java/com/leo/user/common/security/JwtService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.leo.user.common.security;

import org.springframework.security.core.Authentication;

public interface JwtService {

// String extractUsername(String token);
//
// boolean IsTokenValid(String token, UserDetails userDetails);

String generateToken(Authentication authentication);
}
84 changes: 84 additions & 0 deletions src/main/java/com/leo/user/common/security/JwtServiceImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.leo.user.common.security;

import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.stream.Collectors;


@Service
public class JwtServiceImpl implements JwtService {

@Value("${jwt.expiration-time-in-minute:60}")
private long jwtExpirationTimeInMinute;

@Autowired
@Setter
private JwtDecoder jwtDecoder;

@Autowired
@Setter
private JwtEncoder jwtEncoder;

@Override
public String generateToken(Authentication auth) {
String scope = auth.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(" "));

JwtClaimsSet claims = JwtClaimsSet.builder()
.issuedAt(new Date().toInstant())
.subject(auth.getName())
.claim("roles", scope)
.build();

return jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
}

//
// @Override
// public String extractUsername(String token) {
// return extractClaim(token, Claims::getSubject);
// }
//
// private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
// final Claims claims = extractAllCliams(token);
// return claimsResolver.apply(claims);
// }
//
// private Claims extractAllCliams(String token) {
// return Jwts.parser()
// .verifyWith(getSignInKey())
// .build()
// .parseClaimsJws(token)
// .getPayload();
// }
//
// private SecretKey getSignInKey() {
// byte[] bytes = Decoders.BASE64.decode(jwtSecretKey);
// return Keys.hmacShaKeyFor(bytes);}
//
// @Override
// public boolean IsTokenValid(String token, UserDetails userDetails) {
// final String username = extractUsername(token);
// return (username.equals(userDetails.getUsername())) && !isTokenExpired(token);
// }
//
// private boolean isTokenExpired(String token) {
// return extractExpiration(token).before(new Date());
// }
//
// private Date extractExpiration(String token) {
// return extractClaim(token, Claims::getExpiration);
// }

}
13 changes: 13 additions & 0 deletions src/main/java/com/leo/user/common/util/ClockUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.leo.user.common.util;

import lombok.experimental.UtilityClass;

import java.time.Clock;
import java.util.Date;

@UtilityClass
public class ClockUtils {
public static Date current() {
return Date.from(Clock.systemDefaultZone().instant());
}
}
9 changes: 9 additions & 0 deletions src/main/java/com/leo/user/config/RSAKeyProperties.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.leo.user.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;

@ConfigurationProperties(prefix = "jwt")
public record RSAKeyProperties(RSAPublicKey rsaPublicKey, RSAPrivateKey rsaPrivateKey) {}
101 changes: 101 additions & 0 deletions src/main/java/com/leo/user/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package com.leo.user.config;

import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import lombok.Setter;
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.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
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.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@Setter
public class SecurityConfig {

@Autowired
private RSAKeyProperties rsaKeyProperties;

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

@Bean
public AuthenticationProvider authenticationProvider(
UserDetailsService userDetailsService
) {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());

return daoAuthenticationProvider;
}

@Bean
public AuthenticationManager authenticationManager(
AuthenticationProvider authenticationProvider
) throws Exception {
return new ProviderManager(authenticationProvider);
}

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> {
auth.requestMatchers("api/v1/auth/**").permitAll();
auth.anyRequest().authenticated();
})
.oauth2ResourceServer(configure -> configure.jwt(Customizer.withDefaults()))
.sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

return http.build();
}

@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(rsaKeyProperties.rsaPublicKey()).build();
}

@Bean
public JwtEncoder jwtEncoder() {
JWK jwk = new RSAKey.Builder(rsaKeyProperties.rsaPublicKey()).privateKey(rsaKeyProperties.rsaPrivateKey()).build();
JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
return new NimbusJwtEncoder(jwks);
}

@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter(){
JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
jwtConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
return jwtConverter;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.leo.user.controller.auth;


import com.leo.user.domain.user.User;
import com.leo.user.mapper.user.UserMapper;
import com.leo.user.model.auth.AuthenticationResponseDto;
import com.leo.user.model.auth.LoginResponseDto;
import com.leo.user.model.auth.RegisterRequest;
import com.leo.user.service.auth.AuthenticationService;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1/auth")
@Setter
public class AuthenticationController {

@Autowired
private AuthenticationService authenticationService;

@PostMapping("/register")
public AuthenticationResponseDto register(@RequestBody RegisterRequest request) {
User user = authenticationService.register(request);

return AuthenticationResponseDto.builder().user(UserMapper.INSTANCE.toUserDTO(user)).build();
}

@PostMapping("/login")
public LoginResponseDto login(@RequestBody RegisterRequest request) {
String token = authenticationService.login(request);

return LoginResponseDto.builder().token(token).build();
}
}
Loading

0 comments on commit 13b38af

Please sign in to comment.