일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 파이썬
- 게임
- express
- 프로그래밍
- JSON
- springboot
- c#
- 스프링
- IntelliJ
- frontend
- unity
- 깃
- OAuth2.0
- oAuth
- 스프링부트
- jwt
- Python
- 백엔드
- spring
- MongoDB
- AWS
- 게임개발
- bcrypt
- node.js
- 코딩
- 백준
- netlify
- 유니티
- RiotAPI
- react
- Today
- Total
Unwound Developer
SpringBoot에서 Bcrypt와 JWT를 사용 인가와 인증(로그인), Postman 헤더에 토큰 담기 본문
node.js로 로그인을 구현했을 때와 같은 방식으로 로그인을 구현했습니다.
개발 환경이 스프링부트라서 문법차이는 있지만, 과정은 일맥상통합니다.
우선 build.gradle에
dependencies{
...
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'io.jsonwebtoken:jjwt:0.9.1'
}
를 추가합니다.
전자는 Bcrypt사용에, 후자는 JWT사용에 필요한 라이브러리를 사용가능하게 해줍니다.
application.properties에는 JWT Secretkey를 입력합니다.
나중에 jwt 토큰을 해독할 때 필요합니다.
# JWT SecretKey
jwt.password = {JWT SECRETKEY 입력}
그리고 main클래스에
@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
public class StarWritingApplication {
public static void main(String[] args) {
SpringApplication.run(StarWritingApplication.class, args);
}
}
securityconfiguration exclude 를 선언해주는데, 앞에서 build.gradle에 추가한 security가 계속 오류를 발생시키더라구요.
security가 autoConfiguration되면서 생기는 오류라고합니다.
자세히는 어려워서 일단 사용만하고 넘어갔습니다..
- MemberController
@PostMapping("/api/login")
public ResponseEntity<HttpHeaders> Login(@RequestBody LoginRequestDto loginRequestDto) {
String token = memberService.Login(loginRequestDto.getMemberId(),loginRequestDto.getPassword());
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Authorization","Bearer "+token);
return new ResponseEntity<>(httpHeaders, HttpStatus.ACCEPTED);
}
LoginRequestDto는 별거없어요. memberId와 password로 이루어져있습니다.
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class LoginRequestDto {
private String memberId;
private String password;
}
보통 로그인에 필요한 정보가 아이디와 비밀번호이므로 위와같이 설정했고,
받은 memberId와 password를 memberService의 로그인 함수로 보냅니다.
- MemberService
public String Login(String inputMemberID, String inputPassword){
Member member = memberRepository.findByMemberId(inputMemberID).get();
System.out.println("입력한 유저: " + member.getMemberId());
String hashedPassword = member.getPassword();
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(bcryptStrength);
System.out.println("Bcrypt 비밀번호 대조 결과: "+bCryptPasswordEncoder.matches(inputPassword,hashedPassword));
// 비밀번호가 맞다면
if (bCryptPasswordEncoder.matches(inputPassword,hashedPassword)){
String token = jwtProvider.createToken(member.getMemberId());
boolean claims = jwtProvider.parseJwtToken("Bearer "+ token); // 토큰 검증
return token;
}else {
return null;
}
}
우선, 입력받은 memberId가 존재하는 유저인지 Repository의 findByMemberID함수를 통해 확인합니다.
그리고, 여기서 멤버의 비밀번호는 이미 Bcrypt를 통해 암호화 되어있는 상태로 저장되어있는데,
로그인 시 입력한 비밀번호와 암호화된 비밀번호를 대조한 후 결과를 반환해주는
BcryptPasswordEncoder의 matches함수를 사용합니다.
비밀번호 대조 일치 여부를 true,false로 반환합니다.
※ 비밀번호를 Bcrypt로 암호화해서 저장하는 코드는 간단합니다.
new BCryptPasswordEncoder(10).encode(this.password)
Encoder안의 숫자는 해싱을 몇번 진행할지를 의미합니다.
숫자가 클수록 암호화를 강하게 진행합니다.
위와 같이 비밀번호를 암호화 후에, DB에 저장하면 됩니다.
다시 로그인으로 돌아와서
// 비밀번호가 맞다면
if (bCryptPasswordEncoder.matches(inputPassword,hashedPassword)){
String token = jwtProvider.createToken(member.getMemberId());
boolean claims = jwtProvider.parseJwtToken("Bearer "+ token); // 토큰 검증
return token;
}
jwtProvider는 jwt토큰을 관리하는 클래스 입니다.
비밀번호가 일치한다면, jwt토큰을 본격적으로 발급하는 과정에 들어갑니다.
- JwtProvider
@Value("${jwt.password}") /* SECRET_KEY */
private String secretKey;
/*토큰 생성 메소드*/
public String createToken(String subject) {
Date now = new Date();
System.out.println("토큰 발행 memberId: "+subject);
Date expiration = new Date(now.getTime() + Duration.ofDays(1).toMillis()); // 만료기간 1일
System.out.println("토큰 만료기간: " + expiration);
return Jwts.builder()
.setHeaderParam(Header.TYPE, Header.JWT_TYPE) // (1) 토큰헤더에 JWT 속성임을 기재함
.setIssuer("test") // 토큰발급자(iss)
.setIssuedAt(now) // 발급시간(iat)
.setExpiration(expiration) // 만료시간(exp)
.setSubject(subject) // 토큰 제목(subject)
.signWith(SignatureAlgorithm.HS256, Base64.getEncoder().encodeToString(secretKey.getBytes())) // 알고리즘, 시크릿 키
.compact();
}
secretKey는 아까 properties에 설정해 두었던 jwt비밀키 입니다.
createToken의 파라미터로 받아온 subject는 로그인을 시도하는 사용자의 memberId입니다.
expriration은 오늘 날짜에 하루를 더해 토큰 만료일을 24시간으로 설정하도록 했습니다.
그리고 Jwts라이브러리의 builder메소드를 통해 토큰을 만들고 반환합니다.
토큰을 빌드하는데 사용한 메소드들의 관한 간략한 설명은 주석을 달아놓았어요.
빌드에 성공했다면, 완성된 JWT토큰이 memberService로 반환되었을겁니다.
그리고, memberService는 토큰을 다시 컨트롤러로 반환합니다.
- MemberService
@PostMapping("/api/login")
public ResponseEntity<HttpHeaders> Login(@RequestBody LoginRequestDto loginRequestDto) {
String token = memberService.Login(loginRequestDto.getMemberId(),loginRequestDto.getPassword());
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Authorization","Bearer "+token);
return new ResponseEntity<>(httpHeaders, HttpStatus.ACCEPTED);
}
완성된 jwt를 헤더에 담아서 클라이언트에게 반환합니다.
httpHeader에 Authorization이라는 키와 토큰을 value에 담아 반환합니다.
로그인에 실패했을때는 실패했음을 알리고, 400 state를 반환해야하는데, 아직 안했어요..
일단은 이렇게 까지가 JWT를 클라이언트에게 발급하는 과정입니다.
토큰을 발급했으니, 토큰을 인증하는 과정도 진행합니다.
Postman을 통해서 테스트를 진행했는데, Postman에서 헤더를 담아 요청하는 방법이 있더라구요.
Post요청의 Headers탭으로가서 KEY에 Authorization을 적고, VALUE에는 Bearer 한칸띄어쓰기 후 토큰을 적어주면
마치 웹에서 헤더에 JWT토큰을 담아서 요청하듯이 테스트가 가능합니다.
- PostController
@PostMapping("/api/posts") /* POST 요청의 Header에서 키값이 Authorization인 값을 받는다 -> 토큰 받아온다 */
public ResponseEntity<String> savePost(@RequestHeader(value = "Authorization")String token, @RequestBody PostRequestDto requestDto) {
String httpState = postService.post(requestDto,token);
if (httpState =="202") {
return new ResponseEntity<>("Post가 성공적으로 실행되었습니다.", HttpStatus.ACCEPTED); /* http state code 202 반환 */
}else{
return new ResponseEntity<>("Post요청 실패!", HttpStatus.BAD_REQUEST); /* http state code 400 반환 */
}
}
RequestHeader를 통해 Authorization이라는 키를 가진 헤더의 value(JWT)를 가져옵니다.
PostRequestDto는 사용자가 로그인 후 글 작성을 요청하는 상황을 가정해, 글 제목이나 글 내용같은게 있습니다.
그냥 예시일 뿐이라 JWT인증과정에서는 필요없습니다.
postService의 post함수로 token을 보냅니다.
- PostService
public String post(PostRequestDto postRequestDto,String token){
boolean claims = jwtProvider.parseJwtToken(token);
System.out.println("토큰 진위여부: "+claims);
if(claims){
String memberId = postRequestDto.getMember();
Member member = memberRepository.findByMemberId(memberId).get();
Post post = postRequestDto.toEntity(member);
postRepository.save(post);
return "202";
}else{
return "400";
}
}
받아온 토큰을 다시 JwtProvider의 parseJwtToken함수로 보냅니다.
- ParseJwtToken
public boolean parseJwtToken(String token) {
token = BearerRemove(token); // Bearer 제거
try {
System.out.println("토큰 memberId: "+
Jwts.parser()
.setSigningKey(Base64.getEncoder().encodeToString(secretKey.getBytes()))
.parseClaimsJws(token)
.getBody()
.getSubject()
);
return true;
} catch (ExpiredJwtException e) {
System.out.println("토큰 만료");
return false;
} catch (JwtException e) {
System.out.println("토큰 에러");
return false;
}
}
우선 받아온 토큰을 Jwts의 parseClaimsJws메소드 파라미터로 넣습니다.
이 때, setSigningKey메소드 안에 아까 properties에 설정해두었던 JWT 비밀번호를 인자로 전달해요.
SecretKey에 문제가 없다면, try문을 무사히 빠져나와 true를 반환할 거에요.
만약 Jwts.parser메소드에서 오류가 발생하게된다면 밑의 Catch로 넘어가게되는데,
오류내용이 토큰 만료라면 ExpiredJwtException으로,
토큰 자체가 위조되어 맞지 않는 토큰이라면 JwtException으로 넘어가게 됩니다.
여기서 true를 반환하느냐 false를 반환하느냐에 따라서 true/false가 다시 PostController까지 반환됩니다.
Postman을 사용해 테스트를 진행해보겠습니다.
로그인 요청을 성공적으로 반환한 서버의 콘솔 창입니다.
클라이언트도 헤더에 토큰이 담겨 성공적으로 응답받은 모습입니다.
이번엔 Post 요청을 통해 JWT토큰 인증 테스트입니다.
일부러 어제 발급한 토큰을 넣어봤는데, 토큰 만료로 Post요청이 실패했습니다.
방금 로그인을 통해 발급받은 새 JWT를 사용해 요청해봅니다.
JWT인가/인증에 성공했습니다.
'Web > Spring' 카테고리의 다른 글
스프링부트 One To Many 관계설정 (0) | 2023.02.18 |
---|---|
스프링부트 이미지 저장/불러오기, thymeleaf 재 컴파일 (0) | 2023.02.14 |
OAuth2.0을 통해 구글 계정 정보 받아오기 (0) | 2023.02.01 |
OAuth 2.0 이란 (0) | 2023.01.30 |
스프링 - DTO (0) | 2023.01.24 |