[Java] 3. JWT와 bcrypt 적용하기
JWT와 bcrypt를 사용하기 위해 pom.xml에
// bcrypt관련
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.5.2</version>
</dependency>
// JWT관련
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1.1</version>
</dependency>
이 내용을 추가합니다.
자바 새 버전에서는 이것도 추가해야 jwt오류가 안난다네요.
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.2</version>
</dependency>
JWT와 bcrypt를 사용하기 위해 service 폴더에 JWTManager.java와 Bcrypt.java 파일을 만들어줍니다.
Bcrypt부터 진행하겠습니다.
Bcrypt.java
package daily.coding.service;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class Bcrypt {
public String HashPassword(String password) {
BCryptPasswordEncoder ecnoder = new BCryptPasswordEncoder();
String hash = ecnoder.encode(password);
return hash;
}
public Boolean CompareHash(String password, String DBpassword) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
if (encoder.matches(password, DBpassword)) {
return true;
} else {
return false;
}
}
}
ExController.java
package daily.coding.controller; // 자기 위치를 명시
import daily.coding.models.*;
import daily.coding.mapper.*;
import daily.coding.service.*;
// 프레임워크에 탑재된 기능 가져오기
import org.springframework.web.bind.annotation.*;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
// 자바 가지고 있는 내장 모듈
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.List;
import java.util.Iterator;
@RestController // restApi를 작성할 수 있는 컨트롤러
@RequestMapping("/api") // url을 api로 지정
@CrossOrigin(origins="*", allowedHeaders="*") // cors허용
public class ExController {
private UserMapper userMapper;
private Bcrypt bcrypt;
public ExController(UserMapper userMapper, Bcrypt bcrypt) {
this.userMapper = userMapper;
this.bcrypt = bcrypt;
}
// create
@PostMapping("/user")
public ResponseEntity<Map<String,String>> CreateUser(@RequestBody User req) {
String hashpassword = bcrypt.HashPassword(req.getPassword());
req.setPassword(hashpassword);
userMapper.Create(req);
Map<String,String> map = new HashMap<>();
map.put("result", "success");
return new ResponseEntity<>(map, HttpStatus.OK);
}
// read
@GetMapping("/users")
public List<User> AllUser() {
return userMapper.findAll();
}
// update
@PostMapping("/update")
public ResponseEntity<Map<String,String>> UpdateUser(@RequestBody User req) {
userMapper.Update(req);
Map<String,String> map = new HashMap<>();
map.put("result", "success");
return new ResponseEntity<>(map, HttpStatus.OK);
}
// delete
@PostMapping("/delete")
public ResponseEntity<Map<String,String>> DeleteUser(@RequestBody User req) {
userMapper.Delete(req);
Map<String,String> map = new HashMap<>();
map.put("result", "success");
return new ResponseEntity<>(map, HttpStatus.OK);
}
@GetMapping("/hello") // get /api/hello
public ResponseEntity<Map<String,String>> Hello() { // ResponseEntity 리턴타입 Map 키와 값을 하나의 쌍으로 저장
Map<String,String> map = new HashMap<>(); // map 선언, HashMap은 Map 인터페이스를 구현한 대표적인 Map 컬렉션
map.put("result", "hello world"); // map에 값 넣기
return new ResponseEntity<>(map, HttpStatus.OK); // 생성자로 ResponseEntity를 만들어서 map이랑 status클라이언트에 보내줌
}
}
DemoApplication.java
package daily.coding.demo;
import daily.coding.models.*;
import daily.coding.service.*;
import daily.coding.controller.ExController;
import org.apache.ibatis.type.MappedTypes;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan; //컴포넌트 스캔 안하면 컨트롤러 못 읽음
@MappedTypes(User.class)
@MapperScan("daily.coding.mapper")
@SpringBootApplication
@ComponentScan(basePackageClasses={ExController.class, Bcrypt.class}) //컨트롤러 읽기
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
위의 내용을 추가해주시면 됩니다.
성공적으로 서버가 켜졌고 이제 postman으로 확인해보겠습니다.
db에 제대로 암호화가 적용되서 저장되었네요.
로그인 할 때, 해쉬 패스워드와 입력받은 패스워드를 비교하여 로그인을 성공시켜주는 로직도 작성하겠습니다.
그러기 위해선 디비에서 가져온 패스워드를 담을 모델도 필요하겠죠.
models안에 ReqUser.java 파일을 생성하여 아래와 같이 작성합니다.
package daily.coding.models;
public class ReqUser {
private String id;
private String password;
public ReqUser() {
super();
}
public ReqUser(String id, String password) {
super();
this.id = id;
this.password = password;
}
public String getId() {
return id;
}
public String getPassword() {
return password;
}
public void setId(String id) {
this.id = id;
}
public void setPassword(String password) {
this.password = password;
}
}
이제 ExController.java 안에 아래와 같은 로직을 추가하고 포스트맨으로 확인해보겠습니다.
@PostMapping("/login")
public ResponseEntity<Map<String, String>> Login(@RequestBody ReqUser req) {
User user = new User();
user = userMapper.findOne(req.getId());
Boolean result = bcrypt.CompareHash(req.getPassword(), user.getPassword());
Map<String,String> map = new HashMap<>();
if (result) {
map.put("result", "success");
return new ResponseEntity<>(map, HttpStatus.OK);
} else {
map.put("result", "password is not correct");
return new ResponseEntity<>(map, HttpStatus.OK);
}
}
그럼 비밀번호가 일치하면 success가 나올것이고 일치하지 않으면 password is not correct가 나올겁니다.
성공적이네요.
jwt도 적용방식은 똑같습니다.
JWTManager.java
package daily.coding.service;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.crypto.DefaultJwtSignatureValidator;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import static io.jsonwebtoken.SignatureAlgorithm.HS256;
@Service
public class JWTManager {
private final String AccessKey = "AccessKey_SECRET";
private final Long expiredTime = 1000 * 60L * 60L * 24L * 365L; // 1년
///////////////create////////////////////////////
public String CreateToken(String username) {
Date now = new Date();
return Jwts.builder()
.setSubject(username)
.setHeader(createHeader())
.setClaims(createClaims(username))
.setExpiration(new Date(now.getTime() + expiredTime))
.signWith(SignatureAlgorithm.HS256, AccessKey)
.compact();
}
private Map<String, Object> createHeader() {
Map<String, Object> header = new HashMap<>();
header.put("typ", "JWT");
header.put("alg", "HS256");
header.put("regDate", System.currentTimeMillis());
return header;
}
private Map<String, Object> createClaims(String username) {
Map<String, Object> claims = new HashMap<>();
claims.put("username", username);
return claims;
}
/////////////////decoded////////////////////////
private Claims getClaims(String token) {
//return Jwts.parser().setSigningKey(Base64.getEncoder().encodeToString(AccessKey.getBytes())).parseClaimsJws(token).getBody();
return Jwts.parser().setSigningKey(AccessKey).parseClaimsJws(token).getBody();
}
public String VerifyToken(String token) {
return (String) getClaims(token).get("username");
}
}
DemoApplication.java
package daily.coding.demo;
import daily.coding.models.*;
import daily.coding.service.*;
import daily.coding.controller.ExController;
import org.apache.ibatis.type.MappedTypes;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan; //컴포넌트 스캔 안하면 컨트롤러 못 읽음
@MappedTypes(User.class)
@MapperScan("daily.coding.mapper")
@SpringBootApplication
@ComponentScan(basePackageClasses={ExController.class, Bcrypt.class, JWTManager.class}) //컨트롤러 읽기
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
이제 ExController.java의 로그인 로직에 jwt를 추가합니다.
로그인이 성공할 시, 토큰을 발급해주는 로직만 추가해주면 됩니다.
결과값으로 토큰을 받도록 하겠습니다.
로그인 로직은 아래와 같이 될 것이고,
@PostMapping("/login")
public ResponseEntity<Map<String, String>> Login(@RequestBody ReqUser req) {
User user = new User();
user = userMapper.findOne(req.getId());
Boolean result = bcrypt.CompareHash(req.getPassword(), user.getPassword());
Map<String,String> map = new HashMap<>();
if (result) {
String token = jwt.CreateToken(user.getId());
map.put("result", token);
return new ResponseEntity<>(map, HttpStatus.OK);
} else {
map.put("result", "password is not correct");
return new ResponseEntity<>(map, HttpStatus.OK);
}
}
전체코드로 보면 이렇습니다.
package daily.coding.controller; // 자기 위치를 명시
import daily.coding.models.*;
import daily.coding.mapper.*;
import daily.coding.service.*;
// 프레임워크에 탑재된 기능 가져오기
import org.springframework.web.bind.annotation.*;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
// 자바 가지고 있는 내장 모듈
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.List;
import java.util.Iterator;
@RestController // restApi를 작성할 수 있는 컨트롤러
@RequestMapping("/api") // url을 api로 지정
@CrossOrigin(origins="*", allowedHeaders="*") // cors허용
public class ExController {
private UserMapper userMapper;
private Bcrypt bcrypt;
private JWTManager jwt;
public ExController(UserMapper userMapper, JWTManager jwt, Bcrypt bcrypt) {
this.userMapper = userMapper;
this.bcrypt = bcrypt;
this.jwt = jwt;
}
// create
@PostMapping("/user")
public ResponseEntity<Map<String,String>> CreateUser(@RequestBody User req) {
String hashpassword = bcrypt.HashPassword(req.getPassword());
req.setPassword(hashpassword);
userMapper.Create(req);
Map<String,String> map = new HashMap<>();
map.put("result", "success");
return new ResponseEntity<>(map, HttpStatus.OK);
}
// read
@GetMapping("/users")
public List<User> AllUser() {
return userMapper.findAll();
}
@PostMapping("/login")
public ResponseEntity<Map<String, String>> Login(@RequestBody ReqUser req) {
User user = new User();
user = userMapper.findOne(req.getId());
Boolean result = bcrypt.CompareHash(req.getPassword(), user.getPassword());
Map<String,String> map = new HashMap<>();
if (result) {
String token = jwt.CreateToken(user.getId());
map.put("result", token);
return new ResponseEntity<>(map, HttpStatus.OK);
} else {
map.put("result", "password is not correct");
return new ResponseEntity<>(map, HttpStatus.OK);
}
}
// update
@PostMapping("/update")
public ResponseEntity<Map<String,String>> UpdateUser(@RequestBody User req) {
userMapper.Update(req);
Map<String,String> map = new HashMap<>();
map.put("result", "success");
return new ResponseEntity<>(map, HttpStatus.OK);
}
// delete
@PostMapping("/delete")
public ResponseEntity<Map<String,String>> DeleteUser(@RequestBody User req) {
userMapper.Delete(req);
Map<String,String> map = new HashMap<>();
map.put("result", "success");
return new ResponseEntity<>(map, HttpStatus.OK);
}
@GetMapping("/hello") // get /api/hello
public ResponseEntity<Map<String,String>> Hello() { // ResponseEntity 리턴타입 Map 키와 값을 하나의 쌍으로 저장
Map<String,String> map = new HashMap<>(); // map 선언, HashMap은 Map 인터페이스를 구현한 대표적인 Map 컬렉션
map.put("result", "hello world"); // map에 값 넣기
return new ResponseEntity<>(map, HttpStatus.OK); // 생성자로 ResponseEntity를 만들어서 map이랑 status클라이언트에 보내줌
}
}
포스트맨으로 확인해보면
성공적이네요~!^_^