4.1 REST API 인증 기법
4.1.1 Basic 인증
Basic 인증
모든 HTTP 요청에 아이디와 비밀번호를 같이 보내는 것
최초 로그인한 후 HTTP 요청 헤더의 Authorization: 부분에 ‘Basic <ID>:<Password>’처럼 아이디와 비밀번호를 콜론으로 이어붙인 후 Base64로 인코딩한 문자열을 함께 보냄
Copy Authorization : Basic aGVsb93b3JsZEBnbWFpbC5jbC5jb206MTIzNA ==
이 HTTP 요청을 수신한 서버는 인코딩된 문자열을 디코딩해 아이디와 비밀번호를 찾아냄
사용자 정보가 저장된 데이터베이스 또는 인증 서버의 레코드와 비교함
데이터베이스의 레코드는 아이디와 비밀번호가 일치하면 요텅받은 일을 수행하고, 아니면 거부함
Basic 인증의 문제점
아이디와 비밀번호를 노출
중간에 누군가 HTTP 요청을 가로채 문자열을 디코딩하면 아이디와 비밀번호를 알아낼 수 있음 (MITM)
HTTP와 사용하기엔 취약하여, 반드시 HTTPS와 사용해야 함
사용자의 계정 정보가 있는 저장 장소, 인증 서버와 인증 DB에 과부하가 걸릴 확률이 높음
!! 사진사진
4.1.2 토큰 기반 인증
토큰 기반 인증
토큰(Token)은 사용자를 구별할 수 있는 문자열
토큰은 최초 로그인 시 서버가 만들어 줌.
서버가 자기만의 노하우로 토큰을 만들어 반환하면 클라이언트는 이후 요청에 아이디와 비밀번호 대신 토큰을 계속 넘겨 자신이 인증된 사용자임을 알리는 것
Copy Authorization : Bearer Nnfjadshgskdhglkjrnglkjreblgkjerbdgkjsrbg
토큰을 기반으로 하는 요청은 헤더에 위와같이 Authorization: Bearer <TOKEN>을 명시
서버는 이 토큰을 받고 어떤 형태로든 인증을 해야 함
ex
서버가 랜덤한 문자와 숫자를 섞어 UUID로 토큰을 작성해 넘긴다고 가정
요청을 받을 때마다 헤더의 토큰을 서버의 토큰과 비교해 클라이언트를 인증
Basic Auth vs 토큰 기반 인증
아이디와 비밀번호를 매번 네크워크를 통해 전송해야 할 필요가 없음
서버가 토큰을 마음대로 생성할 수 있으므로 사용자의 인가 정보(예 : User, Admin 등) 또는 유효 시간을 정해 관리 가능
디바이스마다 다른 토큰을 생성해 주고 디바이스마다 유효 시간을 다르게 정하거나 임의로 로그아웃을 할 수도 있음
4.1.3 JSON 웹 토큰
JSON 웹 토큰
서버에서 전자 서명된 토큰 을 이용하면 인증에 따른 스케일 문제를 해결 가능
{header}.{payload}.{signature}로 구성
Copy Authorization : Bearer ejsldkf . saeff ; dgjad . fejfwiefk
Header
alg : Algorithm을 줄인 말로 토큰의 서명을 발행하는 데 사용된 해시 알고리듬의 종류 의미
Payload
sub : Subject를 줄인 말로 토큰의 주인 의미. 우리 애플리케이션에서는 사용자의 이메일로 토큰의 주인을 판별함. sub는 ID처럼 유일한 식별자여야 함
iss : Issuer를 줄인 말로 토큰을 발행한 주체를 의미
iat : issued at을 줄인 말로 토큰이 발행된 날짜와 시간
exp : expirarion을 줄인 말로 토큰이 만료되는 시간
Sugnature
토큰을 발행한 주체 Issuer가 발행한 서명으로 토큰의 유효성 검사에 사용
토큰 기반 인증 vs JWT 토큰 기반 인증
JWT 은 서버가 헤더와 페이로드를 생성한 후 전자 서명을 함
전자 서명
{헤더}.{페이로드}와 시크릿키를 이용해 해시 함숫에 돌린 암호화한 결과 값
시크릿키란 나만 알고 있는 문자열, 비밀번호 같은 것
JWT 토큰 생성과 인증
생성
최초 로그인 시 서버는 아이디와 비밀번호를 서버에 저장된 아이디와 비밀번호에 비교해 인증
만약 인증된 사용자인 경우 사용자의 정보를 이용해 {헤더}.{페이로드} 부분을 작성
이후 자신의 시크릿키로 {헤더}.{페이로드}.{서명}으로 이어붙이고 Base64로 인코딩한 후 반환
인증
디코딩해서 얻은 JSON을 {헤더}.{페이로드}와 {서명} 부분으로 나눔
서버는 {헤더}.{헤이로드}와 자신이 갖고 있는 Secret으로 전자 서명을 만들 후 방금 만든 전자 서명을 HTTP 요청이 갖고 온 {서명} 부분과 비교해 토큰의 유효성 검사
이는 인증 서버에 부하를 일으키지 않음. 즉 더 이상 인증 서버가 단일 장애점이 아님
4.2 User 레이어 구현
4.2.1 UserEntity.java
Copy package com . todoweb . todoSpringApp . model ;
import lombok . AllArgsConstructor ;
import lombok . Builder ;
import lombok . Data ;
import lombok . NoArgsConstructor ;
import org . hibernate . annotations . GenericGenerator ;
import javax . persistence . * ;
@ Data
@ Entity
@ Builder
@ NoArgsConstructor
@ AllArgsConstructor
@ Table (uniqueConstraints = {@ UniqueConstraint (columnNames = "email" )})
public class UserEntity {
@ Id
@ GeneratedValue (generator = "system-uuid" )
@ GenericGenerator (name = "system-uuid" , strategy = "uuid" )
private String id;
@ Column (nullable = false )
private String username;
@ Column (nullable = false )
private String email;
@ Column (nullable = false )
private String password;
4.2.2 UserRepository.java
Copy package com . todoweb . todoSpringApp . persistence ;
import com . todoweb . todoSpringApp . model . UserEntity ;
import org . springframework . data . jpa . repository . JpaRepository ;
import org . springframework . stereotype . Repository ;
@ Repository
public interface UserRepository extends JpaRepository < UserEntity , String > {
UserEntity findByEmail ( String email);
Boolean existsByEmail ( String email);
UserEntity findByEmailAndPassword ( String email , String password);
}
4.2.3 UserService.java
Copy package com . todoweb . todoSpringApp . service ;
import com . todoweb . todoSpringApp . model . UserEntity ;
import com . todoweb . todoSpringApp . persistence . UserRepository ;
import lombok . extern . slf4j . Slf4j ;
import org . springframework . beans . factory . annotation . Autowired ;
import org . springframework . security . crypto . password . PasswordEncoder ;
import org . springframework . stereotype . Service ;
@ Slf4j
@ Service
public class UserService {
@ Autowired
private UserRepository userRepository;
public UserEntity create ( final UserEntity userEntity) {
if (userEntity == null || userEntity . getEmail () == null ) {
throw new RuntimeException( "Invalid arguments" ) ;
}
final String email = userEntity . getEmail ();
if ( userRepository . existsByEmail (email)) {
log . warn ( "Email already exists {}" , email);
throw new RuntimeException( "Email already exists" ) ;
}
return userRepository . save (userEntity);
}
public UserEntity getByCredentials ( final String email , final String password , final PasswordEncoder encoder) {
final UserEntity originalUser = userRepository . findByEmail (email);
if (originalUser != null && encoder . matches (password , originalUser . getPassword ())) {
return originalUser;
}
return null ;
}
}
4.2.4 UserController.java
UserController.java UserDTO.java
Copy package com . todoweb . todoSpringApp . controller ;
import com . todoweb . todoSpringApp . dto . ResponseDTO ;
import com . todoweb . todoSpringApp . dto . UserDTO ;
import com . todoweb . todoSpringApp . model . UserEntity ;
import com . todoweb . todoSpringApp . security . TokenProvider ;
import com . todoweb . todoSpringApp . service . UserService ;
import lombok . extern . slf4j . Slf4j ;
import org . springframework . beans . factory . annotation . Autowired ;
import org . springframework . http . ResponseEntity ;
import org . springframework . security . crypto . bcrypt . BCryptPasswordEncoder ;
import org . springframework . security . crypto . password . PasswordEncoder ;
import org . springframework . web . bind . annotation . PostMapping ;
import org . springframework . web . bind . annotation . RequestBody ;
import org . springframework . web . bind . annotation . RequestMapping ;
import org . springframework . web . bind . annotation . RestController ;
@ Slf4j
@ RestController
@ RequestMapping ( "/auth" )
public class UserController {
@ Autowired
private UserService userService;
@ PostMapping ( "/signup" )
public ResponseEntity < ? > registerUser (@ RequestBody UserDTO userDTO) {
try {
// 리퀘스트를 이용해 저장할 유저 만들기
UserEntity user = UserEntity . builder ()
. email ( userDTO . getEmail ())
. username ( userDTO . getUsername ())
. password ( userDTO . getPassword ())
. build ();
// 서비스를 이용해 리파지토리에 유저 저장
UserEntity registeredUser = userService . create (user);
UserDTO responseUserDTO = UserDTO . builder ()
. email ( registeredUser . getEmail ())
. id ( registeredUser . getId ())
. username ( registeredUser . getUsername ())
. build ();
// 유저 정보는 항상 하나이므로 그냥 리스트로 만들어야하는 ResponseDTO를 사용하지 않고 그냥 UserDTO 리턴.
return ResponseEntity . ok (responseUserDTO);
} catch ( Exception e) {
// 예외가 나는 경우 bad 리스폰스 리턴.
ResponseDTO responseDTO = ResponseDTO . builder () . error ( e . getMessage ()) . build ();
return ResponseEntity
. badRequest ()
. body (responseDTO);
}
}
@ PostMapping ( "/signin" )
public ResponseEntity < ? > authenticate (@ RequestBody UserDTO userDTO) {
UserEntity user = userService . getByCredentials (
userDTO . getEmail () ,
userDTO . getPassword ();
if (user != null ) {
final UserDTO responseUserDTO = UserDTO . builder ()
. email ( user . getUsername ())
. id ( user . getId ())
. build ();
return ResponseEntity . ok () . body (responseUserDTO);
} else {
ResponseDTO responseDTO = ResponseDTO . builder ()
. error ( "Login failed" )
. build ();
return ResponseEntity . badRequest () . body (responseDTO);
}
}
}
Copy package com . todoweb . todoSpringApp . dto ;
import lombok . AllArgsConstructor ;
import lombok . Builder ;
import lombok . Data ;
import lombok . NoArgsConstructor ;
@ Data
@ Builder
@ NoArgsConstructor
@ AllArgsConstructor
public class UserDTO {
private String token;
private String email;
private String username;
private String password;
private String id;
}
4.3 Spring Security 통합
4.2에서 인증 및 인가를 구현하려고 사용자에 관련된 클래스을 작성
4.3.1 JWT 생성 및 반환 구현
JWT를 이용해 인증하는 방법 -> 사진
1.jjwt 라이브러리 디펜던시에 추가
Copy // <https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt>
implementation group: 'io.jsonwebtoken', name:'jjwt', version: '0.9.1'
2. 토큰 발행
Copy package com . todoweb . todoSpringApp . security ;
import com . todoweb . todoSpringApp . model . UserEntity ;
import io . jsonwebtoken . Claims ;
import io . jsonwebtoken . Jwts ;
import io . jsonwebtoken . SignatureAlgorithm ;
import lombok . extern . slf4j . Slf4j ;
import org . springframework . stereotype . Service ;
import java . util . Date ;
import java . time . Instant ;
import java . time . temporal . ChronoUnit ;
@ Slf4j
@ Service
public class TokenProvider {
private static final String SECRET_KEY = "NMA8JPctFuna59f5" ;
public String create ( UserEntity userEntity) {
Date expiryDate = Date . from (
Instant . now ()
. plus ( 1 , ChronoUnit . DAYS ));
/*
{ // header
"alg":"HS512"
}.
{ // payload
"sub":"40288093784915d201784916a40c0001",
"iss": "demo app",
"iat":1595733657,
"exp":1596597657
}.
// SECRET_KEY를 이용해 서명한 부분
Nn4d1MOVLZg79sfFACTIpCPKqWmpZMZQsbNrXdJJNWkRv50_l7bPLQPwhMobT4vBOG6Q3JYjhDrKFlBSaUxZOg
*/
// JWT Token 생성
return Jwts . builder ()
. signWith ( SignatureAlgorithm . HS512 , SECRET_KEY)
. setSubject ( userEntity . getId ())
. setIssuer ( "demo app" )
. setIssuedAt ( new Date() )
. setExpiration (expiryDate)
. compact ();
}
public String validateAndGetUserId ( String token) {
Claims claims = Jwts . parser ()
. setSigningKey (SECRET_KEY)
. parseClaimsJws (token)
. getBody ();
return claims . getSubject ();
}
}
3. 로그인 시 토큰 반환
Copy package com . todoweb . todoSpringApp . controller ;
/* import 생략 */
@ Slf4j
@ RestController
@ RequestMapping ( "/auth" )
public class UserController {
@ Autowired
private UserService userService;
@ Autowired
private TokenProvider tokenProvider;
@ PostMapping ( "/signin" )
public ResponseEntity < ? > authenticate (@ RequestBody UserDTO userDTO) {
UserEntity user = userService . getByCredentials (
userDTO . getEmail () ,
userDTO . getPassword () ,
passwordEncoder);
if (user != null ) {
final String token = tokenProvider . create (user);
final UserDTO responseUserDTO = UserDTO . builder ()
. email ( user . getUsername ())
. id ( user . getId ())
. token (token)
. build ();
return ResponseEntity . ok () . body (responseUserDTO);
} else {
ResponseDTO responseDTO = ResponseDTO . builder ()
. error ( "Login failed" )
. build ();
return ResponseEntity . badRequest () . body (responseDTO);
}
}
}
4.3.2 스프링 시큐리티와 서블릿 구현
API 가 실행될 때마다 사용자를 인증해 주는 부분 구현 : 스프링 시큐리티
스프링 시큐리티
토큰 인증을 위해 컨트롤러 메서드의 첫 부분마다 인증 코드 작성해야하는 문제점
서블릿 필터
스프링이 구현하는 디스패처 서블릿이 실행되기 전에 항상 실행
1) 개발자는 서블릿 필터를 구현하고 2) 서플릿 필터를 서블릿 컨테이너가 실행하도록 설정
<서블릿 컨테이너의 서블릿 필터 pic>
서블릿 필터에서 스프링 시큐리티의 위치와 우리가 구현할 필터의 위치
스프링 시큐리티가 FilterChainProxy라는 필터를 서블릿 필터에 끼워 줌
이 FilterChainProxy 클래스 안에서 내부적으로 필터 실행
이 필터들이 스프링이 관리하는 스프링 빈 필터
상속하는 필터는 OncePerRequestFilter
필터 설정하기 위해 상속하는 클래스는 WebSecurityConfigurerAdapter
4.3.3 JWT를 이용한 인증 구현
서블릿 필터 구현
스프링 시큐리티 디펜던시를 build.gradle에 추가
Copy implementation 'org.springframework.boot:spring-boot-starter-security'
OncePerRequestFilter를 상속하는 JwtAuthenticationFilter 구현
Copy package com . todoweb . todoSpringApp . security ;
import lombok . extern . slf4j . Slf4j ;
import org . springframework . beans . factory . annotation . Autowired ;
import org . springframework . security . authentication . AbstractAuthenticationToken ;
import org . springframework . security . authentication . UsernamePasswordAuthenticationToken ;
import org . springframework . security . core . authority . AuthorityUtils ;
import org . springframework . security . core . context . SecurityContext ;
import org . springframework . security . core . context . SecurityContextHolder ;
import org . springframework . security . web . authentication . WebAuthenticationDetailsSource ;
import org . springframework . stereotype . Component ;
import org . springframework . util . StringUtils ;
import org . springframework . web . filter . OncePerRequestFilter ;
import javax . servlet . FilterChain ;
import javax . servlet . ServletException ;
import javax . servlet . http . HttpServletRequest ;
import javax . servlet . http . HttpServletResponse ;
import java . io . IOException ;
@ Slf4j
@ Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@ Autowired
private TokenProvider tokenProvider;
@ Override
protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
String token = parseBearerToken(request) ;
log . info ( "Filter is running ... " );
if (token != null && ! token . equalsIgnoreCase ( "null" )) {
String userId = tokenProvider . validateAndGetUserId (token);
log . info ( "Authenticated user ID : " + userId);
AbstractAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userId ,
null , AuthorityUtils . NO_AUTHORITIES
) ;
authentication . setDetails ( new WebAuthenticationDetailsSource() . buildDetails (request));
SecurityContext securityContext = SecurityContextHolder . createEmptyContext ();
securityContext . setAuthentication (authentication);
SecurityContextHolder . setContext (securityContext);
}
} catch ( Exception ex) {
logger . error ( "Could not set user authentication in security context" , ex);
}
filterChain. doFilter (request , response);
}
private String parseBearerToken( HttpServletRequest request) {
String bearerToken = request . getHeader ( "Authorization" );
if ( StringUtils . hasText (bearerToken) && bearerToken . startsWith ( "Bearer" )) {
return bearerToken . substring ( 7 );
}
return null ;
}
}
parseBearerToken() : 요청의 헤더에서 Bearer 토큰을 가져옴
TokenProvider를 이용해 토큰을 인증하고 UsernamePasswordAuthenticationToken을 작성. 이 오브젝트에 사용자의 인증 정보를 저장하고 SecurityContext에 인증된 사용자 등록
요청을 처리하는 과정에서 사용자가 인증됐는지 여부 혹은 인증괸 사용자가 누군지 알아야 할 때가 있기 때문
SecurityContext
SecurityContextHolder의 createEmptyContext() 메서드를 이용해 생성
생성한 컨텍스트에 인증 정보인 authentication을 넣고 다시 SecurityContextHolder에 컨텍스트로 등록
SecirutyContextHolder는 기본적으로 ThreadLocal에 저장
ThreadLocal에 저장되므로 Thread마다 하나의 컨텍스트를 관리할 수 있음
4.3.4 스프링 시큐리티 설정
서블릿 컨테이너에 이 서블릿 필터를 사용하라고 알려줘야 함
스프링 시큐리티에 JwtAuthenticationFilter를 사용하라고 알려줘야 함
Copy package com . todoweb . todoSpringApp . config ;
import com . todoweb . todoSpringApp . security . JwtAuthenticationFilter ;
import lombok . extern . slf4j . Slf4j ;
import org . springframework . beans . factory . annotation . Autowired ;
import org . springframework . security . config . annotation . web . builders . HttpSecurity ;
import org . springframework . security . config . annotation . web . configuration . EnableWebSecurity ;
import org . springframework . security . config . annotation . web . configuration . WebSecurityConfigurerAdapter ;
import org . springframework . security . config . http . SessionCreationPolicy ;
import org . springframework . web . filter . CorsFilter ;
@ EnableWebSecurity
@ Slf4j
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@ Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@ Override
protected void configure ( HttpSecurity http) throws Exception {
http . cors ()
. and ()
. csrf ()
. disable ()
. httpBasic ()
. disable ()
. sessionManagement ()
. sessionCreationPolicy ( SessionCreationPolicy . STATELESS )
. and ()
. authorizeRequests ()
. antMatchers ( "/" , "/auth/**" ) . permitAll ()
. anyRequest ()
. authenticated ();
http . addFilterAfter (
jwtAuthenticationFilter ,
CorsFilter . class
);
}
}
4.3.5 TodoController에서 인증된 사용자 사용하기
Copy package com . todoweb . todoSpringApp . controller ;
import com . todoweb . todoSpringApp . dto . ResponseDTO ;
import com . todoweb . todoSpringApp . dto . TodoDTO ;
import com . todoweb . todoSpringApp . model . TodoEntity ;
import com . todoweb . todoSpringApp . service . TodoService ;
import org . springframework . beans . factory . annotation . Autowired ;
import org . springframework . http . ResponseEntity ;
import org . springframework . security . core . annotation . AuthenticationPrincipal ;
import org . springframework . web . bind . annotation . * ;
import java . util . List ;
import java . util . stream . Collectors ;
@ RestController
@ RequestMapping ( "todo" )
public class TodoController {
@ Autowired
private TodoService service;
@ PostMapping
public ResponseEntity < ? > createTodo (@ AuthenticationPrincipal String userId , @ RequestBody TodoDTO dto) {
try {
TodoEntity entity = TodoDTO . toEntity (dto);
entity . setId ( null );
entity . setUserId (userId);
List < TodoEntity > entities = service . create (entity);
List < TodoDTO > dtos = entities . stream () . map (TodoDTO ::new ) . collect ( Collectors . toList ());
ResponseDTO < TodoDTO > response = ResponseDTO . < TodoDTO > builder() . data (dtos) . build ();
return ResponseEntity . ok () . body (response);
} catch ( Exception e) {
String error = e . getMessage ();
ResponseDTO < TodoDTO > response = ResponseDTO . < TodoDTO > builder() . error (error) . build ();
return ResponseEntity . badRequest () . body (response);
}
}
@ GetMapping ( "/findAll" )
public ResponseEntity < ? > findAll () {
try {
List < TodoEntity > entities = service . findAll ();
List < TodoDTO > dtos = entities . stream () . map (TodoDTO ::new ) . collect ( Collectors . toList ());
ResponseDTO response = ResponseDTO . < TodoDTO > builder() . data (dtos) . build ();
return ResponseEntity . ok () . body (response);
} catch ( Exception e) {
String error = e . getMessage ();
ResponseDTO < TodoDTO > response = ResponseDTO . < TodoDTO > builder() . error (error) . build ();
return ResponseEntity . badRequest () . body (response);
}
}
@ GetMapping
public ResponseEntity < ? > findById (@ AuthenticationPrincipal String userId) {
System . out . println ( "UserID : " + userId);
List < TodoEntity > entities = service . findByUserId (userId);
List < TodoDTO > dtos = entities . stream () . map (TodoDTO ::new ) . collect ( Collectors . toList ());
ResponseDTO < TodoDTO > response = ResponseDTO . < TodoDTO > builder() . data (dtos) . build ();
return ResponseEntity . ok (response);
}
@ PutMapping
public ResponseEntity < ? > updateTodo (@ AuthenticationPrincipal String userId , @ RequestBody TodoDTO dto) {
TodoEntity entity = TodoDTO . toEntity (dto);
entity . setUserId (userId);
List < TodoEntity > entities = service . update (entity);
List < TodoDTO > dtos = entities . stream () . map (TodoDTO ::new ) . collect ( Collectors . toList ());
ResponseDTO < TodoDTO > response = ResponseDTO . < TodoDTO > builder() . data (dtos) . build ();
return ResponseEntity . ok () . body (response);
}
@ DeleteMapping
public ResponseEntity < ? > deleteTodo (@ AuthenticationPrincipal String userId , @ RequestBody TodoDTO dto) {
try {
TodoEntity entity = TodoDTO . toEntity (dto);
entity . setUserId (userId);
List < TodoEntity > entities = service . deleteById (entity);
List < TodoDTO > dtos = entities . stream () . map (TodoDTO ::new ) . collect ( Collectors . toList ());
ResponseDTO < TodoDTO > response = ResponseDTO . < TodoDTO > builder() . data (dtos) . build ();
return ResponseEntity . ok () . body (response);
} catch ( Exception e) {
String error = e . getMessage ();
ResponseDTO < TodoDTO > response = ResponseDTO . < TodoDTO > builder() . error (error) . build ();
return ResponseEntity . badRequest () . body (response);
}
}
}
4.3.6 패스워드 암호화
패스워드 암호화 부분은 스프링 시큐리티가 제공하는 BCryptPasswordEncoder 사용
Copy package com . todoweb . todoSpringApp . service ;
import com . todoweb . todoSpringApp . model . UserEntity ;
import com . todoweb . todoSpringApp . persistence . UserRepository ;
import lombok . extern . slf4j . Slf4j ;
import org . springframework . beans . factory . annotation . Autowired ;
import org . springframework . security . crypto . password . PasswordEncoder ;
import org . springframework . stereotype . Service ;
@ Slf4j
@ Service
public class UserService {
/* 기존 코드 . . .*/
public UserEntity getByCredentials ( final String email , final String password , final PasswordEncoder encoder) {
final UserEntity originalUser = userRepository . findByEmail (email);
if (originalUser != null && encoder . matches (password , originalUser . getPassword ())) {
return originalUser;
}
return null ;
}
}
BCryptPasswordEncoder는 값은 값을 인코딩하더라도 할 때마다 값이 다르고 패스워드에 랜덤하게 의미 없는 값을 붙여 결과를 생성
이런 의미 없는 값을 보안 용어로 Salt라고 하고, Salt를 붙여 인코딩하는 것을 Salting이라고 함
matches() 메서드 : Salt를 고려해 두 값을 비교
Copy package com . todoweb . todoSpringApp . controller ;
import com . todoweb . todoSpringApp . dto . ResponseDTO ;
import com . todoweb . todoSpringApp . dto . UserDTO ;
import com . todoweb . todoSpringApp . model . UserEntity ;
import com . todoweb . todoSpringApp . security . TokenProvider ;
import com . todoweb . todoSpringApp . service . UserService ;
import lombok . extern . slf4j . Slf4j ;
import org . springframework . beans . factory . annotation . Autowired ;
import org . springframework . http . ResponseEntity ;
import org . springframework . security . crypto . bcrypt . BCryptPasswordEncoder ;
import org . springframework . security . crypto . password . PasswordEncoder ;
import org . springframework . web . bind . annotation . PostMapping ;
import org . springframework . web . bind . annotation . RequestBody ;
import org . springframework . web . bind . annotation . RequestMapping ;
import org . springframework . web . bind . annotation . RestController ;
@ Slf4j
@ RestController
@ RequestMapping ( "/auth" )
public class UserController {
@ Autowired
private UserService userService;
@ Autowired
private TokenProvider tokenProvider;
private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder() ;
/*기존 코드 . .*/
@ PostMapping ( "/signin" )
public ResponseEntity < ? > authenticate (@ RequestBody UserDTO userDTO) {
UserEntity user = userService . getByCredentials (
userDTO . getEmail () ,
userDTO . getPassword () ,
passwordEncoder);
if (user != null ) {
final String token = tokenProvider . create (user);
final UserDTO responseUserDTO = UserDTO . builder ()
. email ( user . getUsername ())
. id ( user . getId ())
. token (token)
. build ();
return ResponseEntity . ok () . body (responseUserDTO);
} else {
ResponseDTO responseDTO = ResponseDTO . builder ()
. error ( "Login failed" )
. build ();
return ResponseEntity . badRequest () . body (responseDTO);
}
}
}