jwt를 사용한 api 인증
Springboot 기준 Jwt api 인증
- 정상적인 로그인 절차를 거친 클라이언트에게 토큰을 지급한다
- 클라이언트는 인증 받은 토큰을 헤더에 Authorization 필드로 저장 후 통신을 진행한다.
- 모든 컨트롤러 요청은 Interceptor를 통해 토큰인증 절차를 진행한다.
maven dependency
<!-- jwt token authorization -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
jwt 클래스
인증토큰을 발급하거나 검증하는데 사용한다.
import java.io.UnsupportedEncodingException;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.yourpakage.common.UnauthorizedException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
@Component
public class JwtUtil {
private static final String SALT = "yoursecretkey";
public <T> String create(String key, T data, String subject) {
String jwt = Jwts.builder().setHeaderParam("typ", "JWT").setHeaderParam("regDate", System.currentTimeMillis())
.setSubject(subject).claim(key, data).signWith(SignatureAlgorithm.HS256, this.generateKey()).compact();
return jwt;
}
private byte[] generateKey() {
byte[] key = null;
try {
key = SALT.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return key;
}
public boolean isUsable(String jwt) {
try{
Jws<Claims> claims = Jwts.parser()
.setSigningKey(this.generateKey())
.parseClaimsJws(jwt);
return true;
}catch (Exception e) {
e.printStackTrace();
return false;
}
}
public Map<String, Object> get(String key) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
String jwt = request.getHeader("Authorization");
Jws<Claims> claims = null;
try {
claims = Jwts.parser()
.setSigningKey(SALT.getBytes("UTF-8"))
.parseClaimsJws(jwt);
} catch (Exception e) {
throw new UnauthorizedException();
}
@SuppressWarnings("unchecked")
Map<String, Object> value = (LinkedHashMap<String, Object>)claims.getBody().get(key);
return value;
}
}
사용자 지정 UnauthorizedException 클래스
public class UnauthorizedException extends RuntimeException{
private static final long serialVersionUID = -2238030302650813813L;
public UnauthorizedException() {
super("계정 권한이 유효하지 않습니다.\n다시 로그인을 해주세요.");
}
}
interceptor 추가
모든 컨트롤러 요청을 preHandle 매소드를 통해 거쳐가도록 설정
실질적인 토큰 인증을 진행한다.
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import com.yourpakage.common.util.JwtUtil;
@Component
public class AuthInterceptor implements HandlerInterceptor {
private static final String HEADER_AUTH = "Authorization";
@Autowired
private JwtUtil jwtUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
final String token = request.getHeader(HEADER_AUTH);
if (token != null && !token.equals("")) {
if (jwtUtil.isUsable(token))
return true;
} else {
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
JSONObject resultJson = new JSONObject();
resultJson.put("result_code", ResultStatus.UNAUTHORIZED.value());
resultJson.put("err_msg", "Authentication has been expired");
try {
response.getWriter().write(resultJson.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
return false;
}
}
Webconfig 클래스 설정 추가
로그인이나 회원가입, 외부 도메인 callback(PG사 연동) 등의 인증 제외 URI를 설정한다.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.yourpakage.common.AuthInterceptor;
@Configuration
public class WebConfig implements WebMvcConfigurer{
private static final String[] EXCLUDE_PATHS = {
"your_exclude_path1","your_exclude_path2","your_exclude_path3"
};
@Autowired
private AuthInterceptor authInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(EXCLUDE_PATHS);
}
}