아래는 OAuth 2.0 표준을 따라 UserInfo Endpoint에서 사용자 정보를 가져오는 예제입니다.
※ 대부분의 Posting에는 Token Payload를 JWT Claim을 통해서 데이터를 복호화 하는 방법이 소개되어 있습니다.
Git 주소
https://github.com/armyost/Oauth2Sample/tree/main
GitHub - armyost/Oauth2Sample: This is OAuth2 Authentication sample with SpringSecurity
This is OAuth2 Authentication sample with SpringSecurity - GitHub - armyost/Oauth2Sample: This is OAuth2 Authentication sample with SpringSecurity
github.com
OAuth 2.0는 JWT(Json Web Token)을 활용한 표준 프레임워크로 Token 기반 인증 프로토콜 중 가장 범용적인 생태계를 구축하고 있습니다. 범용성이 있어 OAuth 관련 모듈을 Spring이 프레임워크 레벨에서 제공하고 있습니다. 이에따라 Client Side 에서는 Spring Security에서 제공하는 전용 OAuth 2.0 모듈을 사용하면 개발생산성이 좋습니다. 예를들어 아래 소스코드에서 redirect-uri 로 표시된 {baseUri}/login/oauth2/code/{provider} 로 OAuth Server로 부터 수신한 사용자 정보, Token 정보를 보내기만 하면 Spring OAuth Client가 알아서 객체로 생명주기를 관리합니다.
└─ src
├─ main
│ ├─ java
│ │ └─ com
│ │ └─ armyost
│ │ └─ demo
│ │ ├─ DemoApplication.java
│ │ ├─ DemoPostController.java
│ │ ├─ KakaoOAuth2UserService.java
│ │ ├─ OAuth2AuthenticationSuccessHandler.java
│ │ ├─ SecurityConfig.java
│ │ └─ TokenAuthenticationFilter.java
│ └─ resources
│ └─ application.yml
|
가) Redis 및 SpringSession 환경설정
spring: security: oauth2: client: registration: kakao: // Provider 별로 정의 client-id: 1a71cXXXXXXXXXXXXXXXXXXXXXXab4ab client-secret: DO2nGXXXXXXXXXXXXXXXXXXXXXXM6XUaFdwT scope: account_email, profile_nickname client-name: Kakao authorization-grant-type: authorization_code redirect-uri: http://localhost:8080/login/oauth2/code/kakao client-authentication-method: POST provider: kakao: // Provider 별로 정의 authorization-uri: https://kauth.kakao.com/oauth/authorize token-uri: https://kauth.kakao.com/oauth/token user-info-uri: https://kapi.kakao.com/v2/user/me user-name-attribute: id |
나) Security Filter Chain Bean 생성
※ Security Filter Chain 제공 기능 Document 링크 : https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/config/annotation/web/builders/HttpSecurity.html
@Configuration @RequiredArgsConstructor @EnableWebSecurity public class SecurityConfig { private final KakaoOAuth2UserService kakaoOAuth2UserService; private final OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler; private final TokenAuthenticationFilter tokenAuthenticationFilter; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf(withDefaults()) .cors(withDefaults()) // if you want to customize cors -> .cors().configurationSource(corsConfigurationSource()); .addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) // Let it check validation of client's token first. .authorizeHttpRequests(request -> request .antMatchers("/index.html","/images/**","/permitAllConents.html") // White list policy. .permitAll() .antMatchers("/**").hasRole("USER") .anyRequest().authenticated()) .oauth2Login(oauth2 -> oauth2.userInfoEndpoint(userInfo -> userInfo .userService(kakaoOAuth2UserService) // Let it do actions with user entity. .and() .successHandler(oAuth2AuthenticationSuccessHandler))) // Let it do actions after authentication like reponsing the authorised token. .logout(withDefaults()); // return http.build(); } } |
다) Client Request 유입시 Request의 유효성 검증을 위한 Filter
Token 유효성 검사와 같이 Client Request의 유효성 검사를 수행
@Component public class TokenAuthenticationFilter extends OncePerRequestFilter { private static final Logger logger = LoggerFactory.getLogger(TokenAuthenticationFilter.class); public static final String AUTHORIZATION_HEADER = "Authorization"; public static final String BEARER_PREFIX = "Bearer "; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { logger.info("!!! Check client's token validation first of all spring security process"); // You can check validation of a Bearer token here first of all. /* if () { response.setStatus(401); return } */ filterChain.doFilter(request, response); } } |
라) 인증 성공 후 사용자 정보에 관련한 로직 구현
사용자 통계를 위한 로깅과 같이 인증 ∙ 인가 프로세스 외 사용자 정보로 부가가치 생성
@RequiredArgsConstructor @Service public class KakaoOAuth2UserService extends DefaultOAuth2UserService { private static final Logger logger = LoggerFactory.getLogger(KakaoOAuth2UserService.class); private final HttpSession httpSession; @Override public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { OAuth2User oAuth2User = super.loadUser(userRequest); Map<String, Object> attributes = oAuth2User.getAttributes(); // How to get informations of authenticated user. logger.info("!!! User attributes are : {} !!!", attributes); logger.info("!!! User authorities are : {} !!!", oAuth2User.getAuthorities()); logger.info("!!! User AccessToken Value is {} !!!", userRequest.getAccessToken().getTokenValue()); logger.info("!!! User AccessToken will be expired at {} !!!", userRequest.getAccessToken().getExpiresAt()); // I use session to some data share through a session. httpSession.setAttribute("login_info", attributes); httpSession.setAttribute("access_token", userRequest.getAccessToken().getTokenValue()); return new DefaultOAuth2User(Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")), oAuth2User.getAttributes(), "id"); } } |
마) 인증 성공 후 수행할 로직 구현
Client에 인증 Token을 Response 하거나, 특정 페이지로 Redirect 하는 등의 역할 수행
@Component public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { private static final Logger logger = LoggerFactory.getLogger(OAuth2AuthenticationSuccessHandler.class); @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { // Code here whatever want to do after authetication. // If you want to response access token to client. Make it as a Cookie. logger.info("!!! Response access token : {}", request.getSession().getAttribute("access_token")); /* Authentication userAuthenticationDetail = SecurityContextHolder.getContext().getAuthentication(); Long userAuthenticationName = Long.valueOf(userAuthenticationDetail.getName()); logger.info("!!! UserAuthentication Name is : {} !!!", userAuthenticationName); */ } } |
'Programming > Java' 카테고리의 다른 글
JAVA ) EventStream 을 이용한 Hexagonal Sample (0) | 2023.11.01 |
---|---|
JPA 사용법 (0) | 2023.10.11 |
Spring Security Filter Chain 설명 (0) | 2023.09.06 |
SpringFramework 에서 Redis 를 활용한 Session Storage 분리 (0) | 2023.09.06 |
JSESSIONID만으로 세션 유효성 검증하는 방법 (0) | 2023.08.28 |