- ํ์ด์ฌ
- ์๋ฐ
- linux
- ์กํ๊ณ
- ict๊ณต๋ชจ์
- ์จ์ผ๋ํ
- DATABASE
- python
- Java
- JOBํ๊ณ
- Naver Cloud
- ์คํฝ์ค๋น
- mysql
- ์คํฝ๋ ํ
- ICT
- ํ๋ก๋ณด๋ ธ
- ํ์ด์
- ICT๋ฉํ ๋ง
- API๋ง์ผํ๋ ์ด์ค
- DB
- API MarketPlace ๊ธ๋ก๋ฒ ์ํฌํฐ์ฆ
- ๋ฐ์ดํฐ๋ฒ ์ด์ค
- RaspberryPi
- TSQL
- SQL
- ์๋์ด๋ ธ
- ํ์ด์๊ณต๋ชจ์
- ์ด๋ธ์
- appetizer
- Spring
- Today
- Total
miinsun
[Lehgo] Spring boot : OAuth SNS Login, sns ํ์๊ฐ์ ๋ก๊ทธ์ธ ๋ณธ๋ฌธ
[Lehgo] Spring boot : OAuth SNS Login, sns ํ์๊ฐ์ ๋ก๊ทธ์ธ
miinsun 2022. 4. 5. 15:25
๐ป ์ค์ต ํ๊ฒฝ
Framework: Spring Boot, Spring Security
Build: Maven
๐ฌ ์๊ตฌ ์ฌํญ & ์๋น์ค ํ๋ฆ
Google, Naver, Kakao API๋ฅผ ์ด์ฉํด ํ์ ์ ๋ณด๋ฅผ ์์ฒญํ๊ณ ๊ทธ ์ ๋ณด๋ฅผ ์ด์ฉํด ์๋น์ค์ ํ์๊ฐ์ , ๋ก๊ทธ์ธํ์
1. Google, Naver, Kakao์ application ๋ฑ๋ก์ ํ๊ณ callback url์ ์ ๋ ฅํจ
2. ๋ก๊ทธ์ธ ์, Google, Naver, Kakao์ AccessToken์ ์์ฒญํ๋ค.
3. ์ฑ๊ณต์ ์ผ๋ก AccessToken์ ๋ฐ์ผ๋ฉด callBack์ฃผ์๋ก ์ด๋ํ๋ค.
4. AccessToken์ ์ด์ฉํด ๊ฐ์ธ ์ ๋ณด๋ฅผ ์์ฒญํ๋ค.
5. ๊ฐ์ ๋์ง ์์ ํ์์ด๋ฉด ํ์๊ฐ์ ์ ์ํํ๋ค.
6. ์ด๋ฏธ ๊ฐ์ ๋ ํ์์ด๋ฉด ๋ก๊ทธ์ธ์ ์ํํ๋ค.
7. ๋ก๊ทธ์ธ์ jwtToken์ ๋ฐ๊ธํ๋ค.
์๋ ์ฝ๋ ๋ฆฌ๋ทฐ๋ Naver OAuth๋ฅผ ๊ธฐ์ค์ผ๋ก ์์ฑ๋์ต๋๋ค. ๊ฐ OAuth๋ง๋ค ์๊ตฌํ๋ ์กฐ๊ฑด์ด ๋ค๋ฅด๊ธฐ ๋๋ฌธ์ ๊ผญ ๊ณต์๋ฌธ์๋ฅผ ํ์ธํด์ฃผ์ธ์.
๋ ๊ตฌ์ฒด์ ์ธ ์ฝ๋๋ ์๋์ ๊น์ ํ์ธํด์ฃผ์ธ์.
๐ ๋/์๋ฌธ์ converter
Controller์์ ๋ฐ๋ ํ๋ผ๋ฏธํฐ๋ฅผ enumํ์ ์ผ๋ก ๋๋ฌธ์๋ฅผ ์๋ฌธ์๋ก ๋งคํ ํ๋๋ก converter๋ฅผ ์์ฑ
@Configuration
public class SocialLoginTypeConverter implements Converter<String, SocialLoginType> {
@Override
public SocialLoginType convert(String s) {
return SocialLoginType.valueOf(s.toUpperCase());
}
}
๐ Redirect URL ์์ฑ
๊ฐ API์ ๋ฉํ ์ ๋ณด๋ค์ property์ ์ฑ๊ฒฉ์ผ๋ก application-API_KEY์ ์ ์ฅํด resource๋ก ๊ด๋ฆฌํด์ค๋๋ค. ๊ฐ๊ฐ ์ด๋ API ๊ณต์ ๊ฐ์ด๋๋ฅผ ์ฐธ๊ณ ํด APIServer์ redirect ์์ฒญ์ ํ๋๋ก ํค๋๋ฅผ ์ค์ ํด์ค๋๋ค.
@Override
public String getOauthRedirectURL() {
Map<String, Object> params = new HashMap<>();
params.put("scope", getScopeUrl());
params.put("response_type", "code");
params.put("client_id", GOOGLE_CLIENT_ID);
params.put("redirect_uri", GOOGLE_CALLBACK_URL);
String parameterString = params.entrySet().stream()
.map(x -> x.getKey() + "=" + x.getValue())
.collect(Collectors.joining("&"));
return GOOGLE_SNS_BASE_URL + "?" + parameterString;
}
๐ Access Token ์์ฑ
Spring Boot RestTemplate๋ฅผ ํ์ฉํด ๋ฉ์๋๋ฅผ ๊ตฌํ. RestTemplate๋ ์ฌ์ฉ์๊ฐ ์ ์ํ ๊ฐ์ฒด mappingํด ๋ค์ํ ํํ๋ก ๋ฐ์ดํฐ๋ฅผ ํ์ฑํด์ ๋ฐ์ ์ ์์ต๋๋ค. API ์๋ฒ๋ณ ๋ฏธ๋ฆฌ ์ง์ ๋ ํ์์ผ๋ก DTO๋ฅผ ๋ง๋ค๊ณ , ํ ํฐ ์์ฒญ ๊ฒฐ๊ณผ์ ๋งคํ ์์ผ์ค๋๋ค.
ResponseEntity<NaverLoginResponse> apiResponseJson = restTemplate.exchange(
NAVER_SNS_BASE_URL + "/token",
HttpMethod.POST,
naverTokenRequest,
NaverLoginResponse.class
);
๐ ์ฌ์ฉ์ ์ ๋ณด ํ์ธ
๋ฐฉ๊ธ ์ป์ Access Token์ ์ด์ฉํด ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ต๋๋ค. Access Token ์ ๋ณด๋ฅผ header์ ์ธํ ํด์ฃผ๊ณ , ์ฌ์ฉ์ ์ ๋ณด๋ฅผ api์๋ฒ์ GET ์์ฒญํฉ๋๋ค. ์ด๋ ๋ฐ์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ๊ณตํด lehgo ์๋น์ค์ ์๋์ผ๋ก ํ์๊ฐ์ ํ๋๋ก ํด์ค๋๋ค.
/* ์ฌ์ฉ์ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ */
// Http Header ์ค์
HttpHeaders headers2 = new HttpHeaders();
headers2.add("Authorization", "Bearer " + naverLoginResponse.getAccessToken());
headers2.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
HttpEntity<MultiValueMap<String, String>> naverTokenRequest2 = new HttpEntity<>(headers2);
ResponseEntity<NaverLoginDto> apiResponseJson2 = restTemplate.exchange(
"https://openapi.naver.com/v1/nid/me",
HttpMethod.GET,
naverTokenRequest2,
NaverLoginDto.class
);
๐ ์ฌ์ฉ์ ์ ๋ณด ํ์ธ
๋ฐฉ๊ธ ์ป์ Access Token์ ์ด์ฉํด ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ต๋๋ค. Access Token ์ ๋ณด๋ฅผ header์ ์ธํ ํด์ฃผ๊ณ , ์ฌ์ฉ์ ์ ๋ณด๋ฅผ api์๋ฒ์ GET ์์ฒญํฉ๋๋ค. ์ด๋ ๋ฐ์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ๊ณตํด lehgo ์๋น์ค์ ์๋์ผ๋ก ํ์๊ฐ์ ํ๋๋ก ํด์ค๋๋ค.
/* ์ฌ์ฉ์ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ */
// Http Header ์ค์
HttpHeaders headers2 = new HttpHeaders();
headers2.add("Authorization", "Bearer " + naverLoginResponse.getAccessToken());
headers2.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
HttpEntity<MultiValueMap<String, String>> naverTokenRequest2 = new HttpEntity<>(headers2);
ResponseEntity<NaverLoginDto> apiResponseJson2 = restTemplate.exchange(
"https://openapi.naver.com/v1/nid/me",
HttpMethod.GET,
naverTokenRequest2,
NaverLoginDto.class
);
๐ Spring Security ์ค์
์คํ๋ง ์ํ๋ฆฌํฐ์ ๊ฐ์ข ์ค์ ์ ์ํํฉ๋๋ค. ๋ฆฌ์์ค์ ๋ํ ์ ๊ทผ ๊ถํ ์ค์ , ์ธ์ฆ ๋ก์ง ์ค์ ์ ๊ตฌํํ์์ต๋๋ค.
- .antMatchers("/admin/**").hasRole("ADMIN")
- ๋ฆฌ์์ค admin์ผ๋ก ์์ํ๋ ๋ชจ๋ url์ ์ธ์ฆ ํ ADMIN ๋ ๋ฒจ์ ๊ถํ์ ๊ฐ์ง ์ฌ์ฉ์๋ง ์ ๊ทผ์ ํ์ฉํ๋ค.
- sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
- ์คํ๋ง ์ํ๋ฆฌํฐ์ ์ธ์ ์ ์ฑ ์ ๋๋ค. ์คํ๋ง ์ํ๋ฆฌํฐ๊ฐ ์ธ์ ์ ์์ฑํ์ง๋ ์๊ณ ๊ธฐ์กด์ ๋ง๋ค์ด์ง ์ธ์ ์ ์ฌ์ฉํ์ง ์์ต๋๋ค. JWT ํ ํฐ ๋ฐฉ์์ ์ฌ์ฉํ๊ธฐ ์ํด stateless ์ค์ ํด์ค๋๋ค.
- http.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);
- Spring Security Filter์ ํตํฉํ์ง ์๊ณ JwtAuthenticationFilter์์ ์ธ์ฆ ๋ฐ ๊ถํ ์์ ์ ์งํํด์ผํ๊ธฐ ๋๋ฌธ์ JwtTokenProvier๋ฅผ ํตํด์ ์ธ์ฆ ํ SecurityContextHolder๋ฅผ ๋ฐ๋ก ์ฌ์ฉํฉ๋๋ค.
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.httpBasic().disable()
.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/exists/**").permitAll() //์ค๋ณต ์ฌ๋ถ ๊ฒ์ฌ
.antMatchers("/checkUser").hasRole("USER")
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().permitAll()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.formLogin()
.disable();
http.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);
}
๐ jwtToken ์์ฑ
jwt Token์ ์์ฑํ๋ ๋ฉ์๋์ ๋๋ค.
- .setIssuedAt(now)
- .setExpiration(new Date(now.getTime() + Duration.ofMinutes(30).toMillis()))
- ํ ํฐ ๋ฐํ ์ผ์์ ๋ง๋ฃ์ผ์๋ฅผ ์ง์ ํด์ค๋๋ค. ๋ง๋ฃ ๊ธฐ๊ฐ์ด ์ง๋๋ฉด ํ ํฐ์ ๊ถํ์ด ์์ด์ง๋๋ค.
- .signWith(SignatureAlgorithm.HS256, JwtProperties.getSecretKey())
- ์๋ช ์ ๊ฒฝ์ฐ HS256 ์๊ณ ๋ฆฌ์ฆ์ ์ด์ฉํ๊ณ , key๋ ์ด์ ์ ์ง์ ํ key์ byte๊ฐ์ ์ ๋ ฅํด ์ด์ฉํ์ต๋๋ค.
public static String makeJwtToken(UserVO user) {
Date now = new Date();
return Jwts.builder()
.setHeaderParam(Header.TYPE, Header.JWT_TYPE)
.setIssuer(JwtProperties.getIssuer())
.setIssuedAt(now)
.setExpiration(new Date(now.getTime() + Duration.ofMinutes(30).toMillis()))
.claim("id", user.getUsername())
.claim("auth", user.getAuth())
.signWith(SignatureAlgorithm.HS256, JwtProperties.getSecretKey())
.compact();
}
๐ ๋น๋ฐ๋ฒํธ ์ํธํ
BCrypt ํด์ฑ ํจ์๋ ๋น๋ฐ๋ฒํธ๋ฅผ ์ํธํํ๊ธฐ ์ํด ์ฌ์ฉํ์์ต๋๋ค.
์ด๋ ๋น๋ฐ๋ฒํธ๋ฅผ ์ฌ์ฉ์๊ฐ ์ ์ถํ ๋น๋ฐ๋ฒํธ์ DB์ ์ ์ฅ๋ ๋น๋ฐ๋ฒํธ์ ์ผ์น ์ฌ๋ถ๋ฅผ ํ์ธํด์ฃผ๋ ์ญํ ์ ํฉ๋๋ค. ๋น๋ฐ๋ฒํธ๋ฅผ ์ํธํ ํจ์ผ๋ก์จ ๋น๋ฐ๋ฒํธ ๋ฐ์ดํฐ๊ฐ ๋ ธ์ถ๋๋๋ผ๋ ํ์ธํ๊ธฐ ์ด๋ ต๋๋ก ๋ง๋ค์ด ์ค๋๋ค.
@Bean //๋น๋ฐ๋ฒํธ ์ํธํ
public BCryptPasswordEncoder bCryptPasswordEncoder()
{
return new BCryptPasswordEncoder();
}
'Project > 2022 Lehgo' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Lehgo] AWS EC2 Spring Boot ์๋ฒ ์คํํ๋ ๋ฐฉ๋ฒ Maven (0) | 2022.04.05 |
---|---|
[Lehgo] SpringBoot Api-key ์จ๊ธฐ๋ ๋ฐฉ๋ฒ, Properties Maven (0) | 2022.04.05 |
[Lehgo] ๊ธฐ๋ฅ ์ ์ (0) | 2022.04.05 |
[Lehgo] ํ๋ก์ ํธ ์์ฐ ์์ (0) | 2022.04.05 |
[Lehgo] ํ๋ก์ ํธ ์๊ฐ (0) | 2022.04.05 |