Spring Security 로그인 인증 코드 개선

썸머·2023년 4월 5일

기존 코드

  • controller
//로그인
    @PostMapping("/user/login")
    public ApiResponse login(@RequestBody Map<String, String> user, HttpServletResponse response) {

        if (empty(userMapper.getUser(user.get("id")))) {
            throw new IllegalStateException("아이디 또는 비밀번호가 맞지 않습니다");
        }
        User user2 = userMapper.getUser(user.get("id"));
        if (!passwordEncoder.matches(user.get("password"), user2.getPassword())) {
            throw new IllegalStateException("아이디 또는 비밀번호가 맞지 않습니다");
        }

        userService.login(user2, response);

        return ApiResponse.createSuccessWithNoContent();
    }
  • 쿼리를 따로 작성하여 인증 진행한다 -> 코드의 간결성과 유지보수성이 떨어진다

개선 방향

  • Spring Security에서 제공하는 authenticationManagerBuilder.getObject()authenticate함수를 사용하여 사용자 인증을 진행한다.

오류

{
    "status": "error",
    "data": null,
    "message": "사용자 계정이 잠겨 있습니다"
}

오류 해결

  • UserDetails인터페이스 를 구현는 User클래스
@Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
  • isAccountNonExpired() : 계정의 만료 여부를 반환합니다.

  • isAccountNonLocked() : 계정이 잠겨 있는지 여부를 반환합니다.

  • isCredentialsNonExpired() : 사용자 자격 증명(비밀번호 등)의 만료 여부를 반환합니다.

  • isEnabled() : 계정의 활성화 여부를 반환합니다.

  • 이러한 메소드를 적절히 구현하여 보안을 강화할 수 있다.

오류원인: 기존에 false로 되어있는걸 바꾸지 않았다.

  • 보안 메소드를 사용하지 않는 경우는 true로 return

  • 왜냐하면, check 함수로 true인지 false인지 체크해 false이면 오류를 발생시킨다.

    private class DefaultPreAuthenticationChecks implements UserDetailsChecker {
      
      		@Override
      		public void check(UserDetails user) {
      			if (!user.isAccountNonLocked()) {
      				AbstractUserDetailsAuthenticationProvider.this.logger
      						.debug("Failed to authenticate since user account is locked");
      				throw new LockedException(AbstractUserDetailsAuthenticationProvider.this.messages
      						.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"));
      			}
      			if (!user.isEnabled()) {
      				AbstractUserDetailsAuthenticationProvider.this.logger
      						.debug("Failed to authenticate since user account is disabled");
      				throw new DisabledException(AbstractUserDetailsAuthenticationProvider.this.messages
      						.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
      			}
      			if (!user.isAccountNonExpired()) {
      				AbstractUserDetailsAuthenticationProvider.this.logger
      						.debug("Failed to authenticate since user account has expired");
      				throw new AccountExpiredException(AbstractUserDetailsAuthenticationProvider.this.messages
      						.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"));
      			}
      		}
      
      	}

개선코드

  • controller
//로그인
    @PostMapping("/user/login")
    public ApiResponse login(@RequestBody Map<String, String> user, HttpServletResponse response) {
        userService.login(user, response);
        return ApiResponse.createSuccessWithNoContent();
    }
  • service
//로그인
    public void login(Map user, HttpServletResponse response){
        // 1. Login ID/PW 를 기반으로 Authentication 객체 생성
        // 이때 authentication 는 인증 여부를 확인하는 authenticated 값이 false
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.get("id"), user.get("password"));
        // 2. 실제 검증 (사용자 비밀번호 체크)이 이스프링 부트 내부 함수 코드 보기루어지는 부분
        // authenticate 매서드가 실행될 때 CustomUserDetailsService 에서 만든 loadUserByUsername 메서드가 실행
        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
        System.out.println("autentication " + authentication);

        String accessToken = jwtTokenProvider.createToken(authentication.getName(), authentication.getAuthorities().toString());
        String refreshToken = jwtTokenProvider.createRefreshToken(authentication.getName(), authentication.getAuthorities().toString());
        jwtTokenProvider.setHeaderAccessToken(response, accessToken);
        
        userMapper.saveRefreshToken(refreshToken, authentication.getName());
    }
profile
썸머의 개발블로그

0개의 댓글