시작하기 전에 대략적인 폴더 구조는 다음과 같다.
여기에 config 파일까지 하면 로그인 완성이다!
먼저 프론트에서 github api를 사용해서 로그인을 한 후, github에서 redirect-url로 보내준 code를 백엔드로 던져준다.
그럼 백엔드에서는 code를 사용해서 github api를 사용해서 token과 사용자 정보를 받는다.
받은 token을 그대로 사용하면 보안 문제가 있을 것 같아서 jwt token을 자체적으로 만들어서 사용하는 로직을 짰다.
0. UserDto, UserRepository, JwtService
UserDto, UserRepository, JwtService는 아래와 같이 구현했다.
OauthService 참고용..!
1. UserDto
package com.d103.dddev.api.user.repository.dto;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.DynamicInsert;
import com.d103.dddev.api.common.oauth2.Role;
import com.d103.dddev.api.file.repository.dto.ProfileDto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Entity(name = "user")
@Getter
@Setter
@DynamicInsert
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class UserDto {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@OneToOne
@JoinColumn(name = "profile_id")
private ProfileDto profileDto;
@Column(name = "github_id")
private Integer githubId;
private String nickname;
@Column(name = "status_msg")
private String statusMsg;
@CreationTimestamp
@JoinColumn(name = "create_time")
private Date createTime;
private Boolean valid;
@Column(name = "refresh_token")
private String refreshToken;
@Column(name = "personal_access_token")
private String personalAccessToken;
@Enumerated(EnumType.STRING)
private Role role;
public void updateRefreshToken(String updateRefreshToken){
this.refreshToken = updateRefreshToken;
}
}
2. UserRepository
package com.d103.dddev.api.user.repository;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import com.d103.dddev.api.user.repository.dto.UserDto;
public interface UserRepository extends JpaRepository<UserDto, Integer> {
Optional<UserDto> findByGithubId(Integer githubId);
Optional<UserDto> findByRefreshToken(String refreshToken);
Optional<UserDto> findByIdNotAndNickname(Integer id, String nickname); // id != not and nickname = nickname
}
3. JwtService
해당 코드는 아래 블로그를 참고하였다..!
package com.d103.dddev.api.common.oauth2.utils;
import java.util.Date;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.d103.dddev.api.user.repository.UserRepository;
import com.d103.dddev.api.user.repository.dto.UserDto;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Service
@RequiredArgsConstructor
@Getter
@Slf4j
public class JwtService {
@Value("${jwt.secretKey}")
private String secretKey;
@Value("${jwt.access.expiration}")
private Long accessTokenExpirationPeriod;
@Value("${jwt.refresh.expiration}")
private Long refreshTokenExpirationPeriod;
@Value("${jwt.access.header}")
private String accessHeader;
@Value("${jwt.refresh.header}")
private String refreshHeader;
/**
* JWT의 Subject와 Claim으로 email 사용 -> 클레임의 name을 "email"으로 설정
* JWT의 헤더에 들어오는 값 : 'Authorization(Key) = Bearer {토큰} (Value)' 형식
*/
private static final String ACCESS_TOKEN_SUBJECT = "AccessToken";
private static final String REFRESH_TOKEN_SUBJECT = "RefreshToken";
private static final String GITHUB_CLAIM = "githubId";
private static final String BEARER = "Bearer ";
private final UserRepository userRepository;
/**
* AccessToken 생성 메소드
*/
public String createAccessToken(Integer githubId) {
Date now = new Date();
return JWT.create() // JWT 토큰을 생성하는 빌더 반환
.withSubject(ACCESS_TOKEN_SUBJECT) // JWT의 subject 지정 -> accessToken
.withExpiresAt(new Date(now.getTime() + accessTokenExpirationPeriod)) // 토큰 만료 시간 설정
//클레임으로 uid 사용.
//추가적으로 식별자나, 이름 등의 정보를 더 추가하셔도 됩니다.
//추가하실 경우 .withClaim(클래임 이름, 클래임 값) 으로 설정해주시면 됩니다
.withClaim(GITHUB_CLAIM, githubId) // 깃허브 아이디 클레임
.sign(Algorithm.HMAC512(secretKey)); // HMAC512 알고리즘 사용, application-jwt.yml에서 지정한 secret 키로 암호화
}
/**
* RefreshToken 생성
* RefreshToken은 Claim에 email도 넣지 않으므로 withClaim() X
*/
public String createRefreshToken() {
Date now = new Date();
return JWT.create()
.withSubject(REFRESH_TOKEN_SUBJECT)
.withExpiresAt(new Date(now.getTime() + refreshTokenExpirationPeriod))
.sign(Algorithm.HMAC512(secretKey));
}
/**
* AccessToken 헤더에 실어서 보내기
*/
public void sendAccessToken(HttpServletResponse response, String accessToken) {
response.setStatus(HttpServletResponse.SC_OK);
response.setHeader(accessHeader, accessToken);
log.info("재발급된 Access Token : {}", accessToken);
}
/**
* AccessToken + RefreshToken 헤더에 실어서 보내기
*/
public void sendAccessAndRefreshToken(HttpServletResponse response, String accessToken, String refreshToken) {
response.setStatus(HttpServletResponse.SC_OK);
setAccessTokenHeader(response, accessToken);
setRefreshTokenHeader(response, refreshToken);
log.info("sendAccessAndRefreshToken :: Access Token, Refresh Token 헤더 설정 완료");
}
/**
* 헤더에서 RefreshToken 추출
* 토큰 형식 : Bearer XXX에서 Bearer를 제외하고 순수 토큰만 가져오기 위해서
* 헤더를 가져온 후 "Bearer"를 삭제(""로 replace)
*/
public Optional<String> extractRefreshToken(HttpServletRequest request) {
return Optional.ofNullable(request.getHeader(refreshHeader))
.filter(refreshToken -> refreshToken.startsWith(BEARER))
.map(refreshToken -> refreshToken.replace(BEARER, ""));
}
/**
* 헤더에서 AccessToken 추출
* 토큰 형식 : Bearer XXX에서 Bearer를 제외하고 순수 토큰만 가져오기 위해서
* 헤더를 가져온 후 "Bearer"를 삭제(""로 replace)
*/
public Optional<String> extractAccessToken(HttpServletRequest request) {
log.info("extractAccessToken");
//System.out.println(request.getHeader(accessHeader));
return Optional.ofNullable(request.getHeader(accessHeader))
.filter(accessToken -> accessToken.startsWith(BEARER))
.map(accessToken -> accessToken.replace(BEARER, ""));
}
/**
* Bearer __________형식으로 되어있는 accessToken에서 Bearer를 제거하는 함수
* */
public Optional<String> extractAccessHeaderToToken(String Authorization) {
if (Authorization.startsWith(BEARER)) {
return Optional.ofNullable(Authorization)
.map(token -> token.replace(BEARER, ""));
} else {
return Optional.of(Authorization);
}
// return Optional.ofNullable(accessToken)
// .filter(token -> token.startsWith(BEARER))
// .map(token -> token.replace(BEARER, ""));
}
/**
* AccessToken에서 githubId 추출
* 추출 전에 JWT.require()로 검증기 생성
* verify로 AceessToken 검증 후
* 유효하다면 getClaim()으로 githubId 추출
* 유효하지 않다면 빈 Optional 객체 반환
*/
public Optional<Integer> extractGithubId(String Authorization) {
try {
String accessToken = extractAccessHeaderToToken(Authorization).get();
return Optional.ofNullable(JWT.require(Algorithm.HMAC512(secretKey))
.build()
.verify(accessToken)
.getClaim(GITHUB_CLAIM)
.asInt());
} catch (Exception e) {
log.error("extractEmail :: 액세스 토큰이 유효하지 않습니다.");
e.printStackTrace();
return Optional.empty();
}
}
public Optional<UserDto> getUser(String Authorization) throws Exception {
Integer githubId = extractGithubId(Authorization).orElseThrow(() -> new NoSuchFieldException("깃허브 아이디가 없습니다."));
return userRepository.findByGithubId(githubId);
}
/**
* AccessToken 헤더 설정
*/
public void setAccessTokenHeader(HttpServletResponse response, String accessToken) {
response.setHeader(accessHeader, accessToken);
}
/**
* RefreshToken 헤더 설정
*/
public void setRefreshTokenHeader(HttpServletResponse response, String refreshToken) {
response.setHeader(refreshHeader, refreshToken);
}
/**
* RefreshToken DB 저장(업데이트)
*/
public void updateRefreshToken(Integer id, String refreshToken) {
userRepository.findById(id)
.ifPresentOrElse(
user -> {
user.updateRefreshToken(refreshToken);
userRepository.saveAndFlush(user);
},
() -> new Exception("updateRefreshToken :: 일치하는 회원이 없습니다.")
);
}
public boolean isTokenValid(String token) {
try {
JWT.require(Algorithm.HMAC512(secretKey)).build().verify(token);
return true;
} catch (Exception e) {
log.error("유효하지 않은 토큰입니다. {}", e.getMessage());
return false;
}
}
}
1. 프론트에서 받은 code로 github에서 토큰 받기
1. Oauth2Service.java
application.properties에 선언한 변수들을 불러오고, api 호출에 사용할 변수를 선언한다.
login() 전체 코드
public Map<String, String> login(String code) throws Exception {
log.info("service - login :: github api login 진입");
// github에서 access, refresh token 받아오기
Map<String, String> response = githubToken(code);
String githubAccessToken = response.get("access_token");
String githubRefreshToken = response.get("refresh_token");
System.out.println(githubAccessToken);
// 사용자 정보 받아오기
Map<String, Object> userInfo = getUserInfo(githubAccessToken);
//System.out.println(userInfo);
String name = (String)userInfo.get("login");
Integer githubId = (Integer)userInfo.get("id");
// jwt로 자체 access, refresh token 만들기
String accessToken = BEARER + jwtService.createAccessToken(githubId);
String refreshToken = BEARER + jwtService.createRefreshToken();
UserDto userDto = getUser(githubId).orElseGet(() -> saveUser(userInfo));
// refresh token db 저장
updateRefreshToken(userDto, refreshToken);
// access, refresh token, 이름
Map<String, String> map = new HashMap<>();
map.put("Authorization", accessToken);
map.put("Authorization-refresh", refreshToken);
map.put("name", name);
map.put("role", String.valueOf(userDto.getRole()));
log.info("login :: github api login 성공");
return map;
}
github에서 access, refresh token을 받아오는 githubToken() 메소드를 호출한다.
받아온 access, refresh 토큰을 각각 githubAccessToken, githubRefreshToken에 저장한다.
사실 refresh token은 사용하지는 않지만 일단 변수로 빼놓았다!
// github에서 access, refresh token 받아오기
Map<String, String> response = githubToken(code);
String githubAccessToken = response.get("access_token");
String githubRefreshToken = response.get("refresh_token");
githubAccessToken을 사용해서 사용자 정보를 받아온다.
받아온 이름과 githubId를 변수에 저장한다.
// 사용자 정보 받아오기
Map<String, Object> userInfo = getUserInfo(githubAccessToken);
//System.out.println(userInfo);
String name = (String)userInfo.get("login");
Integer githubId = (Integer)userInfo.get("id");
githubId를 사용해서 jwt로 자체 access, refresh token을 만든다!
그리고 githubId를 사용해서 db에서 해당 githubId와 일치하는 사용자가 있으면 dto를 불러오고 없으면 db에 저장하고 dto를 반환한다!
그리고 refresh token을 db에 저장한다.
// jwt로 자체 access, refresh token 만들기
String accessToken = BEARER + jwtService.createAccessToken(githubId);
String refreshToken = BEARER + jwtService.createRefreshToken();
UserDto userDto = getUser(githubId).orElseGet(() -> saveUser(userInfo));
// refresh token db 저장
updateRefreshToken(userDto, refreshToken);
그리고 생성한 access token, refresh token, name, role을 map에 저장하고 반환한다.
자세한 부분은 추후 설명한댜!
// access, refresh token, 이름
Map<String, String> map = new HashMap<>();
map.put("Authorization", accessToken);
map.put("Authorization-refresh", refreshToken);
map.put("name", name);
map.put("role", String.valueOf(userDto.getRole()));
log.info("login :: github api login 성공");
return map;
githubToken 전체 코드 : code를 사용해서 github api를 호출해 access, refresh token을 받아오는 함수
api docs는 아래 링크의 3번 문항을 참고하면 된다! (찾는데 한참 걸렸다아...ㅜㅜ)
public Map<String, String> githubToken(String code) throws Exception {
log.info("service - gethubToken :: github에서 token 받아오기 진입");
RestTemplate restTemplate = new RestTemplate();
// header 만들기
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/x-www-form-urlencoded");
// body 만들기
MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
params.add("client_id", CLIENT_ID);
params.add("client_secret", CLIENT_SECRET);
params.add("code", code);
params.add("basicAuth", false);
// header랑 body 합치기
HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(params, headers);
// post 요청
ResponseEntity<Map<String, Object>> response = restTemplate.exchange(
ACCESS_TOKEN_REQUEST_URL,
HttpMethod.POST,
entity,
new ParameterizedTypeReference<Map<String, Object>>() {
}
);
Map<String, Object> responseBody = response.getBody();
Map<String, String> tokens = new HashMap<>();
tokens.put("access_token", (String)responseBody.get("access_token"));
tokens.put("refresh_token", (String)responseBody.get("refresh_token"));
return tokens;
}
RestTemplate 객체를 생성한다.
HttpHeaders 객체를 생성 후, content type을 아래와 같이 설정한다.
RestTemplate restTemplate = new RestTemplate();
// header 만들기
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/x-www-form-urlencoded");
body로 보낼 MultiValueMap 객체를 생성한다.
body에는 client_id, client_secret, code를 추가하고 basicAuth를 false로 설정한다.
client_id, client_secret은 사전설정에서 github app에서 받은 것을 말한다.
code는 프론트에서 받아온 것!
// body 만들기
MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
params.add("client_id", CLIENT_ID);
params.add("client_secret", CLIENT_SECRET);
params.add("code", code);
params.add("basicAuth", false);
HttpEntity에 header랑 body를 합친 후 post요청을 보낸다.
// header랑 body 합치기
HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(params, headers);
// post 요청
ResponseEntity<Map<String, Object>> response = restTemplate.exchange(
ACCESS_TOKEN_REQUEST_URL,
HttpMethod.POST,
entity,
new ParameterizedTypeReference<Map<String, Object>>() {
}
);
response는 다음과 같다!
{
access_token=ghu_{},
expires_in=28800,
refresh_token=ghr_{},
refresh_token_expires_in=15811200,
token_type=bearer,
scope=
}
response에서 access token과 refresh token을 가져와서 tokens라는 map에 넣고 return 한다.
Map<String, Object> responseBody = response.getBody();
Map<String, String> tokens = new HashMap<>();
tokens.put("access_token", (String)responseBody.get("access_token"));
tokens.put("refresh_token", (String)responseBody.get("refresh_token"));
return tokens;
2. 받아온 access token으로 사용자 정보 받아오기
getUserInfo 전체 코드
이건 아무리 찾아봐도.... 공식문서를 못찾아서... 헣.... chatGPT와 다른 블로그들의 도움을 받았당 히히
GET https://api.github.com/user
header : { "Authorization" : "Bearer" + {github access token} }
으로 요청하면 해당 사용자의 사용자 정보를 받아올 수 있다!
public Map<String, Object> getUserInfo(String githubAccessToken) throws Exception {
log.info("servie - getUserInfo :: github api로 사용자 정보 받아오기");
RestTemplate restTemplate = new RestTemplate();
String userInfoUrl = API_URL + "/user";
// header
HttpHeaders headers = new HttpHeaders();
String userInfoAccessToken = USER_INFO_REQUEST_TOKEN + githubAccessToken;
headers.add("Authorization", userInfoAccessToken);
// HttpEntity
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<Map<String, Object>> response = restTemplate.exchange(
userInfoUrl,
HttpMethod.GET,
entity,
new ParameterizedTypeReference<Map<String, Object>>() {
}
);
return response.getBody();
}
이전과 똑같이 RestTemplate을 선언한다.
userInfoUrl에 사용할 url을 넣고
headers에 access token을 넣어준댜
RestTemplate restTemplate = new RestTemplate();
String userInfoUrl = API_URL + "/user";
// header
HttpHeaders headers = new HttpHeaders();
String userInfoAccessToken = USER_INFO_REQUEST_TOKEN + githubAccessToken;
headers.add("Authorization", userInfoAccessToken);
header를 entity에 넣고 GET 요청을 보낸다.
// HttpEntity
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<Map<String, Object>> response = restTemplate.exchange(
userInfoUrl,
HttpMethod.GET,
entity,
new ParameterizedTypeReference<Map<String, Object>>() {
}
);
return response.getBody();
response.getBody()를 출력해보면 아래와 같이 나온다!
login이 github에 설정한 닉네임? 같은 느낌이고 id는 github에서 사용자별로 제공하는 id인 것 같다.
나는 우리 프로젝트에서 login을 닉네임으로 사용하고 id를 JwtService.java에서 jwt token에 넣어서 사용자 구분용도로 사용하였다.
{
login={사용자 이름},
id={github에서 제공하는 id},
node_id=__________,
avatar_url=https://avatars.githubusercontent.com/u/{id}?v=4,
gravatar_id=,
url=https://api.github.com/users/{login},
html_url=https://github.com/{login},
followers_url=https://api.github.com/users/{login}/followers,
following_url=https://api.github.com/users/{login}/following{/other_user},
gists_url=https://api.github.com/users/{login}/gists{/gist_id},
starred_url=https://api.github.com/users/{login}/starred{/owner}{/repo},
subscriptions_url=https://api.github.com/users/{login}/subscriptions,
organizations_url=https://api.github.com/users/{login}/orgs,
repos_url=https://api.github.com/users/{login}/repos,
events_url=https://api.github.com/users/{login}/events{/privacy},
received_events_url=https://api.github.com/users/{login}/received_events,
type=User,
site_admin=false,
name=null,
company=_____,
blog=,
location=null,
email=null,
hireable=null,
bio=null,
twitter_username=null,
public_repos=7,
public_gists=0,
followers=0,
following=0,
created_at=2017-11-24T01:04:27Z,
updated_at=2023-10-17T08:17:56Z
}
참고로 https://api.github.com/users/{login} 이걸로 요청보내면 해당 사용자의 사용자 정보를 간단하게 받아올 수 있다!
getUser() : githubId로 db에서 UserDto를 불러오는 메소드이다.
public Optional<UserDto> getUser(Integer githubId) throws Exception {
log.info("getUser :: DB에서 사용자 정보 가져오기");
return userRepository.findByGithubId(githubId);
}
saveUser() : getUserInfo에서 받아온 사용자 정보를 사용해서 DB에 저장하는 메소드이다.
public UserDto saveUser(Map<String, Object> userInfo) {
log.info("saveUser :: DB에 사용자 정보 저장");
String name = (String)userInfo.get("login");
Integer githubId = (Integer)userInfo.get("id");
UserDto user = UserDto.builder()
.nickname(name)
.githubId(githubId)
.valid(true)
.role(Role.GUEST)
.build();
return userRepository.save(user);
}
getUser()와 saveUser()는 login()에서 사용된다.
getUser()로 UserDto가 호출되면 기존 사용자이고, 없으면 새로 회원가입한 사용자이기 때문에 saveUser()로 사용자 정보를 저장해준다!
updateRefreshToken() : 로그인 후 refresh token을 db에 업데이트 해주는 메소드이다!
public void updateRefreshToken(UserDto user, String refreshToken) {
user.updateRefreshToken(refreshToken);
userRepository.saveAndFlush(user);
}
Oauth2Service.java 전체 코드
중간의 unlink는 로그아웃에 필요한 코드이다! 이건 로그아웃 포스팅에서 이야기할 것...!
package com.d103.dddev.api.common.oauth2.service;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import com.d103.dddev.api.common.oauth2.Role;
import com.d103.dddev.api.common.oauth2.utils.JwtService;
import com.d103.dddev.api.user.repository.UserRepository;
import com.d103.dddev.api.user.repository.dto.UserDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Service
@RequiredArgsConstructor
@Slf4j
public class Oauth2Service {
private final JwtService jwtService;
private final UserRepository userRepository;
private String ACCESS_TOKEN_REQUEST_URL = "https://github.com/login/oauth/access_token";
private String API_URL = "https://api.github.com";
private String USER_INFO_REQUEST_TOKEN = "token ";
private String BEARER = "Bearer ";
@Value("${spring.security.oauth2.client.registration.github.client-id}")
private String CLIENT_ID;
@Value("${spring.security.oauth2.client.registration.github.client-secret}")
private String CLIENT_SECRET;
@Value("${aes.secretKey}")
private String AES_SECRET_KEY;
public Map<String, String> login(String code) throws Exception {
log.info("service - login :: github api login 진입");
// github에서 access, refresh token 받아오기
Map<String, String> response = githubToken(code);
String githubAccessToken = response.get("access_token");
String githubRefreshToken = response.get("refresh_token");
System.out.println(githubAccessToken);
// 사용자 정보 받아오기
Map<String, Object> userInfo = getUserInfo(githubAccessToken);
System.out.println(userInfo);
String name = (String)userInfo.get("login");
Integer githubId = (Integer)userInfo.get("id");
// jwt로 자체 access, refresh token 만들기
String accessToken = BEARER + jwtService.createAccessToken(githubId);
String refreshToken = BEARER + jwtService.createRefreshToken();
UserDto userDto = getUser(githubId).orElseGet(() -> saveUser(userInfo));
// refresh token db 저장
updateRefreshToken(userDto, refreshToken);
// access, refresh token, 이름
Map<String, String> map = new HashMap<>();
map.put("Authorization", accessToken);
map.put("Authorization-refresh", refreshToken);
map.put("name", name);
map.put("role", String.valueOf(userDto.getRole()));
log.info("login :: github api login 성공");
return map;
}
public Map<String, String> githubToken(String code) throws Exception {
log.info("service - gethubToken :: github에서 token 받아오기 진입");
RestTemplate restTemplate = new RestTemplate();
// header 만들기
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/x-www-form-urlencoded");
// body 만들기
MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
params.add("client_id", CLIENT_ID);
params.add("client_secret", CLIENT_SECRET);
params.add("code", code);
params.add("basicAuth", false);
// header랑 body 합치기
HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(params, headers);
// post 요청
ResponseEntity<Map<String, Object>> response = restTemplate.exchange(
ACCESS_TOKEN_REQUEST_URL,
HttpMethod.POST,
entity,
new ParameterizedTypeReference<Map<String, Object>>() {
}
);
Map<String, Object> responseBody = response.getBody();
Map<String, String> tokens = new HashMap<>();
tokens.put("access_token", (String)responseBody.get("access_token"));
tokens.put("refresh_token", (String)responseBody.get("refresh_token"));
return tokens;
}
public Map<String, Object> getUserInfo(String githubAccessToken) throws Exception {
log.info("servie - getUserInfo :: github api로 사용자 정보 받아오기");
RestTemplate restTemplate = new RestTemplate();
String userInfoUrl = API_URL + "/user";
// header
HttpHeaders headers = new HttpHeaders();
String userInfoAccessToken = USER_INFO_REQUEST_TOKEN + githubAccessToken;
headers.add("Authorization", userInfoAccessToken);
// HttpEntity
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<Map<String, Object>> response = restTemplate.exchange(
userInfoUrl,
HttpMethod.GET,
entity,
new ParameterizedTypeReference<Map<String, Object>>() {
}
);
return response.getBody();
}
public Boolean unlink(String oauthAccessToken) throws Exception {
log.info("service - unlink :: github authorization 연결 끊기 진입");
RestTemplate restTemplate = new RestTemplate();
String deleteUrl = API_URL + "/applications/" + CLIENT_ID + "/grant";
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth(CLIENT_ID, CLIENT_SECRET);
headers.setContentType(MediaType.APPLICATION_JSON);
JSONObject requestBody = new JSONObject();
requestBody.put("access_token", oauthAccessToken);
HttpEntity<String> entity = new HttpEntity<>(requestBody.toString(), headers);
ResponseEntity<Object> response = restTemplate.exchange(
deleteUrl,
HttpMethod.DELETE,
entity,
Object.class
);
return response.getStatusCode().is2xxSuccessful();
}
public Optional<UserDto> getUser(Integer githubId) throws Exception {
log.info("getUser :: DB에서 사용자 정보 가져오기");
return userRepository.findByGithubId(githubId);
}
public UserDto saveUser(Map<String, Object> userInfo) {
log.info("saveUser :: DB에 사용자 정보 저장");
String name = (String)userInfo.get("login");
Integer githubId = (Integer)userInfo.get("id");
UserDto user = UserDto.builder()
.nickname(name)
.githubId(githubId)
.valid(true)
.role(Role.GUEST)
.build();
return userRepository.save(user);
}
public void updateRefreshToken(UserDto user, String refreshToken) {
user.updateRefreshToken(refreshToken);
userRepository.saveAndFlush(user);
}
}
3. Oauth2Controller.java
controller에서는 프론트에서 던져준 code를 받아서 프론트에게 jwt 토큰과 id, 그리고 name을 헤더에 넣어서 return해준당!
package com.d103.dddev.api.common.oauth2.controller;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.d103.dddev.api.common.oauth2.service.Oauth2Service;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@RestController
@RequestMapping("/oauth")
@RequiredArgsConstructor
@Slf4j
@Api(tags = {"GitHub 로그인 api"})
public class Oauth2Controller {
private final Oauth2Service oauth2Service;
@GetMapping("/sign-in")
@ApiOperation(value = "GitHub 로그인 api", notes = "GitHub 로그인 api")
public ResponseEntity<String> signIn(@ApiParam(value = "redirect 코드", required = true) @RequestParam String code,
HttpServletResponse response) {
try {
log.info("로그인 api 진입 :: {}", code);
Map<String, String> login = oauth2Service.login(code);
// access, refresh, 이름 헤더로 보내기
response.setStatus(HttpServletResponse.SC_OK);
response.setHeader("Authorization", login.get("Authorization"));
response.setHeader("Authorization-refresh", login.get("Authorization-refresh"));
response.setHeader("name", login.get("name"));
response.setHeader("role", login.get("role"));
return new ResponseEntity<>("로그인 성공!", HttpStatus.OK);
} catch (Exception e) {
log.info("소셜 로그인에 실패했습니다. 에러 메시지 :: {}", e.getMessage());
return new ResponseEntity<>("로그인 실패ㅜㅜ!", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
그럼 github api를 사용한 로그인은 끝!!!
다음은 로그아웃!!
'Backend > Spring Boot' 카테고리의 다른 글
[OAuth2] 04. GitHub 회원 탈퇴 구현하기(Spring boot) (0) | 2023.10.24 |
---|---|
[OAuth2] 03. GitHub 로그인 구현하기(Spring boot) (1) 사전 작업 (0) | 2023.10.16 |