game user 추가하기

최준호·2022년 4월 8일
0

game

목록 보기
4/14
post-thumbnail

🔨테이블 추가

CREATE TABLE `game_users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(10) NOT NULL,
  `name` varchar(20) NOT NULL,
  `pw` varchar(150) NOT NULL,
  PRIMARY KEY (`id`)
)

game users 테이블을 먼저 생성해주었다. 물론 로컬 테스트는 jpa가 자동으로 생성해주지만 까먹지 않게 미리미리!

🔨Entity 추가

@Entity
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Table(name = "game_users")
public class GameUserEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;

    @Column(name = "user_id", nullable = false, unique = true, length = 10)
    private String userId;
    @Column(nullable = false, length = 20)
    private String name;
    @Column(nullable = false, length = 150)
    private String pw;

    @CreationTimestamp
    private LocalDateTime createdAt = LocalDateTime.now();

    @Builder
    public GameUserEntity(@NonNull String userId,@NonNull String name,@NonNull String pw) {
        this.userId = userId;
        this.name = name;
        this.pw = pw;
    }
}

entity는 다음과 같이 추가해주고 로직을 만들어보자!

🔨로직 추가하기

👉Repository

@Repository
public interface GameUserRepository extends CrudRepository<GameUserEntity, Long> {
}

👉Service

@Getter
@AllArgsConstructor
public class ResponseGameUser {
    String userId;
}

Response Vo를 만들어주고

public interface GameUserService {
    ResponseGameUser join();
}

interface만 우선 선언해주자.

👉Controller

@Getter
@AllArgsConstructor
@NoArgsConstructor
public class RequestGameUser {
    @NotEmpty(message = "아이디는 비어있을 수 없습니다.")
    @Length(min = 4, max = 12, message = "아이디는 4~12 글자입니다.")
    private String userId;
    @NotEmpty(message = "비밀번호는 비어있을 수 없습니다.")
    @Length(min = 6, max = 12, message = "비밀번호는 6~12 글자입니다.")
    private String pw;
    @NotEmpty(message = "비밀번호 재입력은 비어있을 수 없습니다.")
    @Length(min = 6, max = 12, message = "비밀번호는 6~12 글자입니다.")
    private String rePw;
    @NotEmpty(message = "이름은 비어있을 수 없습니다.")
    private String name;
}

매개변수로 받을 객체를 먼저 생성해주고

public interface GameUserService {
    ResponseGameUser join(RequestGameUser requestGameUser);
}

GameUserService도 수정해준다.

@RestController
@RequestMapping("/game")
@RequiredArgsConstructor
@Slf4j
public class GameUserController {
    private final GameUserService gameUserService;

    @PostMapping("/join")
    public ResponseEntity<CommonApi> join(@RequestBody RequestGameUser requestGameuser, BindingResult bindingResult){
        if(bindingResult.hasErrors()){
            log.error("game user 회원가입 잘못된 요청");
            throw new UserException(UserCode.BAD_REQUEST, bindingResult);
        }
        
        ResponseGameUser user = gameUserService.join(requestGameuser);
        CommonApi<Object> response = new CommonApi(CommonEnum.OK, user);
        return ResponseEntity.status(HttpStatus.CREATED).body(response);
    }
}

controller의 경우 다음과 같이 작성해주고 bindingResult의 경우 service로 넘겨서 해결해도 되지만 service에서는 서비스 로직만 신경쓰고 싶어서 다음과 같이 작성했다.

👉ServiceImpl

@Service
@RequiredArgsConstructor
public class GameUserServiceImpl implements GameUserService{
    private final BCryptPasswordEncoder passwordEncoder;
    private final GameUserRepository gameUserRepository;

    @Override
    public ResponseGameUser join(RequestGameUser requestGameUser) {

        GameUserEntity gameUserEntity = GameUserEntity.builder()
                .userId(requestGameUser.getUserId())
                .pw(passwordEncoder.encode(requestGameUser.getPw()))
                .name(requestGameUser.getName())
                .build();

        GameUserEntity save = gameUserRepository.save(gameUserEntity);
        ResponseGameUser responseGameUser = new ResponseGameUser(save.getUserId());

        return responseGameUser;
    }
}

우선은 예외처리 없이 작성해보았다.

포스트맨으로 요청시 잘 응답이 온다.


db에도 잘 저장되었다.

여기서 요청하면서 알았다. rePw을 빼먹고 보냈는데 bindingResult에 걸리지 않았다. 왜지? 당연히 @Valid를 추가하지 않아서지

    @PostMapping("/join")
    public ResponseEntity<CommonApi> join(@RequestBody @Valid RequestGameUser requestGameuser, BindingResult bindingResult){
        ...
    }

@RequestBody 옆에 @Valid를 추가해주자

다시 그대로 요청하면

이전에 만들었던 Exception 대로 응답이 온다.

🔨Security 수정

회원가입까진 상관 없지만 로그인 했을 경우 jwt를 반환해주어야한다. api-key로 요청했을 경우 해당 api-key가 어떤 서비스에서 요청되는건지 확인하여 해당 서비스에 맞는 회원의 정보를 jwt로 반환해주도록 해보자.

👉filter 추가

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurity extends WebSecurityConfigurerAdapter {
    private final UserService userService;
    private final GameUserService gameUserService;
    private final BCryptPasswordEncoder passwordEncoder;
    private final Environment env;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();

        http.authorizeRequests().antMatchers("/join").permitAll();
        http.authorizeRequests().antMatchers("/login").permitAll();

        /*
         game user 회원 로그인 요청 및 jwt 발급
         */
        http.authorizeRequests().antMatchers("/game").permitAll()
        .and().addFilter(getAuthenticationGameFilter());

        http.authorizeRequests().antMatchers("/**").permitAll()
        .and().addFilter(getAuthenticationFilter()) //filter 추가
        ;

        http.headers().frameOptions().disable();    //h2 error
    }

    private GameAuthFilter getAuthenticationGameFilter() throws Exception {
        GameAuthFilter auth = new GameAuthFilter(gameUserService, env);
        auth.setFilterProcessesUrl("/game/login");
        auth.setAuthenticationManager(authenticationManager());

        return auth;
    }

    //인증 service 등록
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder);
        auth.userDetailsService(gameUserService).passwordEncoder(passwordEncoder);
    }
}

다음과 같이 Seurity config 파일을 수정해주면 엄청난 오류를 뿜기 시작할건데 하나씩 수정해보자.

👉GameAuthFilter

@Slf4j
@RequiredArgsConstructor
public class GameAuthFilter extends UsernamePasswordAuthenticationFilter {
    private final GameUserService gameUserService;
    private final Environment env;

    //로그인 요청
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        try {
            RequestLogin login = new ObjectMapper().readValue(request.getInputStream(), RequestLogin.class);

            //인증정보 생성
            return getAuthenticationManager()
                    .authenticate(
                            new UsernamePasswordAuthenticationToken(
                                    login.getUserId(),
                                    login.getPw(),
                                    new ArrayList<>() //권한
                            )
                    );
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    //로그인 성공
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        GameUserVo user = gameUserService.getUserDetailByUserId(((User) authResult.getPrincipal()).getUsername());
        log.info("로그인 성공 = {}",user.toString());
        String token = Jwts.builder()
                .setSubject(user.getUserId())
                .setExpiration(new Date(System.currentTimeMillis() + Long.parseLong(env.getProperty("token.expiration_time")))) //파기일
                .signWith(SignatureAlgorithm.HS512, env.getProperty("token.secret"))    //암호화 알고리즘과 암호화 키값
                .compact();

        response.setHeader("token", token);
    }
}

filter로 추가하고 service를 수정해주자.

@Repository
public interface GameUserRepository extends CrudRepository<GameUserEntity, Long> {
    GameUserEntity findByUserId(String userId);
}

repository에 우선 추가해주고

public interface GameUserService extends UserDetailsService {
    ResponseGameUser join(RequestGameUser requestGameUser);

    GameUserVo getUserDetailByUserId(String userId);
}

service에는 getUserDetailByUserId() 메서드를 추가해준 뒤 extends UserDetailsService를 security에서 사용할 수 있기 위해 추가해주자.

@Service
@RequiredArgsConstructor
public class GameUserServiceImpl implements GameUserService{
    private final BCryptPasswordEncoder passwordEncoder;
    private final GameUserRepository gameUserRepository;

    @Override
    public ResponseGameUser join(RequestGameUser requestGameUser) {

        GameUserEntity gameUserEntity = GameUserEntity.builder()
                .userId(requestGameUser.getUserId())
                .pw(passwordEncoder.encode(requestGameUser.getPw()))
                .name(requestGameUser.getName())
                .build();

        GameUserEntity save = gameUserRepository.save(gameUserEntity);
        ResponseGameUser responseGameUser = new ResponseGameUser(save.getUserId());

        return responseGameUser;
    }

    @Override
    public GameUserVo getUserDetailByUserId(String userId) {
        GameUserEntity userEntity = gameUserRepository.findByUserId(userId);
        
        GameUserVo gameUserVo = GameUserVo.builder()
                .userId(userEntity.getUserId())
                .name(userEntity.getName())
                .pw(userEntity.getPw())
                .createdAt(userEntity.getCreatedAt())
                .build();

        return gameUserVo;
    }

    @Override
    public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException {
        GameUserEntity user = gameUserRepository.findByUserId(userId);
        if(user == null) throw new UsernameNotFoundException("user가 존재하지 않습니다.");

        return new User(user.getUserId(), user.getPw(), true, true, true, true, new ArrayList<>());
    }
}

다음과 같이 모두 구현해주고

포스트맨으로 테스트해보자

game의 경우 /game/login 으로 요청해야만 작동한다.

api key 발급을 위한 로그인 서비스도 정상적으로 반환하는 것을 확인할 수 있다!

진행하면서 .setFilterProcessesUrl()를 모르는 상태로 진행해서 모든 요청이 새로운 필터로 요청되는 에러가 있었다. 모든 소스를 다 롤백해야되는 줄 알고 힘들 뻔 했는데 다행히 해당 메서드로 login 요청 url과 작동 filter를 구분할 수 있어서 잘 해결할 수 있었던거 같다!

profile
코딩을 깔끔하게 하고 싶어하는 초보 개발자 (편하게 글을 쓰기위해 반말체를 사용하고 있습니다! 양해 부탁드려요!) 현재 KakaoVX 근무중입니다!

0개의 댓글