구글 로그인 세션 만들기
https://developers.google.com/identity/sign-in/web/sign-in?hl=ko
OAuth 클라이언트 ID 만들기
http://localhost:8080/ex7/login/oauth2/code/google
JSON 다운로드
#OAuth2 include
spring.profiles.include = oauth
💡 **My Additional information**
spring.security.oauth2.client.registration.google.client-id=776858494674-fvo383dker0jierot3pjciiaon2lluqn.apps.googleusercontent.com
spring.security.oauth2.client.registration.google.client-secret=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
spring.security.oauth2.client.registration.google.scope=email
src/main/resources/application-oauth.properties File 생성하기
//소셜 로그인
httpSecurity.oauth2Login(new Customizer<OAuth2LoginConfigurer<HttpSecurity>>() {
@Override
public void customize(OAuth2LoginConfigurer<HttpSecurity> httpSecurityOAuth2LoginConfigurer) {
httpSecurityOAuth2LoginConfigurer.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.sendRedirect(request.getContextPath()+"/sample/all");
}
});
}
});
@Log4j2
@Service
public class ClubOAuth2userDetailsService extends DefaultOAuth2UserService {
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
// String clientName = userRequest.getClientRegistration().getClientName();
// log.info("ClientName >> ",clientName);
// log.info("info_from_google >> ", userRequest.getAdditionalParameters());
// OAuth2User oAuth2User = super.loadUser(userRequest);
// oAuth2User.getAttributes().forEach((k,v) ->{log.info(k+":"+v);});
OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
OAuth2User oAuth2User = delegate.loadUser(userRequest); //서비스에서 가져온 유저 정보를 담음
return super.loadUser(userRequest);
}
private SocialType getSocialType(String registrationId){
if(SocialType.NAVER.name().equals(registrationId)){
return SocialType.NAVER;
}
if(SocialType.KAKAO.name().equals(registrationId)){
return SocialType.KAKAO;
}
return SocialType.GOOGLE;
}
private enum SocialType{
KAKAO,NAVER,GOOGLE
}
}
자바bean이 되어야 와이어링이 되고 전체에서 사용이 가능함
@Log4j2
@Service
@RequiredArgsConstructor
public class ClubOAuth2userDetailsService extends DefaultOAuth2UserService {
private final ClubMemberRepository clubMemberRepository;
private final PasswordEncoder passwordEncoder; //
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
log.info("============= userRequest: " + userRequest);
OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate =
new DefaultOAuth2UserService();
OAuth2User oAuth2User = delegate.loadUser(userRequest); //서비스에서 가져온 유저정보
String registrationId = userRequest.getClientRegistration().getRegistrationId();
SocialType socialType = getSocialType(registrationId.trim().toString());
String userNameAttributeName = userRequest.getClientRegistration()
.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();
log.info("userNameAttributeName >> " + userNameAttributeName);
Map<String, Object> attributes = oAuth2User.getAttributes();
for (Map.Entry<String, Object> entry : attributes.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
String email = null;
if (socialType.name().equals("GOOGLE"))
email = oAuth2User.getAttribute("email");
log.info("Email: " + email);
ClubMember clubMember = saveSocialMember(email);
ClubMemberAuthDTO clubMemberAuthDTO = new ClubMemberAuthDTO(
clubMember.getEmail(),
clubMember.getPassword(),
clubMember.getCno(),
true,
clubMember.getRoleSet().stream().map(
role -> new SimpleGrantedAuthority("ROLE_" + role.name()))
.collect(Collectors.toList())
, attributes
);
clubMemberAuthDTO.setFromSocial(clubMember.isFromSocial());
clubMemberAuthDTO.setName(clubMember.getName());
log.info("clubMemberAuthDTO: " + clubMemberAuthDTO);
return clubMemberAuthDTO;
}
OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService(); 는 소셜에서 가져온 유저정보(userRequest)를 담기 위한 객체(OAuth2User )를 생성 해주는 것을 의미한다
OAuth2UserService 는 social 로 부터 정보를 받기 위한 객체를 생성한다
OAuth2User 는 스프링시큐리티에서 사용하는 세션 객체를 의미함
OAuth2User loadUser(OAuth2UserRequest userRequest)
→ userRequest는 소셜 페이지에서 로그인하고 그 정보를 빌려오는 것을 의미함
유저 정보(userRequest)를 세션 객체(oAuth2User )로 변환
OAuth2User oAuth2User = delegate.loadUser(userRequest);
private ClubMember saveSocialMember(String email) {
Optional<ClubMember> result = clubMemberRepository.findByEmail(email);
if (result.isPresent()) return result.get();
ClubMember clubMember = ClubMember.builder()
.email(email)
.password(passwordEncoder.encode("1"))
.fromSocial(true)
.build();
clubMember.addMemberRole(ClubMemberRole.USER);
clubMemberRepository.save(clubMember);
return clubMember;
}
소셜에서 넘어온 정보가 DB에 없을 때 저장하는 부분
private SocialType getSocialType(String registrationId) {
if (SocialType.NAVER.name().equals(registrationId)) {
return SocialType.NAVER;
}
if (SocialType.KAKAO.name().equals(registrationId)) {
return SocialType.KAKAO;
}
return SocialType.GOOGLE;
}
enum SocialType {
KAKAO, NAVER, GOOGLE
}
}
로그인버튼 누르고 로그인 성공시
@Bean
protected SecurityFilterChain config(HttpSecurity httpSecurity)
throws Exception {
// csrf 사용안하는 설정
httpSecurity.csrf(httpSecurityCsrfConfigurer -> {
httpSecurityCsrfConfigurer.disable();
});
// authorizeHttpRequests :: 선별적으로 접속을 제한하는 메서드
// 모든 페이지가 인증을 받도록 되어 있는 상태
// httpSecurity.authorizeHttpRequests(auth -> auth.anyRequest().authenticated());
httpSecurity.authorizeHttpRequests(
auth -> auth.requestMatchers(AUTH_WHITElIST).permitAll()
.requestMatchers("/sample/admin/**").hasRole("ADMIN")
.requestMatchers("/sample/manager/**").access(
new WebExpressionAuthorizationManager(
"hasRole('ADMIN') or hasRole('MANAGER')")
)
.anyRequest().authenticated());
// formLogin()를 정의해야만 해도 자동생성된 로그인페이지로 이동가능.
httpSecurity.formLogin(new Customizer<FormLoginConfigurer<HttpSecurity>>() {
@Override
public void customize(FormLoginConfigurer<HttpSecurity> httpSecurityFormLoginConfigurer) {
httpSecurityFormLoginConfigurer
// .loginPage("/sample/login")
// .loginProcessingUrl("/sample/login")
// .defaultSuccessUrl("/")
.successHandler(getAuthenticationSuccessHandler());
}
});
// logout() 정의 안해도 로그아웃 페이지 사용 가능. 사용자 로그아웃 페이지 지정할 때사용
httpSecurity.logout(new Customizer<LogoutConfigurer<HttpSecurity>>() {
@Override
public void customize(LogoutConfigurer<HttpSecurity> httpSecurityLogoutConfigurer) {
httpSecurityLogoutConfigurer
// logoutUrl() 설정할 경우 html action 주소 또한 같이 적용해야 함.
// logoutUrl()으로 인해 기존 logout 주소 이동은 가능하나 기능은 사용 안됨.
.logoutUrl("/logout")
.logoutSuccessUrl("/") // 로그아웃 후에 돌아갈 페이지 설정
//.logoutSuccessHandler((request, response, authentication) -> {
// logout 후에 개별적으로 여러가지 상황에 대하여 적용 가능한 설정
//})
.invalidateHttpSession(true); // 서버 세션을 무효화, false도 클라이언트측 무효화
}
});
httpSecurity.oauth2Login(new Customizer<OAuth2LoginConfigurer<HttpSecurity>>() {
@Override
public void customize(OAuth2LoginConfigurer<HttpSecurity> httpSecurityOAuth2LoginConfigurer) {
httpSecurityOAuth2LoginConfigurer.successHandler(getAuthenticationSuccessHandler());
}
});
return httpSecurity.build();
}
AuthenticationSuccessHandler 는 로그인 되었을 때, 처리하는 객체
@Bean
public AuthenticationSuccessHandler getAuthenticationSuccessHandler() {
return new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication)
throws IOException, ServletException {
UserDetails principal = (UserDetails) authentication.getPrincipal();
Collection<GrantedAuthority> authors =
(Collection<GrantedAuthority>) principal.getAuthorities();
List<String> result = authors.stream().map(new Function<GrantedAuthority, String>() {
@Override
public String apply(GrantedAuthority grantedAuthority) {
return grantedAuthority.getAuthority();
}
}).collect(Collectors.toList());
System.out.println(">>" + result.toString());
for (int i = 0; i < result.size(); i++) {
if (result.get(i).equals("ROLE_ADMIN")) {
response.sendRedirect(request.getContextPath() + "/sample/admin");
} else if (result.get(i).equals("ROLE_MANAGER")) {
response.sendRedirect(request.getContextPath() + "/sample/manager");
} else {
response.sendRedirect(request.getContextPath() + "/sample/all");
}
break;
}
}
};
}
로그인 실패시 , 띄워주는 페이지 생성하기
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.sendRedirect(request.getContextPath()+"/auth/accessDenied");
}
}
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.sendRedirect(request.getContextPath()+"/auth/authenticationFailure");
}
}
@Bean
protected SecurityFilterChain config(HttpSecurity httpSecurity)
throws Exception {
httpSecurity.exceptionHandling(httpSecurityExceptionHandlingConfigurer -> {
httpSecurityCsrfConfigurer.disable();
});
return httpSecurity.build();
}
@Bean
public AuthenticationSuccessHandler getAuthenticationSuccessHandler()
{
return new CustomLoginSuccessHandler();
};
@Bean
public AccessDeniedHandler getAccessDeniedHandler(){
return new CustomAccessDeniedHandler();
}
@Bean
public AuthenticationFailureHandler getAuthenticationFailureHandler(){
return new CustomAuthenticationFailuerHandler();
}
@Bean LogoutSuccessHandler getLogoutSuccessHandler(){
return new CustomLogoutSuccessHandler();
}
}
404Error 처리
//@RestControllerAdvice
@ControllerAdvice
public class ExceptionHandlerControllerAdvice {
// 404 exception
@ExceptionHandler(NoResourceFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public String handl404(NoResourceFoundException e, Model model) {
model.addAttribute("errorMessage", e.getMessage());
return "/error/notFound";
}
// Database error
@ExceptionHandler(DataAccessException.class)
public String handleDataAccessException(DataAccessException e, Model model) {
model.addAttribute("errorMessage", e.getMessage());
e.printStackTrace();
return "error/databaseError";
}
// internal server error
@ExceptionHandler(HttpServerErrorException.InternalServerError.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String handleException(HttpServerErrorException.InternalServerError e, Model model) {
model.addAttribute("errorMessage", e.getMessage());
e.printStackTrace();
return "error/serverError";
}
}
.requestMatchers(new AntPathRequestMatcher("/error/**")).permitAll() 해당하는 페이지가 들어왔을때 어떻게 해줄것인가에 대한 보안에 대한 설정
// authorizeHttpRequests :: 선별적으로 접속을 제한하는 메서드
// 모든 페이지가 인증을 받도록 되어 있는 상태
// httpSecurity.authorizeHttpRequests(auth -> auth.anyRequest().authenticated());
httpSecurity.authorizeHttpRequests(
auth -> auth.requestMatchers(AUTH_WHITElIST).permitAll()
.requestMatchers(new AntPathRequestMatcher("/auth/**")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/error/**")).permitAll()
.requestMatchers("/sample/admin/**").hasRole("ADMIN")
.requestMatchers("/sample/manager/**").access(
new WebExpressionAuthorizationManager(
"hasRole('ADMIN') or hasRole('MANAGER')")
)
.anyRequest().authenticated());
database 아이디를 통하여 로그인하는 경우 사용, 소셜로그인은 사용불가
httpSecurity.rememberMe(new Customizer<RememberMeConfigurer<HttpSecurity>>() {
@Override
public void customize(RememberMeConfigurer<HttpSecurity> httpSecurityRememberMeConfigurer) {
httpSecurityRememberMeConfigurer.tokenValiditySeconds(60*60*24*7);
}
});
로그인하고 새창을 열었을 때, 로그인 하지 않아도 로그인 됨
'PROGRAMING📚 > Spring📑' 카테고리의 다른 글
CSRF - API 서버 (0) | 2024.09.25 |
---|---|
JSP(JavaServer Pages) (1) | 2024.09.25 |
Spring Security - USER/MANAGER/ADMIN 에 따른 권한 부여 (0) | 2024.09.25 |
SecuritConfig -코드 해석 (0) | 2024.09.25 |
[Spring]::Startbootstrap - Simple Sidebar 사용하기 (0) | 2024.07.11 |
댓글