인프런/[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());
}
}