1. 일반 로그인과 구글 로그인 테스트 페이지 작성
//세션정보 확인을 위한 테스트 페이지
@GetMapping("/test/login")
public @ResponseBody String testLogin(Authentication authentication, // 이 방법으로는 authentication이 오브젝트 이기 때문에 캐스팅 필요
@AuthenticationPrincipal PrincipalDetails userDetails) { // DI 방식은 다형성에 의해 바로 User를 받아올 수 있음.
System.out.println("==========================/test/login ===========================");
PrincipalDetails principalDetails = (PrincipalDetails)authentication.getPrincipal();
System.out.println("authentication: "+principalDetails.getUser());
System.out.println("userDetails: " + userDetails.getUser());
return "세션 정보 확인하기";
}
//oauth로그인 세션 테스트
@GetMapping("/test/oauth/login")
public @ResponseBody String testOAuthLogin(Authentication authentication, // 이 방법으로는 authentication이 오브젝트 이기 때문에 캐스팅 필요
@AuthenticationPrincipal PrincipalDetails userDetails) { // DI 방식은 다형성에 의해 바로 User를 받아올 수 있음.
System.out.println("==========================/test/oauth/login ===========================");
OAuth2User oauth2User = (OAuth2User)authentication.getPrincipal();
System.out.println("authentication: "+oauth2User.getAttributes());
return "세션 정보 확인하기";
}
위에 것이 일반 로그인 세션 테스트.
두 가지로 테스트를 해봤는데 하나는 메서드 내부에서 object로 반환되는 authentication.getPrincipal()을 PrincipalDetails로 캐스팅하여 User 객체를 뽑아보는 것이고,
어노테이션을 활용한 방식은 원래 UserDetails 객체를 DI 하는 방식인데 PrincipalDetails가 UserDetails를 구현하고 있기 때문에 가능하다.
아래쪽은 Oauth로그인 테스트.
복붙인데 다른점은 OAuth로그인의 경우 authentication.getPrincipal()을 OAuth2User로 캐스팅 해야 한다는 것이다. 그리고 getUser 정보는 getAttributes와 상응한다.
그러면 이제 하나의 로그인 페이지에서 두 가지 방식을 동시에 처리하기 위해서는 PrincipalDetails 객체가 UserDetails와 OAuth2User를 둘 다 구현하면 된다는 것을 알 수 있다.
2. PrincipalDetails 수정
package com.pure.security.config.auth;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User;
import com.pure.security.model.User;
import lombok.Data;
// 시큐리티가 /login 주소 요청이 오면 로그인을 진행시킴.
// 로그인이 완료되면 Security session에 로그인 정보를 넣어줌. 이 정보는 Security ContextHolder에 들어감.
// Security는 Authentication 타입만 들어감.
// Authentication 안에 들어갈 User 정보는 UserDetails 타입 객체만 가능.
// Security Session 안에 Authentication 안에 UserDetils
@Data
public class PrincipalDetails implements UserDetails, OAuth2User {
private User user;
private Map<String, Object> attributes;
//일반 로그인 생성자
public PrincipalDetails(User user) {
this.user = user;
}
//OAuth 로그인 생성자
public PrincipalDetails(User user, Map<String, Object> attributes) {
this.user = user;
this.attributes = attributes;
}
//해당 유저의 권한을 리턴
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collect = new ArrayList<>();
collect.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return user.getRole();
}
});
return collect;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public Map<String, Object> getAttributes() {
return attributes;
}
@Override
public String getName() {
return (String) attributes.get("sub"); //이렇게 하면 name을 리턴할 수 있지만 이 메서드를 아마 안 쓸 것 같다.
}
}
일반 로그인시의 생성자와 OAuth 로그인 시의 생성자가 둘 다 필요하다.
OAuth가 필요한 이유는 Map 형식으로 되어 있는 정보를 받아서 강제 회원가입을 시킬 것이기 때문.
구글 강제 회원가입시 Builder패턴을 이용할 것이기 때문에 User 객체 수정.
package com.pure.security.model;
import java.sql.Timestamp;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import org.hibernate.annotations.CreationTimestamp;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Entity
@NoArgsConstructor
public class User {
@Id // primary key
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String username;
private String password;
private String email;
private String role;
private String provider;
private String providerId;
@CreationTimestamp
private Timestamp createDate;
@Builder
public User(String username, String password, String email, String role, String provider, String providerId,
Timestamp createDate) {
super();
this.username = username;
this.password = password;
this.email = email;
this.role = role;
this.provider = provider;
this.providerId = providerId;
this.createDate = createDate;
}
}
PrincipalOauth2UserService 수정
package com.pure.security.config.oauth;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import com.pure.security.config.auth.PrincipalDetails;
import com.pure.security.model.User;
import com.pure.security.repository.UserRepository;
@Service
public class PrincipalOauth2UserService extends DefaultOAuth2UserService {
@Autowired
private UserRepository userRepository;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
System.out.println("userRequset.getClientRegistration: "+ userRequest.getClientRegistration()); //registrationId로 어떤 OAuth로 로그인 했는지 알 수 있음.
System.out.println("userRequset.getAccessToken: "+ userRequest.getAccessToken());
System.out.println("userRequest.getAttributes: "+ super.loadUser(userRequest).getAttributes());
OAuth2User oauth2User = super.loadUser(userRequest);
String provider = userRequest.getClientRegistration().getRegistrationId(); //google?
String providerId = oauth2User.getAttribute("sub");
String username = provider+"_"+providerId;
String password = new BCryptPasswordEncoder().encode("1234");
String email = oauth2User.getAttribute("email");
String role = "ROLE_USER";
//해당 username이 이미 있는지 확인
User userEntity = userRepository.findByUsername(username);
//구글로 처음 로그인 하면 자동으로 회원가입 시킴.
if(userEntity == null) {
System.out.println("최초 구글 로그인으로 회원가입을 진행했습니다.");
userEntity = User.builder()
.username(username)
.password(password)
.email(email)
.role(role)
.provider(providerId)
.providerId(providerId)
.build();
userRepository.save(userEntity);
}else {
System.out.println("구글 회원가입 정보가 있습니다. 로그인을 진행했습니다.");
}
return new PrincipalDetails(userEntity, oauth2User.getAttributes()); //이 PrincipalDetails가 Authentication 객체 안에 들어가게 됨.
}
}
원래 강의에선 @Autowired로 Bcrypt를 주입하는데 이렇게 했더니 순환참조에러로 실행자체가 안되서 지우고 메서드 내부에서 객체를 새로 만들어서 진행했음.
indexConroller 수정
package com.pure.security.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.pure.security.config.auth.PrincipalDetails;
import com.pure.security.model.User;
import com.pure.security.repository.UserRepository;
@Controller // View를 return 하겠다는 뜻.
public class IndexController {
@Autowired
private UserRepository userRepository;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
//세션정보 확인을 위한 테스트 페이지
@GetMapping("/test/login")
public @ResponseBody String testLogin(Authentication authentication, // 이 방법으로는 authentication이 오브젝트 이기 때문에 캐스팅 필요
@AuthenticationPrincipal PrincipalDetails userDetails) { // DI 방식은 다형성에 의해 바로 User를 받아올 수 있음.
System.out.println("==========================/test/login ===========================");
PrincipalDetails principalDetails = (PrincipalDetails)authentication.getPrincipal();
System.out.println("authentication: "+principalDetails.getUser());
System.out.println("userDetails: " + userDetails.getUser());
return "세션 정보 확인하기";
}
//oauth로그인 세션 테스트
@GetMapping("/test/oauth/login")
public @ResponseBody String testOAuthLogin(Authentication authentication, // 이 방법으로는 authentication이 오브젝트 이기 때문에 캐스팅 필요
@AuthenticationPrincipal PrincipalDetails userDetails) { // DI 방식은 다형성에 의해 바로 User를 받아올 수 있음.
System.out.println("==========================/test/oauth/login ===========================");
OAuth2User oauth2User = (OAuth2User)authentication.getPrincipal();
System.out.println("authentication: "+oauth2User.getAttributes());
return "세션 정보 확인하기";
}
@GetMapping({"","/"})
public String index() {
//mustache 기본폴더 src/main/resources/
//viewResolver 설정: templates(prefix) .mustache(suffix) (기본 설정이기 때문에 .yml에서 생략가능)
return "index"; //경로 : src/main/resources/templates/index.mustache
}
//OAuth 로그인을 하든, 일반 로그인을 하든 PrincipalDetails로 받아짐.
@GetMapping("/user")
public @ResponseBody String user(@AuthenticationPrincipal PrincipalDetails principalDetails) {
System.out.println("principalDetails: "+principalDetails.getUser());
return "user";
}
@GetMapping("/admin")
public String admin() {
return "admin";
}
@GetMapping("/manager")
public String manager() {
return "manager";
}
@GetMapping("/loginForm")
public String loginForm() {
return "loginForm";
}
@PostMapping("/join")
public String join(User user) {
System.out.println(user);
user.setRole("ROLE_USER");
String rawPassword = user.getPassword();
String encPassword = bCryptPasswordEncoder.encode(rawPassword);
user.setPassword(encPassword);
userRepository.save(user);
return "redirect:/loginForm";
}
@GetMapping("/joinForm")
public String joinForm() {
return "joinForm";
}
@Secured("ROLE_ADMIN")
@GetMapping("/info")
public @ResponseBody String info() {
return "개인정보";
}
@PreAuthorize("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')") //함수가 실행되기 전에 권한 필터링
@GetMapping("/data")
public @ResponseBody String data() {
return "data";
}
}
이렇게 하면 /user 페이지를 들어가 봤을 때 일반 로그인을 하든 구글 로그인을 하든 PrincipalDetails로 모두 User 정보를 잘 받아오는 것을 확인할 수 있다.
'취업 준비 > Spring security' 카테고리의 다른 글
9. 네이버 OAuth 로그인 (0) | 2022.02.24 |
---|---|
8. 페이스북 Oauth 로그인 (0) | 2022.02.22 |
6. OAuth2 구글 로그인 (0) | 2022.02.17 |
5. 시큐리티 권한처리 (0) | 2022.02.16 |
4. 로그인 만들기 (0) | 2022.02.16 |