본문 바로가기

Springboot

Springboot security (6) - Oauth2.0 (Google, facebook) 적용

기존에 작성했던 코드에서 경로이름 및 파일이름과 설정파일 등등 수정된 부분이 많습니다.

참고해서 봐주세요

 

1. Application structure

 

2. API키 받아오기

https://m.blog.naver.com/PostView.nhn?blogId=taglive&logNo=220642249212&proxyReferer=https%3A%2F%2Fwww.google.com%2F

 

페이스북 API KEY 발급방법

1. 페이스북 개발자 홈페이지에 접속합니다. https://developers.facebook.com/ 2. 로그인 후, 오른쪽 상단...

blog.naver.com

http://www.wetoz.kr/html/board.php?bo_table=tipntech&wr_id=257&sca=API

 

위토즈소프트

웹프로그램개발, 영카트, 그누보드, 코드이그나이터, 개발자판매몰

www.wetoz.kr

위 경로를 참고해서 api키를 받아주세요

승인된 리디렉션 URI에 구글로그인 버튼을 눌렀을 경우 요청할 주소를 지정합니다.

 

3. application.properties 추가

## google login
google.client.clientId=<clientId>
google.client.clientSecret=<secretkey>
google.client.preEstablishedRedirectUri=http://localhost:8080/user/login/result
google.client.accessTokenUri=https://www.googleapis.com/oauth2/v4/token
google.client.userAuthorizationUri=https://accounts.google.com/o/oauth2/v2/auth
google.client.tokenName=oauth_token
google.client.authenticationScheme=query
google.client.clientAuthenticationScheme=form
google.client.scope=profile
google.resource.user-info-uri=https://www.googleapis.com/oauth2/v3/userinfo

## facebook login
facebook.client.clientId=<clientId>
facebook.client.clientSecret=<secretkey>
facebook.client.accessTokenUri=https://graph.facebook.com/oauth/access_token
facebook.client.userAuthorizationUri=https://www.facebook.com/dialog/oauth
facebook.client.tokenName=oauth_token
facebook.client.authenticationScheme=query
facebook.client.clientAuthenticationScheme=form
facebook.resource=userInfoUri: https://graph.facebook.com/me

 <clientid> 와 <secretkey>부분에 발급받으신 키를 넣으시면됩니다.

 

4. OAuth2SuccessHandler 생성

import java.io.IOException;

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

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

//https://gs.saro.me/dev?tn=520
public class OAuth2SuccessHandler implements AuthenticationSuccessHandler {
	@Override
	public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse res, Authentication auth)
			throws IOException, ServletException {
		res.sendRedirect("/user/login/result");
	}
}

소셜 로그인 성공시 다음경로로 이동시키는 핸들러입니다.

 

5. OAuth2Oauth2Filter생성

import java.util.ArrayList;
import java.util.List;

import javax.servlet.Filter;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter;
import org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
import org.springframework.web.filter.CompositeFilter;

import com.security_blog.yg1110.handler.OAuth2SuccessHandler;

@EnableWebSecurity
@EnableOAuth2Client
public class Oauth2Filter {
	@Autowired
	OAuth2ClientContext oauth2ClientContext;

	@Bean
	@ConfigurationProperties("google.client")
	OAuth2ProtectedResourceDetails googleclient() {
		return new AuthorizationCodeResourceDetails();
	}

	@Bean
	@ConfigurationProperties("google.resource")
	ResourceServerProperties googleResource() {
		return new ResourceServerProperties();
	}

	@Bean
	@ConfigurationProperties("facebook.client")
	OAuth2ProtectedResourceDetails facebookclient() {
		return new AuthorizationCodeResourceDetails();
	}

	@Bean
	@ConfigurationProperties("facebook.resource")
	ResourceServerProperties facebookResource() {
		return new ResourceServerProperties();
	}

	public Filter ssoFilter() {
		CompositeFilter filter = new CompositeFilter();
		List<Filter> filters = new ArrayList<>();
		
		OAuth2ClientAuthenticationProcessingFilter googleFilter = new OAuth2ClientAuthenticationProcessingFilter(
				"/login/google");
		OAuth2ClientAuthenticationProcessingFilter facebookFilter = new OAuth2ClientAuthenticationProcessingFilter(
				"/login/facebook");

		filters.add(ssoFilter("/login/google", googleFilter));
		filters.add(ssoFilter("/login/facebook", facebookFilter));
		filter.setFilters(filters);
		return filter;
	}
	
	public Filter ssoFilter(String path, OAuth2ClientAuthenticationProcessingFilter Filter) {
		ResourceServerProperties resource = null;
		OAuth2ProtectedResourceDetails client = null;

		if(path.equals("/login/google")) {
			resource = googleResource();
			client = googleclient();
		}
		else {
			resource = facebookResource();
			client = facebookclient();
		}

		OAuth2RestTemplate Template = new OAuth2RestTemplate(client, oauth2ClientContext);
		Filter.setRestTemplate(Template);
		Filter.setTokenServices(
				new UserInfoTokenServices(resource.getUserInfoUri(), client.getClientId()));
		Filter.setAuthenticationSuccessHandler(new OAuth2SuccessHandler());
		
		return Filter;
	}
	
	@Bean
	public FilterRegistrationBean<OAuth2ClientContextFilter> oauth2ClientFilterRegistration(
	    OAuth2ClientContextFilter filter) {
	  FilterRegistrationBean<OAuth2ClientContextFilter> registration = new FilterRegistrationBean<>();
	  registration.setFilter(filter);
	  registration.setOrder(-100);
	  return registration;
	}
}

properties에서 설정한 facebook과 google의 client와 resource정보를 가져와 사용합니다.

/login/google경로로 요청이 오게된다면 구글로그인 로직을 실행하고,

/login/facebook경로로 요청이 오게된다면 페이스북 로직을 실행합니다.

FilterRegistrationBean를 통해 다른 필터보다 우선순위를 올리기위해 -100을 주었습니다.

만약 로그인이 성공적으로 이루어졌을경우 위에서 만든 로그인 성공 핸들러를 타고 리다이렉션 되게됩니다.

 

6. SecurityConfig 수정

import javax.servlet.Filter;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import com.security_blog.yg1110.filter.Oauth2Filter;
import com.security_blog.yg1110.filter.jwt.JwtAuthenticationFilter;
import com.security_blog.yg1110.servicer.IUserService;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	private IUserService userService;

	@Autowired
	private Oauth2Filter filter;

	@Override
	public void configure(WebSecurity web) throws Exception {
		web.ignoring().antMatchers("/login/css/**", "/login/js/**", "/login/images/**", "/login/vendor/**", "/login/fonts/**", "/ckeditor/**");
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests().antMatchers("/user/admin/**").access("hasAuthority('ROLE_ADMIN')")
				.antMatchers("/user/myinfo").access("hasAuthority('ROLE_USER')") // 페이지 권한 설정
				.antMatchers("/user/signup", "/user/denied", "/user/logout/result", "/signup").permitAll().anyRequest()
				.authenticated().and().addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class) // 소셜로그인 설정
				.addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class) // jwt 필터 설정
				.formLogin().loginPage("/user/login").loginProcessingUrl("/login")
				.defaultSuccessUrl("/user/login/result", true).permitAll() // 로그인 설정
				.and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/user/logout")) // 로그아웃 설정
				.logoutSuccessUrl("/user/logout/result").invalidateHttpSession(true).and().exceptionHandling()
				.accessDeniedPage("/user/denied") // 403 예외처리 핸들링
				.and().csrf().disable();
	}

	@Bean
	public Filter ssoFilter() {
		return filter.ssoFilter();
	}

	@Bean
	public JwtAuthenticationFilter jwtFilter() {
		return new JwtAuthenticationFilter();
	}

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(userService).passwordEncoder(userService.passwordEncoder());
	}
}

기존의 security정보에서 addFilterBefore를 이용하여 Oauth2Filter 필터를 추가합니다.

 

7. 결과화면

<a href="/login/facebook"></a>
<a href="/login/google"></a>

해당경로로 요청을 보내고

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>

<sec:authorize access="isAuthenticated()">
  <sec:authentication property="principal" var="user" />
  ${user}
</sec:authorize>

다음과같이 요청을 출력해볼수 있습니다.

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OauthController {
	@RequestMapping(value = "/social_uset_info", method = RequestMethod.GET)
	public Object index() {
    	Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        System.out.println(auth.getDetails());
        return auth;
    }
}

스프링에서는 위와 같이 테스트해볼수 있습니다.