Skip to content

Commit

Permalink
Merge pull request #6 from NadoYagsa/feat/1-login
Browse files Browse the repository at this point in the history
Feat/1 login
  • Loading branch information
youngniw authored Apr 17, 2022
2 parents b335fca + 979bf64 commit e04f13a
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 0 deletions.
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ dependencies {
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
runtimeOnly 'mysql:mysql-connector-java'

implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.2'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.2'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.nadoyagsa.pillaroid.configuration;

import com.nadoyagsa.pillaroid.jwt.AuthInterceptor;
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;

@Configuration
public class SecurityConfiguration implements WebMvcConfigurer {
private final AuthInterceptor authInterceptor;

@Autowired
public SecurityConfiguration(AuthInterceptor authInterceptor) {
this.authInterceptor = authInterceptor;
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
//토큰 검사 안하는 경로 설정함(/login/**, 알약 검색 등)
//TODO: 알약 검색 시 patterns 사용해서 경로 추가해야 함
registry.addInterceptor(authInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(new String[]{"/login/**"});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.nadoyagsa.pillaroid.controller;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nadoyagsa.pillaroid.entity.User;
import com.nadoyagsa.pillaroid.jwt.AuthTokenProvider;
import com.nadoyagsa.pillaroid.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

@RestController
@RequestMapping(value = "/login")
public class LoginController {
private final UserService userService;
private final AuthTokenProvider authTokenProvider;

@Autowired
public LoginController(UserService userService, AuthTokenProvider authTokenProvider) {
this.userService = userService;
this.authTokenProvider = authTokenProvider;
}

// 카카오 로그인 (Input: access token)
@PostMapping("/kakao")
public ResponseEntity<Map<String, Object>> kakaoLogin(@RequestBody Map<String, String> requestBody) {
String accessToken = requestBody.get("access_token");

HashMap<String, Object> response = new HashMap<>(); // 서버->클라이언트 응답

RestTemplate restTemplate = new RestTemplate(); // Spring의 HTTP 통신 템플릿
// 카카오로 보낼 헤더 설정
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Bearer " + accessToken);
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
// 카카오와의 통신
HttpEntity<MultiValueMap<String, String>> kakaoUserInfoRequest = new HttpEntity<>(headers);
ResponseEntity<String> kakaoResponse;
try {
kakaoResponse = restTemplate.exchange(
"https://kapi.kakao.com/v2/user/me",
HttpMethod.POST,
kakaoUserInfoRequest,
String.class
);
} catch (Exception e) { // 카카오로 요청 실패
response.put("success", false);
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); // Status Code=400
}

ObjectMapper objectMapper = new ObjectMapper();
Long kakaoUserId;
try {
JsonNode userInfo = objectMapper.readTree(kakaoResponse.getBody());
kakaoUserId = userInfo.path("id").asLong();
} catch (JsonProcessingException e) {
response.put("success", false);
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); // Status Code=500
}

String authToken = authTokenProvider.createAuthToken(kakaoUserId);
Optional<User> user = userService.findUserByKakaoAccountId(kakaoUserId);
// 클라이언트의 로그인 경험 있음
if (user.isPresent()) {
response.put("success", true);
response.put("authToken", authToken);
//response.put("user", user);
return new ResponseEntity<>(response, HttpStatus.OK); // Status Code=200
}
// 클라이언트의 로그인 경험 없음(DB에 사용자 추가)
else {
User newUser = userService.signUp(new User(kakaoUserId));

response.put("success", true);
response.put("authToken", authToken);
//response.put("user", newUser);
return new ResponseEntity<>(response, HttpStatus.CREATED); // Status Code=201
}
}

// 자동로그인은 시각장애인을 위해 프론트에서 authToken값 존재 여부에 따라 수행됨
}
30 changes: 30 additions & 0 deletions src/main/java/com/nadoyagsa/pillaroid/jwt/AuthInterceptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.nadoyagsa.pillaroid.jwt;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class AuthInterceptor implements HandlerInterceptor {
private final AuthTokenProvider authTokenProvider;

@Autowired
public AuthInterceptor(AuthTokenProvider authTokenProvider) {
this.authTokenProvider = authTokenProvider;
}

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("authorization");
if (token != null && authTokenProvider.validateToken(token)) {
return true;
} else {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
return false;
}
}
}
70 changes: 70 additions & 0 deletions src/main/java/com/nadoyagsa/pillaroid/jwt/AuthTokenProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.nadoyagsa.pillaroid.jwt;

import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Date;

@Component
public class AuthTokenProvider implements InitializingBean {
private final Logger logger = LoggerFactory.getLogger(AuthTokenProvider.class);

private final String secretKey;
private Key key;

public AuthTokenProvider(@Value("${jwt.secret-key}") String secretKey) {
this.secretKey = secretKey;
}

@Override
public void afterPropertiesSet() {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
this.key = Keys.hmacShaKeyFor(keyBytes);
}

// 토큰의 payload에 카카오 회원번호를 삽입
public String createAuthToken(Long kakaoAccountId) {
return Jwts.builder()
.setHeaderParam(Header.TYPE, Header.JWT_TYPE)
.setIssuer("pillaroid")
.setIssuedAt(new Date())
.claim("accountId", kakaoAccountId)
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}

// 토큰으로부터 payload를 추출하는 메서드
public Claims getClaims(String token) {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
}

public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);

return true;
} catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
logger.info("잘못된 JWT 서명입니다.");
} catch (ExpiredJwtException e) {
logger.info("만료된 JWT 토큰입니다.");
} catch (UnsupportedJwtException e) {
logger.info("지원되지 않는 JWT 토큰입니다.");
} catch (IllegalArgumentException e) {
logger.info("JWT 토큰이 잘못되었습니다.");
} catch (Exception e) {
logger.info("서비스에 접근할 수 없는 토큰입니다.");
}
return false;
}
}

0 comments on commit e04f13a

Please sign in to comment.