Spring Security
Spring 기반의 애플리케이션의 보안(인증과 권한, 인가 등)을 담당하는 스프링 하위 프레임워크이다
인증과 인가
•
해당 사용자가 본인이 맞는지를 확인하는 절차(누구인지 확인하는 단계)
인증(Authentication)
•
해당 사용자가 본인이 맞는지를 확인하는 절차
•
로그인에 성공하면 애플리케이션 서버는 사용자에게 토큰을 전달한다
인가(Authorization):
•
인증된 사용자가 요청한 자원에 접근 가능한지를 결정하는 절차
•
예시) 인증된 사용자가 특정 게시판에 접근하려고 할 때 접근 등급을 확인하는 절차
인증과 인가 절차는 필터체인에서 진행한다
필터체인
•
많은 필터가 WebSecurityConfigurerAdapter 클래스를 상속받아 설정한다 하지만 SpringBoot에서 잘 안쓰게 된다
•
여러 보안 필터를 만들기 위해서는 WebSecurityConfigurerAdapter를 상속받는 클래스를 여러개 만들면 된다
•
별다른 설정이 없다면 스프링 시큐리티에서는 usernamePasswordAuthenticationFiler를 통해 인증을 처리한다
•
Security 적용 코드
@EnableWebSecurity
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.httpBasic().disable()
.csrf().disable()
.cors().and()
.authorizeRequests()
.antMatchers("/api/**").permitAll()
.antMatchers("/api/v1/users/join", "/api/v1/users/login").permitAll() // join, login은 언제나 가능
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // jwt사용하는 경우 씀
.and()
//.addFilterBefore(new JwtTokenFilter(userService, secretKey), UsernamePasswordAuthenticationFilter.class) //UserNamePasswordAuthenticationFilter적용하기 전에 JWTTokenFilter를 적용 하라는 뜻 입니다.
.build();
}
}
Java
복사
@EnableWebSecurity : 필터체인에 포함시킨다
@Configuration
•
수동으로 스프링 컨테이너에 빈을 등록하는 방법
•
개발자가 직접 제어가 불가능한 라이브러리를 빈으로 등록할 때 불가피하게 사용
•
1개 이상의 @Bean을 제공하는 클래스의 경우 반드시 @Configuration을 명시해 주어야 싱글톤이 보장됨
@Component
•
자동으로 스프링 컨테이너에 빈을 등록하는 방법
•
@Component 하위 어노테이션으로 @Configuration, @Controller, @Service, @Repository 등이 있음
BCryptPasswordEncoder
스프링 시큐리티(Spring Seurity) 프레임워크에서 제공하는 클래스 중 하나로 비밀번호를 암호화하는 데 사용할 수 있는 메서드를 가진 클래스입니다.
@Configuration
public class EncrypterConfig {
@Bean
public BCryptPasswordEncoder encodePwd() {
return new BCryptPasswordEncoder();// password를 인코딩 해줄때 쓰기 위함
}
}
Java
복사
@Builder
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class UserJoinRequest {
private String userName;
private String password;
private String emailAddress;
public User toEntity(String password) {
return User.builder()
.userName(this.userName)
.password(password)
.emailAddress(this.emailAddress)
.build();
}
}
Java
복사
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final BCryptPasswordEncoder encoder;
public UserDto join(UserJoinRequest request) {
userRepository.findByUserName(request.getUserName())
.ifPresent(user ->{
throw new HospitalReviewAppException(ErrorCode.DUPLICATED_USER_NAME, String.format("UserName:%s", request.getUserName()));
});
User savedUser = userRepository.save(request.toEntity(encoder.encode(request.getPassword())));
return UserDto.builder()
.id(savedUser.getId())
.userName(savedUser.getUserName())
.emailAddress(savedUser.getEmailAddress())
.build();
}
}
Java
복사
encode : 패스워드를 암호화해주는 메서드입니다. encde() 메서드는 SHA-1, 8바이트로 결합된 해쉬, 랜덤 하게 생성된 솔트(salt)를 지원합니다. String 반환
로그인
@AllArgsConstructor
@Getter
public class UserLoginRequest {
private String userName;
private String password;
}
Java
복사
@AllArgsConstructor
@Getter
public class UserLoginResponse {
private String token;
}
Java
복사
@PostMapping("/login")
public Response<UserLoginResponse> login(@RequestBody UserLoginRequest userLoginRequest) {
String token = userService.login(userLoginRequest.getUserName(), userLoginRequest.getPassword());
return Response.success(new UserLoginResponse(token));
}
Java
복사
@Value("${jwt.token.secret}")
private String secretKey;
private long expireTimeMs = 1000 * 60 * 60;
public String login(String userName, String password) {
// userName있는지 여부 확인
// 없으면 NOT_FOUND에러 발생
User user = userRepository.findByUserName(userName)
.orElseThrow(() -> new HospitalReviewAppException(ErrorCode.NOT_FOUND, String.format("%s는 가입된 적이 없습니다.", userName)));
// password일치 하는지 여부 확인
if(!encoder.matches(password, user.getPassword())){
throw new HospitalReviewAppException(ErrorCode.INVALID_PASSWORD, String.format("userName 또는 password가 잘못 됐습니다."));
}
// 두가지 확인중 예외 안났으면 Token발행
return JwtTokenUtil.createToken(userName, secretKey, expireTimeMs);
}
Java
복사
matches: 제출된 인코딩 되지 않은 패스워드(일치 여부를 확인하고자 하는 패스워드)와 인코딩 된 패스워드의 일치 여부를 확인해줍니다. boolean 반환
JWT
JSON Web Token은 당사자 간의 정보를 JSON형태로 안전하게 전송하기 위한 토큰
특징
URL에서 사용할 수 있는 문자열로만 구성되어 있어 HTTP 구성요소 어디든 위치할 수 있습니다.
public class JwtTokenUtil {
public static String createToken(String userName, String key, long expireTimeMs) {
Claims claims = Jwts.claims(); // 일종의 map
claims.put("userName", userName);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expireTimeMs))
.signWith(SignatureAlgorithm.HS256, key)
.compact();
}
}
Java
복사