본문 바로가기

Springboot

Springboot security (4) - Oauth2.0 (google) 적용

해당 버전은 Google만 적용되어 있습니다.

Google과 facebook 두가지의 소셜로그인이 필요한 경우 다음의 링크에서 확인해주세요 : https://yg1110.tistory.com/8

 

1. Application structure

 

2. 구글 API키 받아오기

1) console.developers.google.com/ -> 사용자인증정보 -> 사용자인증정보만들기 -> Oauth 클라이언트 ID만들기 -> 웹어플리케이션

순서로 진행하여 사용자 인증정보를 만듭니다.

 

만든 클라이언트 ID를 클릭하여 들어가게 될경우 다음과 같은 화면이 보이게 되며,

승인된 리디렉션 URL에 구글 로그인 요청에 사용하고싶은 주소를 넣어주시면 됩니다.

 

 

3. application.properties 추가

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

clientID와 secret_ID부분에 방금 만든 key값을 넣어주시면됩니다.

spring.main.allow-bean-definition-overriding=true

해당 부분은 bean이 중복되는 오류때문에 넣어두었습니다.

 

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;

public class OAuth2SuccessHandler implements AuthenticationSuccessHandler {
	@Override
	public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse res, Authentication auth)
			throws IOException, ServletException {
		res.sendRedirect("/user/login/result");
	}
}

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

 

5. SecurityConfig 수정

@Configuration
@EnableWebSecurity
@EnableOAuth2Client
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	...
    
	@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").permitAll()
			.anyRequest().authenticated()
			.and()
			.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class) // 소셜로그인 설정
			.formLogin().loginPage("/user/loginPage")
			.loginProcessingUrl("/login")
			.defaultSuccessUrl("/user/login/result")
			.permitAll() // 로그인 설정
			.and()
			.logout().logoutRequestMatcher(new AntPathRequestMatcher("/user/logout")) // 로그아웃 설정
			.logoutSuccessUrl("/user/logout/result").invalidateHttpSession(true)
			.and()
			.exceptionHandling().accessDeniedPage("/user/denied") // 403 예외처리 핸들링
			.and()
			.csrf().disable();
	}
    
	...
    

@EnableWebSecurity, @EnableOauth2Client 어노테이션을 추가합니다.

기존의 security정보에서 addFilterBefore 부분을 추가합니다.

 

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

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

properties에 있는 client와 resource정보를 가져와 사용합니다.

 

@Autowired
OAuth2ClientContext oauth2ClientContext;

private Filter ssoFilter() {
	OAuth2ClientAuthenticationProcessingFilter googleFilter = new OAuth2ClientAuthenticationProcessingFilter("/social_Login"); //해당 경
	OAuth2RestTemplate googleTemplate = new OAuth2RestTemplate(googleclient(), oauth2ClientContext);
	googleFilter.setRestTemplate(googleTemplate);
	googleFilter.setTokenServices(new UserInfoTokenServices(googleResource().getUserInfoUri(), googleclient().getClientId()));
	googleFilter.setAuthenticationSuccessHandler(new OAuth2SuccessHandler());
	return googleFilter;
}

만약 /social_Login 경로로 요청이 올경우 ssoFilter를 통해 소셜로그인을 진행하게됩니다.

해당 경로는 위에서 지정해둔 리디렉션 주소입니다.

만약 성공적으로 로그인 하였을경우 OAuth2SuccessHandler()에서 지정해둔 경로로 이동시키게됩니다.

 

전체 소스

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.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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
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.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.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import com.security_blog.yg1110.handler.OAuth2SuccessHandler;
import com.security_blog.yg1110.servicer.UserService;

@Configuration
@EnableWebSecurity
@EnableOAuth2Client
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	UserService userService;
	
	@Autowired
	OAuth2ClientContext oauth2ClientContext;
	
	@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").permitAll()
			.anyRequest().authenticated()
			.and()
			.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class) // 소셜로그인 설정
			.formLogin().loginPage("/user/loginPage")
			.loginProcessingUrl("/login")
			.defaultSuccessUrl("/user/login/result")
			.permitAll() // 로그인 설정
			.and()
			.logout().logoutRequestMatcher(new AntPathRequestMatcher("/user/logout")) // 로그아웃 설정
			.logoutSuccessUrl("/user/logout/result").invalidateHttpSession(true)
			.and()
			.exceptionHandling().accessDeniedPage("/user/denied") // 403 예외처리 핸들링
			.and()
			.csrf().disable();
	}
	
	@Bean
	@ConfigurationProperties("security.oauth2.client")
	OAuth2ProtectedResourceDetails googleclient() {
		return new AuthorizationCodeResourceDetails();
	}
	
	@Bean
	@ConfigurationProperties("security.oauth2.resource")
	ResourceServerProperties googleResource() {
		return new ResourceServerProperties();
	}
	
	private Filter ssoFilter() {
		OAuth2ClientAuthenticationProcessingFilter googleFilter = new OAuth2ClientAuthenticationProcessingFilter("/social_Login"); //해당 경
		OAuth2RestTemplate googleTemplate = new OAuth2RestTemplate(googleclient(), oauth2ClientContext);
		googleFilter.setRestTemplate(googleTemplate);
		googleFilter.setTokenServices(new UserInfoTokenServices(googleResource().getUserInfoUri(), googleclient().getClientId()));
		googleFilter.setAuthenticationSuccessHandler(new OAuth2SuccessHandler());
		return googleFilter;
	}
	
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(userService).passwordEncoder(userService.passwordEncoder());
	}
}

 

6. 결과화면

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

<!DOCTYPE html>
<html lang="kr">
<head>
    <meta charset="UTF-8">
    <title>로그인 페이지</title>
</head>
<body>
    <h1>로그인</h1>
    <hr>

	<form action="/login" method="post">
        <%-- <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> --%>
        <input type="text" name="username" placeholder="이메일 입력해주세요">
        <input type="password" name="password" placeholder="비밀번호">
        <button type="submit">로그인</button>
    </form>
    
    <a href="/social_Login">Login with Google</a>
        
</body>
</html>

테스트를 위해 간단하게 social_Login으로 요청을 보내는 부분을 추가하였고,

로그인을 진행한뒤

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;
    }
}

rest요청으로 해당 정보를 확인해보겠습니다.

다음과 같이 결과가 나오게됩니다.