728x90
반응형
목표
- 회원가입 기능
- 로그인 기능
- 로그인 시 JWT 토큰 발급
- JWT 인증, 인가 필터 생성
- JWT의 회원 정보 가져오기
환경
- java 17
- spring boot 2.7
- gradle 7.5
환경 설정
1. build.gradle
- Web, Security, Lombok, JPA, mysql-connector, jwt 의존성을 추가합니다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
// JWT
implementation 'com.auth0:java-jwt:4.2.0'
}
2. application.yml
server:
port: 8080
servlet:
context-path: /
encoding:
charset: UTF-8
enabled: true
force: true
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/dbname?serverTimezone=Asia/Seoul
username: username #유저네임
password: password #암호
jpa:
hibernate:
ddl-auto: update #create update none
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
show-sql: true
# 시크릿 키값
jwt:
secret: "Vwgff4uvzQ4pes0Zt7sDNtL6pxGIkg2k95ZIrVhvlGmxcDRq9O1fnLN2lEzItsNE4w_lQ3f7xd09ukYxzIYS6InrYfg4ed2BSP0wmZ2RJEswzDsCLNqwRRXW780o1TYbuQpiXuUN0TnwXzb2l4YnNcXLHyBBJoIU17y1H56Aq1-ABW6MREvcFvlW-oUcMw92R0piQK4hO_Xo8AFIDnbeAqQUQ2Q91iQZRTtiNrV9Gv_pF_f1LF9OLDnvmTTy7Av7yFRstie90G9ABYsFTrxywHLzA-QMDYOeUOk8wq6TfxKbULK8PqWR__s1ebFlA3bFO1shhUdffA"
- JpaConfig
@EnableJpaAuditing
public class JpaConfig {
}
- SecurityConfig
@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
public class SecurityConfig {
// 회원의 패스워드 암호화
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
// 시큐리티 필터 설정
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors().disable()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.formLogin().disable()
.httpBasic().disable() // http의 기본 인증. ID, PW 인증방식
.authorizeHttpRequests()
.anyRequest().permitAll();
return http.build();
}
}
Entity 생성 및 Repository 생성
- User
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String username;
private String email;
private String password;
private String roles; // USER, ADMIN
private LocalDateTime createdDate;
public List<String> getRolesList(){
if(this.roles.length() > 0){
return Arrays.asList(this.roles.split(","));
}
return new ArrayList<>();
}
}
- UserRepository
JPA를 이용해 User 테이블에 접근할 수 있는 인터페이스를 생성합니다.
public interface UserRepository extends JpaRepository<User, Long> {
User findByEmail(String email);
}
회원가입 기능 개발
DTO 객체 생성
- JoinRequestDto
회원 가입 시 요청될 객체를 생성합니다.
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class JoinRequestDto {
private String email;
private String username;
private String password;
}
contorller
@RestController
@RequestMapping
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@PostMapping("/api/join")
public String join(@RequestBody JoinRequestDto joinRequestDto) {
return userService.join(joinRequestDto);
}
}
service
로그인 기능 개발
DTO 객체 생성
- LoginRequestDto
로그인 시 요청될 객체를 생성합니다.
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class LoginRequestDto {
private String email;
private String password;
}
contorller
@RestController
@RequestMapping
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@PostMapping("/api/join")
public String join(@RequestBody JoinRequestDto joinRequestDto) {
return userService.join(joinRequestDto);
}
@PostMapping("/api/login")
public String login(@RequestBody LoginRequestDto loginRequestDto) {
return userService.login(loginRequestDto);
}
}
service
@Service
@RequiredArgsConstructor
public class UserService {
private final BCryptPasswordEncoder passwordEncoder;
private final UserRepository userRepository;
public String join(JoinRequestDto joinRequestDto) {
// ...
return "회원가입";
}
public String login(LoginRequestDto loginRequestDto) {
String email = loginRequestDto.getEmail();
String rawPassword = loginRequestDto.getPassword();
User byEmail = userRepository.findByEmail(email);
// 비밀번호 일치 여부 확인
if(passwordEncoder.matches(rawPassword, byEmail.getPassword())){
return "로그인 성공";
}
return "로그인 실패";
}
}
로그인 시 JWT 토큰 발급
JwtProvider
JWT 라이브러리를 활용해 Provider를 생성하여 JWT 토큰 관련 메소드를 관리합니다.
토큰 생성(generate)
유효성 검증(valid)
@RequiredArgsConstructor
public class JwtProvider {
private final UserRepository userRepository;
static Long EXPIRE_TIME = 60L * 60L * 1000L; // 만료 시간 1시간
@Value("${jwt.secret}")
private String secretKey;
private Algorithm getSign(){
return Algorithm.HMAC512(secretKey);
}
//객체 초기화, secretKey를 Base64로 인코딩한다.
@PostConstruct
protected void init() {
this.secretKey = Base64.getEncoder().encodeToString(this.secretKey.getBytes());
}
// Jwt 토큰 생성
public String generateJwtToken(Long id, String email, String username){
Date tokenExpiration = new Date(System.currentTimeMillis() + (EXPIRE_TIME));
String jwtToken = JWT.create()
.withSubject(email) //토큰 이름
.withExpiresAt(tokenExpiration)
.withClaim("id", id)
.withClaim("email", email)
.withClaim("username", username)
.sign(this.getSign());
return jwtToken;
}
/**
* 토큰 검증
* - 토큰에서 가져온 email 정보와 DB의 유저 정보 일치하는지 확인
* - 토큰 만료 시간이 지났는지 확인
* @param jwtToken
* @return 유저 객체 반환
*/
public User validToken(String jwtToken){
try {
String email = JWT.require(this.getSign())
.build().verify(jwtToken).getClaim("email").asString();
// 비어있는 값이다.
if (email == null){
return null;
}
// EXPIRE_TIME이 지나지 않았는지 확인
Date expiresAt = JWT.require(this.getSign()).acceptExpiresAt(EXPIRE_TIME).build().verify(jwtToken).getExpiresAt();
if (!this.validExpiredTime(expiresAt)) {
// 만료시간이 지났다.
return null;
}
User tokenUser = userRepository.findByEmail(email);
return tokenUser;
} catch (Exception e){
e.printStackTrace();
return null;
}
}
// 만료 시간 검증
private boolean validExpiredTime(Date expiresAt){
// LocalDateTime으로 만료시간 변경
LocalDateTime localTimeExpired = expiresAt.toInstant().atZone(ZoneId.of("Asia/Seoul")).toLocalDateTime();
// 현재 시간이 만료시간의 이전이다
return LocalDateTime.now().isBefore(localTimeExpired);
}
}
SecurityConfig
JwtProvider를 Bean으로 등록합니다.
public class SecurityConfig {
private final UserRepository userRepository;
@Bean
public JwtProvider jwtTokenProvider() {
return new JwtProvider(userRepository);
}
// ...
}
service
로그인 성공 시 JWT 토큰을 생성하여 반환해줍니다.
public String login(LoginRequestDto loginRequestDto) {
String email = loginRequestDto.getEmail();
String rawPassword = loginRequestDto.getPassword();
User byEmail = userRepository.findByEmail(email);
// 비밀번호 일치 여부 확인
if(passwordEncoder.matches(rawPassword, byEmail.getPassword())){
// JWT 토큰 반환
String jwtToken = jwtProvider.generateJwtToken(byEmail.getId(), byEmail.getEmail(), byEmail.getUsername());
return "로그인 성공 " + jwtToken;
}
return "로그인 실패";
}
이것으로 JWT를 발급하는 로그인 기능을 완성했습니다.
다음은 JWT의 인증, 인가 필터를 만들고 JWT에서 유저정보를 반환하는 기능을 개발합니다.
2022.10.28 - [Spring/Security] - [JWT] JWT 인증, 인가 필터 생성 / 회원 정보 가져오기 / 인증 객체로 로그인 처리 (2)
함께보면 도움되는 글
2022.10.21 - [Spring/Security] - [Security] 스프링 시큐리티의 아키텍처(구조) 및 흐름
2022.09.05 - [Spring/Security] - [Security] WebSecurityConfigurerAdapter Deprecated
파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음
반응형
'Spring > Security' 카테고리의 다른 글
[Security] 스프링 시큐리티의 아키텍처(구조) 및 흐름 (0) | 2022.11.01 |
---|---|
[JWT] JWT 인증, 인가 필터 생성 / 회원 정보 가져오기 / 인증 객체로 로그인 처리 (2) (0) | 2022.10.28 |
[Security] WebSecurityConfigurerAdapter Deprecated (0) | 2022.09.05 |
Spring Security 기능을 제거하는 간단한 방법 (0) | 2021.09.16 |
[Spring Security] defaultSuccessUrl, successForwardUrl, successHandler (0) | 2021.09.01 |