카카오, 구글, 네이버 계정을 통해 회원으로 등록하여 로그인기능을 구현하는 웹 프로젝트를 진행 하였다.
🎯 프로젝트 구현 목표
1. 구글, 카카오, 네이버 계정으로 회원정보 DB에 등록
2. 일반 로그인, 회원가입(아이디,비밀번호 사용)구현
3. JWT와 쿠키를 활용한 로그인 유지
<프로젝트 깃허브 리포지토리>
https://github.com/yangwoohyeon/Oauth_Practice
GitHub - yangwoohyeon/Oauth_Practice: 구글,네이버,카카오,클라이언트 로그인 연습 토이 프로젝트
구글,네이버,카카오,클라이언트 로그인 연습 토이 프로젝트. Contribute to yangwoohyeon/Oauth_Practice development by creating an account on GitHub.
github.com
전체적인 구현 로직
- /oauth2/authorization/{provider} 경로로 이동, OAuth2 제공자(Naver,Kakao,Naver)의 로그인 화면으로 리다이렉트
- 소셜 계정으로 인증 후 PrincipalOauth2UserService가 호출되어 OAuth2User 정보를 가져옴
- OAuth2UserInfo 인터페이스를 구현한 각 소셜 클래스 (GoogleUserInfo, KakaoUserInfo, NaverUserInfo)를 통해 사용자 정보 추출
- 기존 사용자가 아니면 DB에 사용자 정보를 저장, DB에 있는 사용자면 그대로 진행
- JwtTokenProvider를 통해 토큰 발급 (만료 시간 : 30분)
- 쿠키에 저장 후 메인 페이지로 이동
이제부터 코드를 보며 구현과정을 알아보자!
소셜 로그인을 위한 키 발급
소셜 로그인을 위해 카카오,구글, 네이버에서 client-id와 client-secret키를 받아와야 한다.
키 발급 과정은 아래 블로그를 참고하여 진행하였다.
Spring Security를 이용한 통합 OAuth2 소셜 로그인 기능 구현(Kakao, Google, Naver)
이번 시간에는 이전 포스팅들에서 공부한 시큐리티 설정 build.gradle SecurityConfig
velog.io
각각의 키 값을 받았다면 application.yml에 등록해줘야 한다.
<application.yml>
spring:
web:
resources:
static-locations: classpath:/static/
thymeleaf:
prefix: classpath:/templates/
suffix: .html
mode: HTML5
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/oauth?useSSL=false&serverTimezone=Asia/Seoul&characterEncoding=UTF-8
username: root
password: 1234
jpa:
open-in-view: true
hibernate:
ddl-auto: create
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
show-sql: true
properties:
hibernate.format_sql: true
dialect: org.hibernate.dialect.MySQL8InnoDBDialect
security:
oauth2:
client:
registration:
google:
client-id: client id 넣기
client-secret: 비밀키 넣기
scope:
- email
- profile
naver:
client-id: client id 넣기
client-secret: 비밀키 넣기
scope:
- name
- email
client-name: Naver
authorization-grant-type: authorization_code
redirect-uri: http://localhost:8080/login/oauth2/code/naver
kakao:
client-name: kakao
authorization-grant-type: authorization_code
redirect-uri: http://localhost:8080/login/oauth2/code/kakao
client-id: 키값 넣기
client-secret: 비밀키 넣기
client-authentication-method: client_secret_post
scope:
- profile_nickname
- account_email
provider:
naver:
authorization-uri: https://nid.naver.com/oauth2.0/authorize
token-uri: https://nid.naver.com/oauth2.0/token
user-info-uri: https://openapi.naver.com/v1/nid/me
user-name-attribute: response
kakao:
authorization-uri: https://kauth.kakao.com/oauth/authorize
user-name-attribute: id
token-uri: https://kauth.kakao.com/oauth/token
user-info-uri: https://kapi.kakao.com/v2/user/me
jwt:
secret: ZGVmYXVsdC1zZWNyZXQta2V5LXRvLWNoYW5nZQ==
유저 객체 생성
각 소셜 로그인 사용자 정보를 저장하기 위한 유저 객체를 생성해줘야 한다.
<User>
package hello.Member_Management.User.Entity;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import java.sql.Timestamp;
@Data
@Entity
@Table
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private long id;
@Column(name="user_id", unique = true)
private String userId; //유저 식별자
private String password; //비밀번호
private String email; //이메일
private String role; //유저 신분
private String name; //이름
@CreationTimestamp //INSERT 쿼리가 발생할 때, 현재 시간을 값으로 채워서 쿼리를 생성한다.
private Timestamp timestamp; //가입일 기록
private String provider;
private String providerId;
}
<UserRepository>
package hello.Member_Management.User.Repository;
import hello.Member_Management.User.Entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User,Long> { //Spring Data JPA 사용
User findByUserId(String userId); //이름으로 유저 찾기
}
하지만 여기서 한가지 문제가 있는데
카카오, 구글, 네이버로 로그인 했을때 넘어오는 정보가 다 달라서 유저 객체에 매핑 시키는데 문제가 생긴다.
따라서 각 플랫폼에서 가져온 사용자 정보를 표준화된 형태로 변환해주는 Adapter가 필요하다.
<OAuth2UserInfo 인터페이스>
package hello.Member_Management.User.UserInfo;
public interface OAuth2UserInfo {
String getProvider();
String getProviderId();
String getEmail();
String getName();
}
<GoogleUserInfo>
package hello.Member_Management.User.UserInfo;
import java.util.Map;
public class GoogleUserInfo implements OAuth2UserInfo{
private Map<String,Object> attributes;
public GoogleUserInfo(Map<String,Object> attributes){
this.attributes=attributes;
}
@Override
public String getProvider() {
return "google";
}
@Override
public String getProviderId() {
return (String)attributes.get("sub");
}
@Override
public String getEmail() {
return (String)attributes.get("email");
}
@Override
public String getName() {
return (String)attributes.get("name");
}
}
<KakaoUserInfo>
package hello.Member_Management.User.UserInfo;
import java.util.LinkedHashMap;
import java.util.Map;
public class KakaoUserInfo implements OAuth2UserInfo{
private Map<String, Object> attributes; // getAttributes()
public KakaoUserInfo(Map<String, Object> attributes) {
this.attributes = attributes;
}
@Override
public String getProvider() {
return "kakao";
}
@Override
public String getProviderId() {
return String.valueOf(attributes.get("id"));
}
@Override
public String getEmail() {
Object object = attributes.get("kakao_account");
LinkedHashMap accountMap = (LinkedHashMap) object;
return (String) accountMap.get("email");
}
@Override
public String getName() {
LinkedHashMap<String, Object> accountMap = (LinkedHashMap<String, Object>) attributes.get("kakao_account");
LinkedHashMap<String, Object> profileMap = (LinkedHashMap<String, Object>) accountMap.get("profile");
return (String) profileMap.get("nickname");
}
}
<NaverUserInfo>
package hello.Member_Management.User.UserInfo;
import java.util.Map;
public class NaverUserInfo implements OAuth2UserInfo{
private Map<String, Object> attributes; // getAttributes()
public NaverUserInfo(Map<String, Object> attributes) {
this.attributes = attributes;
}
@Override
public String getProvider() {
return "naver";
}
@Override
public String getProviderId() {
return (String) attributes.get("id");
}
@Override
public String getEmail() {
return (String) attributes.get("email");
}
@Override
public String getName() {
return (String) attributes.get("name");
}
}
받아온 정보를 이용하여 로그인 구현
이제 소셜계정을 통해 받아온 정보를 통해 User객체와 매팽을 시키고 DB에 저장하거나 로그인을 유지시켜야 한다.
<PrincipalDetail>
package hello.Member_Management.User;
import hello.Member_Management.User.Entity.User;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
@Data
@RequiredArgsConstructor
public class PrincipalDetails implements UserDetails, OAuth2User {
private User user; // 콤포지션
private Map<String, Object> attributes;
// 일반 로그인
public PrincipalDetails(User user) {
this.user = user;
}
// OAuth 로그인
public PrincipalDetails(User user, Map<String, Object> attributes) {
this.user = user;
this.attributes = attributes;
}
// 해당 유저의 권한을 리턴하는 곳
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collect = new ArrayList<>();
collect.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return user.getRole();
}
});
return collect;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUserId();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public String getName() {
return user.getName(); // ✅ DB에 저장된 이름 반환
}
@Override
public Map<String, Object> getAttributes() {
return attributes;
}
}
<PrincipalOauth2UserService>
package hello.Member_Management.User.Service;
import hello.Member_Management.User.*;
import hello.Member_Management.User.Entity.User;
import hello.Member_Management.User.Jwt.JwtTokenProvider;
import hello.Member_Management.User.Repository.UserRepository;
import hello.Member_Management.User.UserInfo.GoogleUserInfo;
import hello.Member_Management.User.UserInfo.KakaoUserInfo;
import hello.Member_Management.User.UserInfo.NaverUserInfo;
import hello.Member_Management.User.UserInfo.OAuth2UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.Map;
@Service
public class PrincipalOauth2UserService extends DefaultOAuth2UserService {
@Autowired
private PasswordEncoder bCryptPasswordEncoder;
@Autowired
private UserRepository userRepository;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
System.out.println("getClientRegistration = " + userRequest.getClientRegistration());
System.out.println("getAccessToken = " + userRequest.getAccessToken().getTokenValue());
OAuth2User oAuth2User = super.loadUser(userRequest);
System.out.println("getAttributes = " + oAuth2User.getAttributes());
OAuth2UserInfo oAuth2UserInfo = null;
if (userRequest.getClientRegistration().getRegistrationId().equals("google")) {
System.out.println("구글 로그인 요청");
oAuth2UserInfo = new GoogleUserInfo(oAuth2User.getAttributes());
} else if(userRequest.getClientRegistration().getRegistrationId().equals("naver")){
System.out.println("네이버 로그인 요청");
oAuth2UserInfo = new NaverUserInfo((Map) oAuth2User.getAttributes().get("response"));
} else if(userRequest.getClientRegistration().getRegistrationId().equals("kakao")){
System.out.println("카카오 로그인 요청");
oAuth2UserInfo = new KakaoUserInfo(oAuth2User.getAttributes());
} else {
System.out.println("지원하지 않는 로그인 방식입니다!");
}
String provider = oAuth2UserInfo.getProvider();
String providerId = oAuth2UserInfo.getProviderId();
String username = provider + "_" + providerId; //각 소셜 플랫폼을 구분하기 위해 플랫폼명 + ID로 설정
String password = bCryptPasswordEncoder.encode("임의 비밀번호");
String email = oAuth2UserInfo.getEmail();
String name = oAuth2UserInfo.getName(); //사용자 이름 가져오기
String role = "ROLE_USER";
User userEntity = userRepository.findByUserId(username);
if (userEntity == null) {
System.out.println("로그인이 최초입니다.");
userEntity = User.builder()
.userId(username)
.password(password)
.email(email)
.name(name) // ✅ 사용자 이름 저장
.role(role)
.provider(provider)
.providerId(providerId)
.build();
userRepository.save(userEntity);
} else {
System.out.println("이미 등록된 사용자입니다.");
}
// PrincipalDetails 생성
PrincipalDetails principalDetails = new PrincipalDetails(userEntity, oAuth2User.getAttributes());
// SecurityContext에 인증 정보 강제 등록
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(principalDetails, null, principalDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
return principalDetails;
}
}
userRequest.getClientRegistration()를 통해 어떤 소셜 플랫폼인지를 확인하고,
로그인을 시도한 소셜 플랫폼에 맞게 OAuth2UserInfo 인터페이스를 구현한 클래스( GoogleUserInfo, KakaoUserInfo, NaverUserInfo)를 사용한다.
그 후 소셜 로그인 사용자의 정보를 받아와서 DB에 있는지 비교 후 없으면 사용자 정보를 DB에 저장하고,
만약 이 전에 로그인 했던 유저라면 DB에 저장하지 않고 기존 정보를 사용한다.
JWT토큰 발급 후 쿠키에 저장
소셜 로그인을 통해 사용자 정보를 받아와서 인증이 완료 되었다면 클라이언트와 서버 간 인증 상태를 유지하기 위해 JWT 토큰을 발급받아야 한다.
<JwtTokenProvider>
package hello.Member_Management.User.Jwt;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import java.util.Base64;
import java.util.Date;
@Component
public class JwtTokenProvider {
@Value("${jwt.secret}")
private String secretKey;
private final long ACCESS_TOKEN_VALIDITY = 1000 * 60 * 30; // 30분
private final long REFRESH_TOKEN_VALIDITY = 1000 * 60 * 60 * 24 * 7; // 7일
@PostConstruct
protected void init() {
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
}
// ✅ Access Token 생성
public String createAccessToken(String userPk) {
return createToken(userPk, ACCESS_TOKEN_VALIDITY);
}
// ✅ Refresh Token 생성
public String createRefreshToken(String userPk) {
return createToken(userPk, REFRESH_TOKEN_VALIDITY);
}
// ✅ JWT 생성 공통 메서드
public String createToken(String userPk, long validity) {
Claims claims = Jwts.claims().setSubject(userPk);
Date now = new Date();
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(new Date(now.getTime() + validity))
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
// ✅ 토큰에서 회원 정보 추출
public String getUserPk(String token) {
try {
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody()
.getSubject();
} catch (Exception e) {
System.out.println("❌ 토큰 파싱 실패: " + e.getMessage());
return null;
}
}
public boolean validateToken(String token) {
try {
return !Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token)
.getBody().getExpiration().before(new Date());
} catch (Exception e) {
System.out.println("❌ JWT 유효성 검사 실패: " + e.getMessage());
return false;
}
}
}
<SecurityConfig>
package hello.Member_Management.User.Config;
import hello.Member_Management.User.Jwt.JwtTokenProvider;
import hello.Member_Management.User.Jwt.JwtAuthenticationFilter;
import hello.Member_Management.User.PrincipalDetails;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
@Configuration
@EnableWebSecurity // 스프링 시큐리티 필터 활성화
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtTokenProvider jwtTokenProvider;
private final UserDetailsService userDetailsService;
private final CustomOAuth2SuccessHandler customOAuth2SuccessHandler;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.disable())
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/loginForm","joinForm").permitAll()
.requestMatchers("/images/**","/css/**","/js/**","/webjars/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/loginForm")
.loginProcessingUrl("/login") // POST 요청 URL
.successHandler(customOAuth2SuccessHandler) // 커스텀 SuccessHandler 등록
.failureUrl("/loginForm?error=true")
)
.oauth2Login(oauth2 -> oauth2
.loginPage("/loginForm")
.successHandler(customOAuth2SuccessHandler) // 커스텀 SuccessHandler 등록
)
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider, userDetailsService),
UsernamePasswordAuthenticationFilter.class)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.deleteCookies("JSESSIONID", "accessToken")
.invalidateHttpSession(true)
);
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:8080")); // 프론트엔드 주소
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*")); // 모든 헤더 허용
configuration.setAllowCredentials(true); // 쿠키 및 헤더 포함 허용
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
<CustomOAuth2SuccessHandler>
package hello.Member_Management.User.Config;
import com.fasterxml.jackson.databind.ObjectMapper;
import hello.Member_Management.User.Jwt.JwtTokenProvider;
import hello.Member_Management.User.PrincipalDetails;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Component
@RequiredArgsConstructor
public class CustomOAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
private final JwtTokenProvider jwtTokenProvider;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal();
String username = principalDetails.getUsername();
// ✅ JWT 생성
String accessToken = jwtTokenProvider.createAccessToken(username);
// ✅ SecurityContext에 인증 정보 수동 저장
SecurityContextHolder.getContext().setAuthentication(authentication);
// ✅ JWT를 쿠키에 저장 (선택)
Cookie accessTokenCookie = new Cookie("accessToken", accessToken);
System.out.println("accessToken = " + accessToken);
accessTokenCookie.setHttpOnly(true);
accessTokenCookie.setPath("/");
accessTokenCookie.setMaxAge(60*30); //30분동안 유효
response.addCookie(accessTokenCookie);
// ✅ 홈으로 리다이렉트
response.sendRedirect("/");
}
}
소셜 로그인 성공시 CustomOAuth2SuccessHandler가 호출되어
JWT토큰을 생성하고 쿠키에 저장한다.
이 토큰을 이용해 사용자의 인증이 이루어진다.
서버는 클라이언트의 요청이 서버에 도달하기 전에 JWT토큰의 유효성 검사를 통해 사용자 인증을 수행해야한다.
이를 구현하기 위해서 JwtAuthenticationFilter를 생성했다.
<JwtAuthenticationFilter>
package hello.Member_Management.User.Jwt;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider jwtTokenProvider;
private final UserDetailsService userDetailsService;
public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider, UserDetailsService userDetailsService) {
this.jwtTokenProvider = jwtTokenProvider;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = resolveToken(request);
System.out.println("필터 실행됨. 추출된 토큰: " + token);
// ✅ 이미 인증된 사용자인지 확인
Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication();
if (existingAuth != null && existingAuth.isAuthenticated()) {
System.out.println("이미 인증된 사용자입니다: " + existingAuth.getName());
filterChain.doFilter(request, response);
return;
}
if (StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) {
String username = jwtTokenProvider.getUserPk(token);
System.out.println("username = " + username);
try {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (userDetails != null) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
System.out.println("✅ SecurityContext에 인증 정보 저장 완료");
} else {
System.out.println("❌ 사용자 정보를 찾을 수 없습니다. username: " + username);
clearTokenCookie(response); // ✅ 쿠키 초기화
}
} catch (UsernameNotFoundException e) {
System.out.println("❌ 사용자 조회 실패: " + e.getMessage());
clearTokenCookie(response); // ✅ 쿠키 초기화
}
} else {
clearTokenCookie(response); // ✅ 쿠키 초기화
}
filterChain.doFilter(request, response);
}
// ✅ 쿠키 삭제 메서드
private void clearTokenCookie(HttpServletResponse response) {
Cookie cookie = new Cookie("accessToken", null);
cookie.setHttpOnly(true);
cookie.setMaxAge(0); // 쿠키 삭제
cookie.setPath("/");
response.addCookie(cookie);
}
private String resolveToken(HttpServletRequest request) {
// 1️⃣ 헤더에서 먼저 토큰을 찾음
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
// 2️⃣ 헤더에 없으면 쿠키에서 찾음
if (request.getCookies() != null) {
for (Cookie cookie : request.getCookies()) {
if ("accessToken".equals(cookie.getName())) {
return cookie.getValue();
}
}
}
return null;
}
}
일반 로그인,회원가입 구현
유저가 직접 입력한 아이디, 패스워드를 통해 로그인을 하는 기능도 구현 하였다.
토큰을 받는것은 OAuth2로그인과 동일한다.
<UserService>
package hello.Member_Management.User.Service;
import hello.Member_Management.User.Entity.User;
import hello.Member_Management.User.Repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public User registraion(String username, String password, String email,String id){
User user = new User();
user.setUserId(id);
user.setName(username);
user.setPassword(passwordEncoder.encode(password));
user.setEmail(email);
user.setRole("ROLE_USER");
user.setProvider("Web");
user.setProviderId("Web");
userRepository.save(user);
return user;
}
}
<UserCreateForm>
package hello.Member_Management.User;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class UserCreateForm {
@Size(min=3, max = 25)
private String username;
@NotEmpty(message = "아이디는 필수항목입니다.")
private String id;
@NotEmpty(message = "비밀번호는 필수항목입니다.")
private String password1;
@NotEmpty(message = "비밀번호는 필수항목입니다.")
private String password2;
@NotEmpty(message = "이메일은 필수항목입니다.")
@Email
private String email;
}
<IndexController>
package hello.Member_Management.User.Controller;
import hello.Member_Management.User.Entity.User;
import hello.Member_Management.User.Jwt.JwtTokenProvider;
import hello.Member_Management.User.PrincipalDetails;
import hello.Member_Management.User.Repository.UserRepository;
import hello.Member_Management.User.Service.UserService;
import hello.Member_Management.User.UserCreateForm;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
@Controller
@RequiredArgsConstructor
public class IndexController {
private final UserRepository userRepository;
private final UserService userService;
private final JwtTokenProvider jwtTokenProvider;
@GetMapping({"","/"})
public String index(){
return "index";
}
@GetMapping("/user")
public @ResponseBody String user(@AuthenticationPrincipal PrincipalDetails principalDetails){
System.out.println("principalDetails = "+principalDetails.getUser());
return "user";
}
@GetMapping("/admin")
public @ResponseBody String admin(){
return "admin";
}
@GetMapping("/manager") //@ResponseBody ==> 자바객체를 HTTP요청의 바디 내용으로 매핑하여 클라이언트로 전송
public @ResponseBody String manager(){
return "manager";
}
@GetMapping("/loginForm") //로그인 폼
public String loginForm() {
return "loginForm";
}
@GetMapping("/joinForm") //회원가입 폼
public String joinForm(Model model) {
model.addAttribute("userCreateForm",new UserCreateForm()); //객체 생성후 모델에 담아준다.
return "joinForm";
}
@PostMapping("/joinForm") //회원가입
public String join(@Valid UserCreateForm userCreateForm, BindingResult bindingResult) {
if(bindingResult.hasErrors()){
return "joinForm";
}
if(!userCreateForm.getPassword1().equals(userCreateForm.getPassword2())){
bindingResult.rejectValue("passwerd2","passwordInCorrect","2개의 패스워드가 일치하지 않습니다.");
return "joinForm";
}
userService.registraion(userCreateForm.getUsername(),userCreateForm.getPassword1(),userCreateForm.getEmail(),userCreateForm.getId());
return "redirect:/";
}
}
실행결과
데이터베이스에 OAuth2를 이용한 회원 등록이 잘 된것을 확인할 수 있다.
'프로젝트' 카테고리의 다른 글
BidZone_Front 작업 (0) | 2024.12.28 |
---|