๋ชฉ์ฐจ
์ด์ ๊ธ
Spring Security๋ ๊ฐ ์ ๋ช ํ ์ฌ์ดํธ๋ค(๊ตฌ๊ธ, ๊นํ๋ธ, ํ์ด์ค๋ถ, okta)์ OAuth2๋ฅผ ๋ฏธ๋ฆฌ ์ค์ ํด๋์๋ค.
public enum CommonOAuth2Provider {
GOOGLE {
@Override
public Builder getBuilder(String registrationId) {
ClientRegistration.Builder builder = getBuilder(registrationId,
ClientAuthenticationMethod.CLIENT_SECRET_BASIC, DEFAULT_REDIRECT_URL);
builder.scope("openid", "profile", "email");
builder.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth");
builder.tokenUri("https://www.googleapis.com/oauth2/v4/token");
builder.jwkSetUri("https://www.googleapis.com/oauth2/v3/certs");
builder.issuerUri("https://accounts.google.com");
builder.userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo");
builder.userNameAttributeName(IdTokenClaimNames.SUB);
builder.clientName("Google");
return builder;
}
},
....
}
๊ทธ๋์ ๊ตฌ๊ธ ๋ก๊ทธ์ธ์ ์ฐ๋ํ ๋ application.properties์ ๊ธฐ์ฌํ๋ ๋ด์ฉ์ด ํฌ๊ฒ ์์๋ค (์ด๋ฏธ ์ค์ ๋์ด์์๊ธฐ๋๋ฌธ)
spring.security.oauth2.client.registration.google.client-id =
spring.security.oauth2.client.registration.google.client-secret =
spring.security.oauth2.client.registration.google.scope = profile, email
ํ์ง๋ง ๋ค์ด๋ฒ์ ๊ฒฝ์ฐ๋ ๊ธฐ๋ณธ Provider๊ฐ ์๋๊ธฐ๋๋ฌธ์ application.properties์ ๋ถ๊ฐ์ ์ธ ๋ด์ฉ์ ๋ฑ๋กํด์ฃผ์ด์ผํ๋ค!
OAuth2 ์ค์ ํํธ์์ ํ์ธํ์.
Spring Security - OAuth2 ๋ค์ด๋ฒ ๋ก๊ทธ์ธํ๊ธฐ
๋ค์ด๋ฒ ์ค์
1. ๋ค์ด๋ฒ ๊ฐ๋ฐ์ ์ผํฐ ์ ์
2. Application > ์ ํ๋ฆฌ์ผ์ด์ ๋ฑ๋ก
3. ์ฝ๊ด๋์, ๊ณ์ ์ ๋ณด๋ฑ๋ก ํ ์ ํ๋ฆฌ์ผ์ด์ ๋ฑ๋ก
> ์ ํ๋ฆฌ์ผ์ด์ ์ด๋ฆ ์์ฑ > ์ฌ์ฉ API : ๋ค์ด๋ฒ ๋ก๊ทธ์ธ ํด๋ฆญ > ํ์์ด๋ฆ, ์ด๋ฉ์ผ์ฃผ์ ํด๋ฆญ > ์๋น์คํ๊ฒฝ PC์น ํด๋ฆญ
4. ์๋น์ค URL : http://localhost:8080 ์ ๋ ฅ
์ฝ๋ฐฑ URL : http://localhost:8080/login/oauth2/code/naver ์ ๋ ฅ ํ ๋ฑ๋ก
5. ์์ด๋์ ๋น๋ฐ๋ฒํธ ๊ธฐ์ตํด๋์
OAuth2 ์ค์
build.gradle : ์ด์ ๊ณผ ๋์ผํจ
application.properties
# naver
spring.security.oauth2.client.registration.naver.client-id =
spring.security.oauth2.client.registration.naver.client-secret =
spring.security.oauth2.client.registration.naver.scope = name, email
spring.security.oauth2.client.registration.naver.client-name = Naver
spring.security.oauth2.client.registration.naver.authorization-grant-type = authorization_code
spring.security.oauth2.client.registration.naver.redirect-uri = http://localhost:8080/login/oauth2/code/naver
spring.security.oauth2.client.provider.naver.authorization-uri = https://nid.naver.com/oauth2.0/authorize
spring.security.oauth2.client.provider.naver.token-uri = https://nid.naver.com/oauth2.0/token
spring.security.oauth2.client.provider.naver.user-info-uri = https://openapi.naver.com/v1/nid/me
spring.security.oauth2.client.provider.naver.user-name-attribute = response
registration.naver.client-name | ์๋ ์์ฑ๋๋ ๋ก๊ทธ์ธ ํ์ด์ง์์ ๋ ธ์ถํ๋ ๋ฑ์ ์ฌ์ฉํ๋ค(๊ผญ ์์ด์ผํ๋์ง๋ ๋ชจ๋ฅด๊ฒ ๋ค..) |
registration.naver.authorization-grant-type | OAuth2๋ 4๊ฐ์ง Authorization Grant์ ํ์ด ์๋ค ๋ค์ด๋ฒ๋ ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉ๋๋ authorization_code ๋ฐฉ์์ ์ด์ฉํ๋ค (์ฐธ๊ณ ) |
registration.naver.redirect-uri | ๋ค์ด๋ฒ๊ฐ ์ฌ์ฉ์ ํ์ธ ํ ์ ๋ณด๋ฅผ ๋ด ํ๋ก์ ํธ๋ก ๋ณด๋ด์ฃผ๋ ์ฃผ์ ๊ตฌ๊ธ์ฒ๋ผ ํจํด์ ๊ฐ์ง๊ณ ์๋ค. "{baseUrl}/login/oauth2/code/{registrationId}" |
provider.naver.authorization-uri | ์ธ์ฆ์ ์์ฒญํ๋ url์ ์์ฑํ๋ค (#3.4.2) |
provider.naver.token-uri | ํ ๊ทผ์ ์์ฒญํ๋ url์ ์์ฑํ๋ค (#3.4.4) |
provider.naver.user-info-uri | ํ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋ url์ ์์ฑํ๋ค (#3.4.5) |
provider.naver.user-name-attribute | { "resultCode":~, "message":~, "response": { "email":~, "name":~, ... } } ๋ค์ด๋ฒ๋ ์ฌ์ฉ์์ ๋ณด๋ฅผ ์ด๋ฐ์์ผ๋ก ๋ฐํํด์ฃผ๋๋ฐ, spring security์์๋ ํ์ ํ๋๋ฅผ ๋ช ์ํ ์ ์๊ณ ์ต์์ ํ๋๋ง user_name์ผ๋ก ์ง์ ํ ์ ์๋ค. ๋ค์ด๋ฒ์ ์ต์์ํ๋๋ resultCode, message, response์ด๊ธฐ๋๋ฌธ์ 3๊ฐ ์ค์ ๊ณจ๋ผ์ผํ๋๋ฐ, ํ์ํ๋๊ฐ ์กด์ฌํ๋ response๋ฅผ user_name์ผ๋ก ์ง์ ํ๋ค. (#3.4.5) |
OAuth2UserInfo ์ธํฐํ์ด์ค์ GoogleUserInfo ํด๋์ค, NaverUserInfo ํด๋์ค
๊ธฐ์กด ๊ตฌ๊ธ ๋ก๊ทธ์ธ์์๋ providerId๊ฐ์ "sub"๋ก ๋ฐ์์ง๋ง ๋ค์ด๋ฒ์ ๊ฒฝ์ฐ๋ "id"๋ก ๋ฐ์์ผํ๋ค.
๋ค๋ฅธ OAuth2 ๋ก๊ทธ์ธ์ ์ถ๊ฐํ๊ฒ๋๋ค๋ฉด ๊ฒฝ์ฐ์ ๋ฐ๋ผ์ providerId๊ฐ์ด ์๋ email, name ๊ฐ์ ๋ค๋ฅด๊ฒ ์ค์ ํด์ผํ ์๋์๋ค. ์ถํ ์ ์ง๋ณด์๋ฅผ ์ํด ์ธํฐํ์ด์ค๋ฅผ ์์ฑํ์!
//๊ตฌ๊ธ์ ๊ฒฝ์ฐ
String providerId = oAuth2User.getAttribute("sub");
//๋ค์ด๋ฒ์ ๊ฒฝ์ฐ
String providerId = oAuth2User.getAttributes().get("response").get("id").toString();
{ "resultCode":~, "message":~, "response": { "id":~, "email":~, "name":~, ... } }
ํจํค์ง > auth ํด๋ > userinfo ํด๋ > OAuth2UserInfo.java
public interface OAuth2UserInfo {
Map<String, Object> getAttributes();
String getProviderId();
String getProvider();
String getEmail();
String getName();
}
ํจํค์ง > auth ํด๋ > userinfo ํด๋ > GoogleUserInfo.java
public class GoogleUserInfo implements OAuth2UserInfo{
private Map<String, Object> attributes;
public GoogleUserInfo(Map<String, Object> attributes) {
this.attributes = attributes;
}
@Override
public Map<String, Object> getAttributes() {
return attributes;
}
@Override
public String getProviderId() {
return attributes.get("sub").toString();
}
@Override
public String getProvider() {
return "google";
}
@Override
public String getEmail() {
return attributes.get("email").toString();
}
@Override
public String getName() {
return attributes.get("name").toString();
}
}
ํจํค์ง > auth ํด๋ > userinfo ํด๋ > NaverUserInfo.java
public class NaverUserInfo implements OAuth2UserInfo{
private Map<String, Object> attributes; //OAuth2User.getAttributes();
private Map<String, Object> attributesResponse;
public NaverUserInfo(Map<String, Object> attributes) {
this.attributes = (Map<String, Object>) attributes.get("response");
this.attributesResponse = (Map<String, Object>) attributes.get("response");
}
@Override
public Map<String, Object> getAttributes() {
return attributes;
}
@Override
public String getProviderId() {
return attributesResponse.get("id").toString();
}
@Override
public String getProvider() {
return "naver";
}
@Override
public String getEmail() {
return attributesResponse.get("email").toString();
}
@Override
public String getName() {
return attributesResponse.get("name").toString();
}
}
OAuth2Login loadUserByUsername
ํจํค์ง > auth ํด๋ > PrincipalOauth2UserService.java
package com.example.demo.oauth;
@Service
public class PrincipalOauth2UserService extends DefaultOAuth2UserService {
@Autowired private UserRepository userRepository;
@Autowired private BCryptPasswordEncoder bCryptPasswordEncoder;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
OAuth2UserInfo oAuth2UserInfo = null; //์ถ๊ฐ
String provider = userRequest.getClientRegistration().getRegistrationId();
//์ถ๊ฐ
if(provider.equals("google")){
oAuth2UserInfo = new GoogleUserInfo(oAuth2User.getAttributes());
}
else if(provider.equals("naver")){
oAuth2UserInfo = new NaverUserInfo(oAuth2User.getAttributes());
}
String providerId = oAuth2UserInfo.getProviderId(); //์์
String username = provider+"_"+providerId;
String uuid = UUID.randomUUID().toString().substring(0, 6);
String password = bCryptPasswordEncoder.encode("ํจ์ค์๋"+uuid);
String email = oAuth2UserInfo.getEmail(); //์์
Role role = Role.ROLE_USER;
User byUsername = userRepository.findByUsername(username);
//DB์ ์๋ ์ฌ์ฉ์๋ผ๋ฉด ํ์๊ฐ์
์ฒ๋ฆฌ
if(byUsername == null){
byUsername = User.oauth2Register()
.username(username).password(password).email(email).role(role)
.provider(provider).providerId(providerId)
.build();
userRepository.save(byUsername);
}
return new PrincipalDetails(byUsername, oAuth2UserInfo); //์์
}
}
OAuth2Login Authentication OAuth2User
ํจํค์ง > auth ํด๋ > PrincipalDetails.java
package com.example.demo.auth;
@Getter
@ToString
public class PrincipalDetails implements UserDetails, OAuth2User {
private User user;
//private Map<String, Object> attributes;
private OAuth2UserInfo oAuth2UserInfo;
//UserDetails : Form ๋ก๊ทธ์ธ ์ ์ฌ์ฉ
public PrincipalDetails(User user) {
this.user = user;
}
//OAuth2User : OAuth2 ๋ก๊ทธ์ธ ์ ์ฌ์ฉ
//public PrincipalDetails(User user, Map<String, Object> attributes) {
// //PrincipalOauth2UserService ์ฐธ๊ณ
// this.user = user;
// this.attributes = attributes;
//}
public PrincipalDetails(User user, OAuth2UserInfo oAuth2UserInfo) {
this.user = user;
this.oAuth2UserInfo = oAuth2UserInfo;
}
/**
* UserDetails ๊ตฌํ
* ํด๋น ์ ์ ์ ๊ถํ๋ชฉ๋ก ๋ฆฌํด
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collect = new ArrayList<>();
collect.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return user.getRole().toString();
}
});
return collect;
}
/**
* UserDetails ๊ตฌํ
* ๋น๋ฐ๋ฒํธ๋ฅผ ๋ฆฌํด
*/
@Override
public String getPassword() {
return user.getPassword();
}
/**
* UserDetails ๊ตฌํ
* PK๊ฐ์ ๋ฐํํด์ค๋ค
*/
@Override
public String getUsername() {
return user.getUsername();
}
/**
* UserDetails ๊ตฌํ
* ๊ณ์ ๋ง๋ฃ ์ฌ๋ถ
* true : ๋ง๋ฃ์๋จ
* false : ๋ง๋ฃ๋จ
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* UserDetails ๊ตฌํ
* ๊ณ์ ์ ๊น ์ฌ๋ถ
* true : ์ ๊ธฐ์ง ์์
* false : ์ ๊น
*/
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* UserDetails ๊ตฌํ
* ๊ณ์ ๋น๋ฐ๋ฒํธ ๋ง๋ฃ ์ฌ๋ถ
* true : ๋ง๋ฃ ์๋จ
* false : ๋ง๋ฃ๋จ
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* UserDetails ๊ตฌํ
* ๊ณ์ ํ์ฑํ ์ฌ๋ถ
* true : ํ์ฑํ๋จ
* false : ํ์ฑํ ์๋จ
*/
@Override
public boolean isEnabled() {
return true;
}
/**
* OAuth2User ๊ตฌํ
* @return
*/
@Override
public Map<String, Object> getAttributes() {
//return attributes;
return oAuth2UserInfo.getAttributes();
}
/**
* OAuth2User ๊ตฌํ
* @return
*/
@Override
public String getName() {
//String sub = attributes.get("sub").toString();
//return sub;
return oAuth2UserInfo.getProviderId();
}
}
OAuth2Login Domain๊ตฌํ
ํจํค์ง > domain ํด๋ > Role.java : ์ด์ ๊ณผ ๊ฐ์
ํจํค์ง > domain ํด๋ > User.java : ์ด์ ๊ณผ ๊ฐ์
OAuth2Login Controller ๊ตฌํ
ํจํค์ง > controller ํด๋ > UserController.java : ์ด์ ๊ณผ ๊ฐ์
OAuth2Login View๊ตฌํ
resources > templates > join.html : ์ด์ ๊ณผ ๊ฐ์
resources > templates > login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>๋ก๊ทธ์ธ ํ์ด์ง</title>
</head>
<body>
<h1>๋ก๊ทธ์ธ ํ์ด์ง</h1>
<hr/>
<h2>๋ก๊ทธ์ธ ์ ์ : </h2>
<p sec:authentication="principal"></p>
<div sec:authorize="isAnonymous()" style="background-color:pink; padding:1em;">
<form action="/login" method="post" >
<input type="text" name="username" />
<input type="password" name="password" />
<button>๋ก๊ทธ์ธ</button>
</form>
<a href="/oauth2/authorization/google">๊ตฌ๊ธ ๋ก๊ทธ์ธ</a>
<a href="/oauth2/authorization/naver">๋ค์ด๋ฒ ๋ก๊ทธ์ธ</a>
<!-- /oauth2/authorization/{registrationId}์ ์์ฒญ์ด ๋ค์ด์ค๋ฉด,
์คํ๋ง ์ํ๋ฆฌํฐ๊ฐ provider์ authorization-uri๋ก ์์ฒญ์ ์ ๋ฌํ๋ค-->
<br><br>
<a href="/joinForm">ํ์๊ฐ์
ํ๊ธฐ</a><br>
</div>
<div sec:authorize="isAuthenticated()" style="background-color:pink; padding:1em;">
<a href="/logout">๋ก๊ทธ์์</a>
</div>
<br><br>
<a href="/user">์ ์ </a><br>
<a href="/manager" sec:authorize="hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')">๋งค๋์ </a><br>
<a href="/admin" sec:authorize="hasRole('ROLE_ADMIN')">์ด๋๋ฏผ</a><br>
<br><br>
<a href="/form/loginInfo">Form ๋ก๊ทธ์ธ ์ ๋ณด</a>
<a href="/oauth/loginInfo">OAuth2 ๋ก๊ทธ์ธ ์ ๋ณด</a>
<a href="/loginInfo">๋ก๊ทธ์ธ ์ ๋ณด</a>
</body>
</html>
'๐ฟ Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Spring Security] OAuth ์นด์นด์ค ๋ก๊ทธ์ธํ๊ธฐ (0) | 2022.02.23 |
---|---|
[OAuth2] ๋์๊ณผ์ (0) | 2022.02.05 |
[Spring Security] OAuth ๊ตฌ๊ธ ๋ก๊ทธ์ธํ๊ธฐ (0) | 2022.02.02 |
[Spring Security] ๋์๋ฐฉ๋ฒ ๋ฐ Form, OAuth ๋ก๊ทธ์ธํ๊ธฐ (Feat.Thymeleaf ํ์๋ฆฌํ) (8) | 2022.02.01 |
Spring + MariaDB + Mybatis ์ฐ๋ ๋ฐ ์คํํด๋ณด๊ธฐ (0) | 2021.12.22 |