사붐이개발일기

[JWT] Security Authentication in user-service 본문

인프런/[Spring Cloud] MSA-이도원

[JWT] Security Authentication in user-service

sabeom 2023. 12. 5. 10:36

 

vo.RequestLogin

@Data
public class RequestLogin {
    @NotNull(message = "Email cannot be null")
    @Size(min = 2, message = "Email not be less than two characters")
    @Email
    private String email;
    @NotNull(message = "Password cannot be null")
    @Size(min = 8, message = "Password not be equals or grater than 8 characters")
    private String password;
}

 

dto.UserDto

@Data
public class UserDto {
    private String email;
    private String name;
    private String pwd;
    private String userId;
    private Date createdAt;

    private String encryptedPwd;

    private List<ResponseOrder> orders;
}

 

jpa.UserEntity

@Data
@Entity
@Table(name = "users")
public class UserEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(nullable = false, length = 50, unique = true)
    private String email;
    @Column(nullable = false, length = 50)
    private String Name;
    @Column(nullable = false, unique = true)
    private String userId;
    @Column(nullable = false, unique = true)
    private String encryptedPwd;
}

 

jpa.UserRepository

public interface UserRepository extends CrudRepository<UserEntity, Long> {
    UserEntity findByUserId(String userId);
    UserEntity findByEmail(String username);
}

 

controller.UserController

@RestController
@RequestMapping("/")
public class UserController {
    private Environment env;
    private UserService userService;

    @Autowired
    public UserController(Environment env, UserService userService)
    {
        this.env = env;
        this.userService = userService;
    }

    @PostMapping("/users")
    public ResponseEntity<ResponseUser> createUser(@RequestBody RequestUser user) {
        ModelMapper mapper = new ModelMapper();
        mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);

        UserDto userDto = mapper.map(user, UserDto.class);
        userService.createUser(userDto);

        ResponseUser responseUser = mapper.map(userDto, ResponseUser.class);

        return ResponseEntity.status(HttpStatus.CREATED).body(responseUser);
    }

    @GetMapping("/users")
    public ResponseEntity<List<ResponseUser>> getUsers() {
        Iterable<UserEntity> userList = userService.getUserByAll();

        List<ResponseUser> result = new ArrayList<>();
        userList.forEach(v->{
            result.add(new ModelMapper().map(v, ResponseUser.class));
        });

        return ResponseEntity.status(HttpStatus.OK).body(result);
    }
    @GetMapping("/users/{userId}")
    public ResponseEntity<ResponseUser> getUsers(@PathVariable("userId") String userId) {
        UserDto userDto = userService.getUserByUserId(userId);

            ResponseUser returnValue= new ModelMapper().map(userDto, ResponseUser.class);

        return ResponseEntity.status(HttpStatus.OK).body(returnValue);
    }
}

 

service.UserService

public interface UserService extends UserDetailsService {
    UserDto createUser(UserDto userDto);

    UserDto getUserByUserId(String userId);
    Iterable<UserEntity> getUserByAll();

    UserDto getUserDetailsByEmail(String userName);
}

 

service.UserServiceImpl

@Service
public class UserServiceImpl implements UserService {
    UserRepository userRepository;
    BCryptPasswordEncoder passwordEncoder;

    @Autowired
    public UserServiceImpl(UserRepository userRepository, BCryptPasswordEncoder passwordEncoder) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserEntity userEntity = userRepository.findByEmail(username);

        if(userEntity == null)
            throw new UsernameNotFoundException(username);

        return new User(userEntity.getEmail(), userEntity.getEncryptedPwd(), true, true, true, true, new ArrayList<>());
    }

    @Override
    public UserDto createUser(UserDto userDto) {
        userDto.setUserId(UUID.randomUUID().toString());

        // dto -> entity 객체로 변환 시키기 위한 객체
        ModelMapper mapper = new ModelMapper();
        // modelmapper가 변환할 수 있는 환경설정 설정정보 지정 (strategy는 딱 맞아 떨어지지않으면 지정이 안됨.)
        mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
        // 변환 작업
        UserEntity userEntity = mapper.map(userDto, UserEntity.class);
        userEntity.setEncryptedPwd(passwordEncoder.encode(userDto.getPwd()));

        userRepository.save(userEntity);
        UserDto returnUserDto = mapper.map(userEntity, UserDto.class);

        return returnUserDto;
    }

    @Override
    public UserDto getUserByUserId(String userId) {
        UserEntity userEntity = userRepository.findByUserId(userId);

        if (userEntity == null)
            throw new UsernameNotFoundException("User not found");

        UserDto userDto = new ModelMapper().map(userEntity, UserDto.class);

        List<ResponseOrder> orders = new ArrayList<>();
        userDto.setOrders(orders);

        return userDto;
    }

    @Override
    public Iterable<UserEntity> getUserByAll() {
        return userRepository.findAll();
    }

    @Override
    public UserDto getUserDetailsByEmail(String email) {
        UserEntity userEntity = userRepository.findByEmail(email);

        if(userEntity == null)
            throw new UsernameNotFoundException(email);

        UserDto userDto = new ModelMapper().map(userEntity, UserDto.class);
        return userDto;
    }
}

 

security.WebSecurity

@Configuration
@EnableWebSecurity
public class WebSecurity {
    private static final String IP_ADDRESS = "59.10.164.35";
    private static final String SUBNET = "/32";
    private static final IpAddressMatcher IP_ADDRESS_MATCHER = new IpAddressMatcher(IP_ADDRESS + SUBNET);
    private static final String[] WHITE_LIST = {
            "/**",
            "/users/**"
    };
    private Environment env;
    private UserService userService;

    public WebSecurity(Environment env, UserService userService) {
        this.env = env;
        this.userService = userService;
    }

    // IP 접근제한
    private AuthorizationDecision hasIpAddress(Supplier<Authentication> authentication, RequestAuthorizationContext object) {
        return new AuthorizationDecision(IP_ADDRESS_MATCHER.matches(object.getRequest()));
    }

//    @Bean
//    PasswordEncoder passwordEncoder() {
//        return new BCryptPasswordEncoder();
//    }
    @Bean
    AuthenticationManager authenticationManager(
            AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    @Bean
    protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf(csrf -> csrf.disable())
                .headers(headers -> headers
                        .frameOptions(frameOptions -> frameOptions.disable())
                );
        http.authorizeHttpRequests(authorize -> authorize
//                        .requestMatchers(AntPathRequestMatcher.antMatcher("/users/**")).permitAll()
                        .requestMatchers(AntPathRequestMatcher.antMatcher("/**")).access(this::hasIpAddress)    // IP 변경
                        .requestMatchers(PathRequest.toH2Console()).permitAll()

        );
        http.addFilter(getAuthenticationFilter(http));
        return http.build();
    }

    private AuthenticationFilter getAuthenticationFilter(HttpSecurity http) throws Exception {
        AuthenticationManager authenticationManager = authenticationManager(http.getSharedObject(AuthenticationConfiguration.class));
        AuthenticationFilter authenticationFilter = new AuthenticationFilter(authenticationManager, userService, env);
//        authenticationFilter.setAuthenticationManager(authenticationManager);
        return authenticationFilter;
    }

}

 

security.AuthenticationFilter

@Slf4j
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
//    private final AuthenticationManager authenticationManager;
//
//    public AuthenticationFilter(AuthenticationManager authenticationManager){
//        this.authenticationManager = authenticationManager;
//    }
    private UserService userService;
    // 사용자가 만들었던 토큰에대한 만료기간, 토큰을 만들기위한 알고리즘만들때 자바코드안에 토큰정보를 넣는것이아니라 application.yml파일에서 관리할것이기 때문에 필요하다.
    private Environment env;
    private final Key key;
    public AuthenticationFilter(AuthenticationManager authenticationManager,
                                UserService userService,
                                Environment env) {
        super.setAuthenticationManager(authenticationManager);  // super(authenticationManager)와 같은 방법
        this.userService = userService;
        this.env = env;
        byte[] keyBytes = Decoders.BASE64.decode(env.getProperty("token.secret"));
        this.key = Keys.hmacShaKeyFor(keyBytes);
    }

    @Override
    // 요청정보를 보냈을때 그것을 처리해줄수있는 메소드 재정의
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {
        try {
            // request.getInputStream() : 전달시켜주려는 로그인값이 post형식으로 전달된다. post형식을 requestParameter로 받을수 없기때문에 inputstream으로 수작업하도록한다.
            RequestLogin creds = new ObjectMapper().readValue(request.getInputStream(), RequestLogin.class);

            // getAuthenticationManager().authenticate(): 인증작업 요청
            // 사용자가 입력했던 email(ID), password 값을 스프링시큐리티에서 사용할수있는 형태의값으로 변환하기위해선 UsernamePasswordAuthenticationToken 으로 변경할 필요가 있다.
            return getAuthenticationManager().authenticate(
                    new UsernamePasswordAuthenticationToken(
                            creds.getEmail(),       // ID
                            creds.getPassword(),    // PW
                            new ArrayList<>()       // 어떤 권한을 가질것인지
                    )
            );
        } catch(IOException e) {
            throw new RuntimeException(e);
        }
    }


    @Override
    // 실제 로그인 성공했을때 정확히 어떤 처리를 해줄것인지 정의
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {
        // 로그인성공하면 userName을 가져오자
        String userName = ((User)authResult.getPrincipal()).getUsername();
        // userName으로 user정보를 가져오자
        UserDto userDetails = userService.getUserDetailsByEmail(userName);


        // token 생성
        String token = Jwts.builder()
                .setSubject(userDetails.getUserId())
                .setExpiration(new Date(System.currentTimeMillis() +
                        Long.parseLong(env.getProperty("token.expiration_time")))) // 토큰 유효시간 설정
//                .signWith(SignatureAlgorithm.HS512, env.getProperty("token.secret"))    // 암호화
                .signWith(key, SignatureAlgorithm.HS512)    // 암호화
                .compact();

        // token정보 주입
        response.addHeader("token", token);
        // 정상적으로 만들어진 token인지 확인하는 작업을 위해 userId 주입
        response.addHeader("userId", userDetails.getUserId());

    }
}