소스 전체
https://gitlab.com/kimdongy1000/public_project_amadeus/-/tree/main?ref_type=heads
해당 소스는 민감한 정보를 제외한 순수 코드입니다 사용하실려면 application.yml 에 자신이 필요한 정보를 기입하시면 사용 가능합니다 해당 글을 적는부분과 소스의 올라간 부분은 상당히 많이 다릅니다
프로젝트 구성
JDK - 11 Srping - boot 2.7.1 Srping - Security Spring - resource - server Spring - web mysql React
이렇게 구성이 되었습니다 라이브러리는 이보다 더 많은 라이브러리가 현재 사용중이지만 필요한 것들만 나열했습니다
우리가 만들것은 JWT 토큰을 만들어서 Return 해주는 것입니다
로그인
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Autowired
private Gson gson;
@PostMapping("/user/login")
public ResponseEntity<?> loginUser(
@Valid @RequestBody LoginDto loginDto)
{
try{
String jwtToken = loginService.loginUser(loginDto);
Map<String , Object> result = new HashMap<>();
result.put("JWT_TOKEN" , jwtToken);
String to_json_result = gson.toJson(result);
return new ResponseEntity<>(to_json_result , HttpStatus.OK);
}catch (Exception e){
throw new RuntimeException(e);
}
}
로그인 Controller 은 아이디 비밀번호를 받아서 최종적으로 JWT_TOKEN 을 반환 해주는 것이 목적입니다
Gson Bean 구성
1
2
3
4
5
6
7
8
9
10
@Configuration
public class GsonConfig {
@Bean
public Gson gson(){
return new Gson();
}
}
이때 gson 은 bean 으로 만들어서 사용중입니다 이렇게 하는 이유는 gson 은 하나의 객체로만 움직여도 문제 없을 것이라고 판단해서 이렇게 사용했습니다
loginUser
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public String loginUser(LoginDto loginDto) throws Exception{
String jwtToken = "";
Authentication nonAuthentication = new UsernamePasswordAuthenticationToken(loginDto.getEmail() , loginDto.getPassword());
Authentication authenticatedUser = customAuthenticationService.authenticate(nonAuthentication);
jwtToken = jwtGenerator.jwtGenerator(authenticatedUser , javaWebKey);
return jwtToken;
}
우리는 해당 LoginDto 를 통해서 들어오는 정보를 이용해서 Authentication 작성해서 해당 Authentication 을 이용해서 jwtToken 을 만들어나갈 예정입니다 이때 UsernamePasswordAuthenticationToken 은 Security api 를 활용해서 인증되지 않은 Authentication 을 만들고 이를 활용해서 제가 만든 customAuthenticationService 을 활용해서 인증된 Authentication 객체를 만드는 과정입니다
LoginDto
1
2
3
4
5
6
7
8
9
10
11
12
13
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class LoginDto {
@NotBlank(message = "email 은 필수값입니다.")
private String email;
@NotBlank(message = "password 은 필수값입니다.")
private String password;
}
customAuthenticationService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@Service
public class CustomAuthenticationService implements AuthenticationProvider {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private CustomUserDetailService customUserDetailService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
try{
String username = authentication.getName();
String password = authentication.getCredentials().toString();
UserDetails userDetails = customUserDetailService.loadUserByUsername(username);
if(!passwordEncoder.matches(password , userDetails.getPassword())){
throw new BadCredentialsException("비밀번호가 일치하지 않습니다");
}
UsernamePasswordAuthenticationToken userLoginToken = new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities());
return userLoginToken;
}catch(Exception e){
throw new RuntimeException(e.getMessage());
}
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
제가 커스텀을 한 인증 서비스는 시큐리티의 구현체 AuthenticationProvider 을 활용해서 재정의 구현을 하게 됩니다 이때 사용자 정보는 customUserDetailService 에서 loadByUserName 을 통해서 가져오게 됩니다 그리고 그 정보를 가지고 입력한 password 와 , 저장된 password 를 활용해서 일치여부를 판단하고 인증된 UsernamePasswordAuthenticationToken 을 만들어서 return 을 해줍니다
CustomUserDetailService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Service
public class CustomUserDetailService implements UserDetailsService {
@Autowired
private LoginRepository loginRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Project_Amdeus_User_Dao projectAmdeusUserDao = loginRepository.loadUserByUsername(username);
if(projectAmdeusUserDao == null){
throw new UsernameNotFoundException("회원이 존재하지 않습니다.");
}
/*기본권한은 member*/
return new SecurityUsers(
projectAmdeusUserDao.getUser_email(),
projectAmdeusUserDao.getUser_password(),
Arrays.asList(new SimpleGrantedAuthority("ROLE_MEMBER")),
true ,
true ,
true ,
true
);
}
}
이 부분은 최초 입력된 로그인 아이디를 바탕으로 loadUserByUsername 호출해서 시큐리티 UserDetails 을 만들어줍니다 이때 loginRepository 는 DB 에 저장된 정보를 가지고 최초 데이터를 가져옵니다 (이 부분은 생략)
그래서 최종적으로 return 하는 것은 UserDetails 타입의 정보로서 그 정보는 SecurityUsers 인데 이것은 제가 커스텀을 했습니다
SecurityUsers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@AllArgsConstructor
public class SecurityUsers implements UserDetails {
private final String username;
private final String password;
private final List<GrantedAuthority> authorities;
private final boolean accountNonExpired;
private final boolean accountNonLocked;
private final boolean credentialsNonExpired;
private final boolean enabled;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isAccountNonExpired() {
return this.accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return this.accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return this.credentialsNonExpired;
}
@Override
public boolean isEnabled() {
return this.enabled;
}
}
UserDetails 구현하면 이 클래스도 시큐리티User 로 만들 수 있습니다 이를 활용할 예정입니다 이 부분까지가 입력한 로그인 정보를 바탕으로 UserDetails 를 만들어서 그 정보를 바탕으로 AuthenticationToken 을 만드는 과정이었습니다 다음 포스트에서는 이 정보를 가지고 JWT 토큰을 만드는 과정을 보여드리도록 하겠습니다