JWT(Json Web Token)
Json 객체를 통해 안전하게 정보를 전송할 수 있는 웹표준
Json 객체를 암호화하여 만든 String 값
기본적으로 암호화가 되어 있어 변조하기 어려움
JWT를 이용하여 로그인 구현
1. gradle dependency 추가
- build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'io.jsonwebtoken:jjwt:0.9.1'
}
2. 비밀키 설정
- application.properties
jwt.secret=thisiskey
3. JwtTokenProvider 생성
Jwt 생성하고, 유효성을 검증하는 컴포넌트
@RequiredArgsConstructor
@Component
public class JwtTokenProvider {
@Value("${jwt.secret}")
private String secretKey;
// 토큰 유효 시간(30분)
private long defaultTokenValidTime = 30 * 60 * 1000L;
private final UserDetailsService userDetailsService;
// 객체 초기화
// secretKey를 Base64로 인코딩
@PostConstruct
protected void init() {
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
}
// JWT 토큰 생성
public String createToken(String userPk) {
Claims claims = Jwts.claims().setSubject(userPk);
Date now = new Date();
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(new Date(now.getTime() + defaultTokenValidTime))
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
// JWT 토큰 생성(유효시간 지정)
public String createToken(String userPk, long tokenValidTime) {
Claims claims = Jwts.claims().setSubject(userPk);
Date now = new Date();
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(new Date(now.getTime() + tokenValidTime))
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
// JWT 토큰에서 인증 정보 조회
public Authentication getAuthentication(String token) {
UserDetails userDetails = userDetailsService.loadUserByUsername(this.getUserPk(token));
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}
// 토큰에서 회원 정보 추출
public String getUserPk(String token) {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
}
// Request의 header에서 token 값을 가져옴
// "X-AUTH-TOKEN" : "token값"
public String resolveToken(HttpServletRequest request) {
return request.getHeader("X-AUTH-TOKEN");
}
// token 유효성, 만료 일자 확인
public boolean validateToken(String jwtToken) {
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwtToken);
return !claims.getBody().getExpiration().before(new Date());
} catch (Exception e) {
return false;
}
}
}
4. JwtAuthenticationFilter 생성
Jwt가 유효한 token인지 인증하기 위한 필터
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends GenericFilterBean {
private final JwtTokenProvider jwtTokenProvider;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// header에서 JWT를 받아옴
String token = jwtTokenProvider.resolveToken((HttpServletRequest) request);
// 유효한 token인지 확인
if(token != null && jwtTokenProvider.validateToken(token)) {
// token이 유효하면 token으로부터 유저 정보 받아옴
Authentication authentication = jwtTokenProvider.getAuthentication(token);
// SecurityContext에 Authentication 객체를 저장함
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
}
GenericFilterBean
기존 Filter에서 얻어올 수 없는 정보였던 Spring의 설정 정보를 가져올 수 있게 확장된 추상 클래스
5. 로그인 성공 시, Jwt Token 생성 후 return
@Service
public class MemberService {
private MemberRepository memberRepository;
private BCryptPasswordEncoder passwordEncoder;
private JwtTokenProvider jwtTokenProvider;
private ApiResponseService apiResponseService;
public MemberService(MemberRepository memberRepository, BCryptPasswordEncoder passwordEncoder,
JwtTokenProvider jwtTokenProvider, ApiResponseService apiResponseService) {
super();
this.memberRepository = memberRepository;
this.passwordEncoder = passwordEncoder;
this.jwtTokenProvider = jwtTokenProvider;
this.apiResponseService = apiResponseService;
}
// 로그인
public String login(MemberLoginDto loginInfo) {
ApiResponseModel apiResponse = null;
MemberEntity member = null;
try {
// 전달 받은 Id로 회원 조회
Optional<MemberEntity> memberEntity = memberRepository.findById(loginInfo.getUserId());
// 일치하는 Id가 없을 때
if(memberEntity.isEmpty()) {
apiResponse = apiResponseService.getApiResponse("fail", "가입되지 않은 Id 입니다.", "");
}
// 일치하는 Id가 있을 때
else {
member = memberEntity.get();
// 비밀번호가 틀렸을 때
if (!passwordEncoder.matches(loginInfo.getPassword(), member.getPassword())) {
apiResponse = apiResponseService.getApiResponse("fail", "잘못된 비밀번호입니다.", "");
}
// 비밀번호가 일치했을 때
else {
// 로그인 성공 시 token 발행 후 결과로 전송할 TokenInfoDto에 저장
TokenInfoModel jwtToken = TokenInfoModel.builder()
.userId(member.getUserId())
.token(jwtTokenProvider.createToken(member.getUserId()))
.build();
apiResponse = apiResponseService.getApiResponse("success", "", jwtToken);
}
}
} catch (Exception e) {
apiResponse = apiResponseService.getApiResponse("fail", "로그인에 실패하였습니다.", "");
}
return (new Gson()).toJson(apiResponse);
}
}
jwtTokenProvider.createToken(member.getUserId())
회원 ID(userId)를 넣어 JwtToken 생성
6. Token을 이용하여 접근 권한 체크
@RequiredArgsConstructor
@RestController
public class TestRestApiController {
private final JwtTokenProvider jwtTokenProvider;
private final MemberService memberService;
private final ApiResponseService apiResponseService;
@ApiOperation(value="내 정보 보기", notes="jwt token을 받아 권한이 있는 회원만 본인의 정보 열람 가능")
@ApiImplicitParams({
@ApiImplicitParam(name="X-AUTH-TOKEN", value="로그인 성공 후 발급 받은 token", dataType="String", paramType="header", required=true)
})
@PostMapping("/test/me")
public String me(HttpServletRequest request) {
String jwtToken = jwtTokenProvider.resolveToken(request);
ApiResponseModel apiResponse = null;
// token 확인 후 유효한 token일 때
if(jwtTokenProvider.validateToken(jwtToken)) {
String userId = jwtTokenProvider.getUserPk(jwtToken);
MemberDto member = memberService.getMemberInfo(userId);
if(member == null) apiResponse = apiResponseService.getApiResponse("fail", "회원 정보 조회에 실패하였습니다.", "");
else apiResponse = apiResponseService.getApiResponse("success", "", member);
}
// token이 유효하지 않을 때
else {
apiResponse = apiResponseService.getApiResponse("fail", "token이 유효하지 않습니다.", "");
}
return (new Gson()).toJson(apiResponse);
}
}
X-AUTH-TOKEN
header에 X-AUTH-TOKEN라는 이름으로 담아 전송
jwtTokenProvider.resolveToken(request)
header에 X-AUTH-TOKEN라는 이름으로 전송받은 Jwt Token 추출
jwtTokenProvider.validateToken(jwtToken)
Jwt Token 유효성 체크
jwtTokenProvider.getUserPk(jwtToken)
Jwt Token에 담긴 정보 가져오기
- SpringBoot2로 Rest api 만들기(8) – SpringSecurity 를 이용한 인증 및 권한부여
https://daddyprogrammer.org/post/636/springboot2-springsecurity-authentication-authorization
- SPRING SECURITY + JWT 회원가입, 로그인 기능 구현
https://webfirewood.tistory.com/115
'Programming > Spring Boot' 카테고리의 다른 글
[Spring Boot] Spring Security PasswordEncoder (0) | 2022.02.15 |
---|---|
[Spring Boot] Swagger 3.x 적용 및 접속 (0) | 2022.02.15 |
[Spring Boot] Jackson과 Gson (0) | 2022.02.15 |
[Spring Boot] H2 Database 설치 및 접속 (0) | 2022.02.15 |
[Spring Boot] thymeleaf-layout-dialect 적용하기 (0) | 2021.12.28 |