Spring Secuirty 37 React - Spring Security JWT 를 이용한 JWT 토큰 발급 1
포스트
취소

Spring Secuirty 37 React - Spring Security JWT 를 이용한 JWT 토큰 발급 1

소스 전체

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 토큰을 만드는 과정을 보여드리도록 하겠습니다