[2023.01.22] 개발자 교육 84일 차 : 강의-SpringSecurity 학습 [구디 아카데미]

DaramGee·2024년 1월 22일
0

강의 내용

스프링 시큐리티 인증 설정

  • 구글 클라우드 인증

  • 구글 클라우드 콘솔 접속 -> 앱 선택 -> 사용자 인증 정보 클릭
  • 앱클라이언트 클릭(없다면... 상단의 사용자 인증 정보 만들기)
  • 프로젝트에서 사용하는 url 입력
  • oauth2문서에서 입력해야하는 uri 양식에 맞춰 리디렉션 URI 입력
    - 위 url/login/oauth2/code/google
  • security-step4 프로젝트 생성 및 설정

  • 인증 관련 의존성 추가(스프링Io 확인)
dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
}
  • 인증 관련 id, security 추가
    - 구글클라우드 콘솔 -> 앱선택 -> API 및 서비스 -> 사용자인증정보 -> 오른쪽에 클라이언트 id, 보안비밀번호 확인 가능
    - 아래 양식으로 yml에 추가
security:  
  oauth2:  
    client:  
      registration:  
        google:  
            client-id: your-client-id  
            client-secret: your-client-secret
          scope:  
            - email  
            - profile

만약, 분명히 Id, secret 부분 잘 넣었는데도 구글인증을 할 때 엑세스가 거부되었다고 나온다면??
OAuth동의 화면에서 게시상태를 반드시 확인해보자(다른 곳에 오타가 없다면 ㅠㅠ )
앱 게시를 한 뒤에야 OAuth인증이 가능한 상태가 된다!!


  • 세션노트

  • Security Session에 들어가는 객체가 Authentication으로 정해져 있음.

  • (톰캣 제공 세션으로 일하는 스프링)
  • 세션은 서버사이드(캐쉬메모리)
  • 쿠키는 클라이언트 사이드
  • 세션저장

  • Java에선?

  • HttpSession session = req.getSession() -> XXX(HttpSession session)

  • session.setAttribute("s_name","홍길동")

  • session.getAttribute("s_name")

  • Spring에선?

  • Authentication안(세션)에 user 저장 시 반드시 UserDetails타입이어야 받아줄 수 있음.

  • 우리는 실습을 통해 UserDetails를 구현하기 위한 User클래스와 PrincipalDetails 클래스를 생성하였음.
    - User : user 정보갖고 있는 Model 클래스
    - PrincipalDetails : UserDetails를 구현한 클래스 => Spring Security에서 사용자 인증 및 권한 부여에 활용

  • 스프링 시큐리티는 SecurityConfig.java에서 loginProcessingUrl("/loginProcess")에 등록

<form action=/loginProcess>
  • 스프링 시큐리티가 낚아채서 로그인 과정 진행
  • 로그인 완료 후 시큐리티 세션 생성 - > Security ContextHolder에 세션값 담음
  • UserDetails에 저장되어있는 정보를 스크링 시큐리티가 쥐게 된다.

스프링 시큐리티는 별도 세션을 관리하진 않는다. 본진은 캐시 메모리 하나인데, 톰캣이란 was가 해주고 있다.
결국 톰캣이 관리하는 것에 접근해서 사용하는 것인데
사용하는 약속은 Authentication이고, 그 안의 타입을 UserDetails로 정해져 있는 것이다.
이런 흐름으로 세션에 접근하여 사용할 수 있다는 큰 그림을 그릴 수 있으면 된다.

  • 테스트 시나리오

  • 로그인 -> 세션관리 진행
  • 세션에 접근하는 방법은 두 가지가 있다.
    - Authentication -> UserDetails 두번 접근해서 꺼내오기
    - UserDetails로 바로 접근해서 꺼내오기
2024-01-22 10:33:57.602 [INFO] 24689 [PrincipalDetailService.java : 28] {지연1}
2024-01-22 10:33:57.620 [INFO] 24689 [UserDao.java : 18] {login}
2024-01-22 10:33:57.797 [INFO] 24689 [ViewController.java : 45] {indextrue}
2024-01-22 10:33:57.797 [INFO] 24689 [ViewController.java : 46] {indexfalse}
2024-01-22 10:35:01.767 [INFO] 24689 [ViewController.java : 34] {principalDetails: PrincipalDetails(user=User(id=14, username=지연1, password=$2a$10$uuuJscXvSjd1OKCL0MeAJO7czRYN7OXYmZMl4z5Lkvemk8umH/IKW, email=test@hot.com, role=ROLE_USER, provider=, providerId=, createDate=2024-01-22))}
2024-01-22 10:35:01.769 [INFO] 24689 [ViewController.java : 36] {principalDetails.getUser(): User(id=14, username=지연1, password=$2a$10$uuuJscXvSjd1OKCL0MeAJO7czRYN7OXYmZMl4z5Lkvemk8umH/IKW, email=test@hot.com, role=ROLE_USER, provider=, providerId=, createDate=2024-01-22)}
2024-01-22 10:35:01.769 [INFO] 24689 [ViewController.java : 38] {userDetails.getUser(): User(id=14, username=지연1, password=$2a$10$uuuJscXvSjd1OKCL0MeAJO7czRYN7OXYmZMl4z5Lkvemk8umH/IKW, email=test@hot.com, role=ROLE_USER, provider=, providerId=, createDate=2024-01-22)}

실습 시나리오

  • 구글로그인으로 실습

  • 왜? 소셜로그인은 안심이 되고, 회원가입을 하지 않아도 되는 장점이 있음.
  • 방식? access Token -> refresh Token -> 유지, 상태 관리 가능{중급기술-시큐리티}
  • 추가로? 일반로그인, 구글로그인 모두 한 가지 타입으로 묶어서 처리할 수 있도록 클래스 재정의!
    - 구글로 로그인
  • 강제 회원가입 처리

  • JWT로 처리

  • 구글 인증과 달리 Jwt는 다 넘김

지금의 학습은?

  • 인증과 권한 부여

  • 구글 로그인을 통한 사용자 인증 및 권한 부여를 구현하기 위해서는 Spring Security와 OAuth 2.0을 사용!
  1. 의존성 설정:

    • 먼저, spring-boot-starter-security와 구글 OAuth 2.0 클라이언트 의존성을 추가합니다. build.gradle
dependencies {  
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'  
    implementation 'org.springframework.boot:spring-boot-starter-security'  
    implementation 'org.springframework.boot:spring-boot-starter-web'  
    implementation "org.apache.tomcat.embed:tomcat-embed-jasper"  
    implementation 'com.google.code.gson:gson:2.9.0'  
    implementation 'org.glassfish.web:jakarta.servlet.jsp.jstl:3.0.1'  
    implementation 'jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:3.0.0'  
    runtimeOnly 'com.oracle.database.jdbc:ojdbc11'  
    implementation 'com.oracle.ojdbc:orai18n:19.3.0.0'  
    implementation 'com.zaxxer:HikariCP:5.1.0'  
    implementation 'org.mybatis:mybatis:3.5.15'  
    implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'    
	implementation 'jakarta.el:jakarta.el-api:5.0.1'     
	compileOnly 'org.projectlombok:lombok'  
    developmentOnly 'org.springframework.boot:spring-boot-devtools'  
    annotationProcessor 'org.projectlombok:lombok'  
    testImplementation 'org.springframework.boot:spring-boot-starter-test'  
    testImplementation 'org.springframework.security:spring-security-test'  
}
  1. application.properties 또는 application.yml 설정:

    • 구글 OAuth 2.0 클라이언트 ID 및 시크릿 키를 설정합니다. application.yml
server:  
  tomcat:      
    additional-tld-skip-patterns: "*.jar"    
port: 8000  
  error:  
    path: /error  
  servlet:  
    context-path: /  
    encoding:  
      charset: UTF-8  
      enabled: true  
      force: true  
spring:  
  output:  
    ansi:  
      enabled: always  
  mvc:  
    view:  
      prefix: /WEB-INF/views/  
      suffix: .jsp  
  datasource:  
    hikari:  
      jdbc-url:  
      username: 
      password:  
      driver-class-name: oracle.jdbc.OracleDriver  
      connection-timeout: 20000  
      validation-timeout: 3000  
      minimum-idle: 5  
      maximum-pool-size: 12  
      idle-timeout: 300000  
      max-lifetime: 1200000  
      auto-commit: true  
      pool-name: oraPool  
  jpa:  
    open-in-view: true  
    hibernate:  
      ddl-auto: create  
      naming:  
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl  
    show-sql: true      
  servlet:  
    multipart:  
      max-file-size: 10MB  
      enabled: true  
  security:  
    oauth2:  
      client:  
        registration:  
          google:  
            client-id: your-client-id  
            client-secret: your-client-secret
            scope:  
            - email  
            - profile
  1. 구글 로그인 사용자 정보를 담을 모델 클래스 생성:

    • 사용자 정보를 담을 모델 클래스를 생성합니다.User.java
package com.example.demo.model;  
  
import lombok.Builder;  
import lombok.Data;  
  
@Data  
public class User {  
    public User(){}  
    @Builder  
    public User(String username, String password, String email, String role, String provider, String providerId, String createDate){  
        this.id = id;  
        this.username = username;//소셜로그인-강제회원가입처리-provider_providerId 예)googel_123232123123  
        this.password = password;  
        this.email = email;  
        this.role = role;  
        this.provider = provider;  
        this.providerId = providerId;  
        this.createDate = createDate;  
    }  
    private int id;  
    private String username="";  
    private String password;  
    private String email;  
    private String role;  
    private String provider;//google, kakao, github  
    private String providerId;//userId, uid  
    private String createDate;//java.sql.Timestamp  
}
  1. CustomUserDetailsService 구현:

    • UserDetailsService를 상속받아 구현한 클래스를 생성합니다. PrincipalDetails
package com.example.demo.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.example.demo.model.User;  
import lombok.Data;  

@Data //@Getter + @Setter - 외부 클래스에서 User객체 정보 접근을 위해 추가함  
//일반로그인과 구글 로그인을 한 가지 타입으로 묶어서 양쪽 타입 모두를 처리할 수 있도록 클래스 재정의한다  
//구글 로그인을 처리하기 위해서 OAuth2User를 추가하였다  
public class PrincipalDetails implements UserDetails, OAuth2User {  
    private User user; //캡슐레이션   
//구글 로그인시 구글서버에서 넣어주는 정보가 Map의 형태임  
    private Map<String,Object> attributes;  
    public PrincipalDetails(User user){  
        this.user = user;  
    }  
    //일반로그인과 구글 로그인 두가지를 모두 처리한다  
    //OAuth로그인시 사용하는 생성자 이다 - GitHub, 네이버  
    public PrincipalDetails(User user, Map<String,Object> attributes){  
        this.user = user;  
        this.attributes = attributes;  
    }  
    //해당 User의 권한을 리턴함  
    @Override  
    public Collection<? extends GrantedAuthority> getAuthorities() {  
        Collection<GrantedAuthority> collect = new ArrayList<>();  
        collect.add(new GrantedAuthority() {  
            @Override  
            public String getAuthority() {  
                return user.getRole();  
            }  
        });  
        return collect;  
    }  
    //세션에 담을 다른 컬럼 정보도 추가 가능하다  
    public int getId(){  
        return user.getId();  
    }  
    public String getEmail(){  
        return user.getEmail();  
    }  
    public String getRole(){  
        return user.getRole();  
    }  
    //데이터베이스와 매칭이 안되면 loginFaill.jsp 호출됨  
    @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;  
    }  
    //구글 로그인 후에 프로필 정보를 담을 변수  
    //{sub=구글에서 할당하는 나에대한 고유식별자숫자값, name=이름, picture=, email=, email_verified=true,  lokale=ko}  
    @Override  
    public Map<String,Object> getAttributes(){  
        return attributes;  
    }  
    @Override  
    public String getName() {  
        return null;  
    }  
    }
  1. OAuth2UserDetailsService 구현:

    • OAuth 2.0 사용자 정보를 처리할 서비스를 구현합니다. PrincipalDetailService
package com.example.demo.auth;  
  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.security.core.userdetails.UserDetails;  
import org.springframework.security.core.userdetails.UserDetailsService;  
import org.springframework.security.core.userdetails.UsernameNotFoundException;  
import org.springframework.stereotype.Service;  
import com.example.demo.dao.UserDao;  
import com.example.demo.model.User;  
//스프링 시큐리티가 낚아채서 로그인 진행해줌 - 왜냐면 세션관리를 해야 하니까  
//SecurityConfig설정에서 loginProcessingUrl("/loginProcess") 이부분  
//loginProcess 요청이 오면 자동으로 UserDetailsService타입으로 IoC되어 있는  
//loadUserByUsername함수가 실행된다 -스프링 시큐리티 컨벤션  
//시큐리티 session <- Authentication <- UserDetails@Service  
public class PrincipalDetailService implements UserDetailsService {  
    Logger logger = LoggerFactory.getLogger(PrincipalDetailService.class);  
    @Autowired  
    private UserDao userDao;  
    @Override  
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {  
        logger.info(username);//파라미터로 사용자가 입력한 이름이 담김  
        User user = userDao.login(username);  
        if(user !=null){//DB에서 가져온 값이 있으면  
            return new PrincipalDetails(user);  
        }  
        return null;  
    }  
}
  1. SecurityConfig 설정:

    • SecurityConfig 클래스에서 구글 로그인 및 권한 설정을 추가합니다. SecurityConfig
  2. Controller에서 로그인 및 사용자 정보 처리:

    • 로그인 및 사용자 정보를 처리할 Controller를 작성합니다.
@EnableWebSecurity // 스프링 시큐리티 필터가 스프링 필터 체인에 등록됨  
@EnableMethodSecurity  
@Configuration  
@RequiredArgsConstructor  
public class SecurityConfig {  
        @Bean  
        public static BCryptPasswordEncoder bCryptPasswordEncoder(){  
                return new BCryptPasswordEncoder();  
        }  
        @Autowired  
        private PrincipalOauth2UserService principalOauth2UserService;  
        @Bean  
        RoleHierarchy roleHierarchy(){  
                RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();  
                //큰 권한 순서로 '>'를 사용하여 입력해준다. 띄어쓰기도 중요  
                roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_TEACHER > ROLE_USER");  
                return roleHierarchy;  
        }          
        @Bean  
        public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {  
                http  
					.csrf(AbstractHttpConfigurer::disable)  
					.authorizeHttpRequests(requests -> requests  
						.dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll()  
						.requestMatchers("/user/**").authenticated()// 인증만되면 들어가는 주소  
						.requestMatchers("/teacher/**").hasRole("TEACHER")  
						.requestMatchers("/admin/**").hasRole("ADMIN")  
						.requestMatchers("/login", "/join", "/joinForm").permitAll()//권한없이 접근가능함  
						.anyRequest().authenticated())  
		.formLogin(login -> login  
						.loginPage("/login")  
						.loginProcessingUrl("/loginProcess")//로그인 버튼을 요청했을 때  
						.failureUrl("/login-error")//비번이 틀렸을 때  
						.defaultSuccessUrl("/", true)  
						.permitAll())  
						.oauth2Login(oauth -> oauth  
						.loginPage("/login") //구글로그인 완료된 후 후처리가 필요함  
						.userInfoEndpoint(end -> end   
						.userService(principalOauth2UserService))  
					)  
						.logout(logout -> logout  
						//현재 페이지에서 로그아웃 눌렀을 때 로그인 페이지가 아니라 메인페이지로 이동하기  
						.logoutSuccessUrl("/")  
						.permitAll())  
						.exceptionHandling(exception -> exception.accessDeniedPage("/access-denied"));  
	                return http.build();  
		        }  
        @Bean  
        public WebSecurityCustomizer webSecurityCustomizer() {  
                return (web) -> web.ignoring()  
			  .requestMatchers(PathRequest.toStaticResources().atCommonLocations());  
        }        
	}```

 
> 위의 코드에서는 구글 로그인을 쉽게 구현하기 위한 주요 단계를 설명하고 있습니다
1. **구글 클라이언트 설정**: 구글 로그인을 사용하기 위해 클라이언트 설정을 하세요. 클라이언트 ID와 시크릿 키 등이 필요합니다.
2. **User 클래스 (모델 클래스)**: 사용자 정보를 담을 클래스를 만들어주세요. 사용자의 기본 정보와 권한을 저장합니다.
3. **UserDetailsService 및 OAuth2UserService의 구현**: `UserDetailsService`는 사용자 정보를 가져오는 역할, `OAuth2UserService`는 OAuth 2.0 프로토콜을 통해 사용자 정보를 가져오는 역할을 합니다.
4. **SecurityConfig 설정**: Spring Security 설정 클래스에서 구글 로그인에 관한 설정을 하세요. 여기에는 로그인 및 콜백 URL, 사용자 정보를 가져오는 서비스 등이 포함됩니다.
    

> 이렇게 설정된 구성은 사용자가 구글 로그인을 시도할 때, Spring Security가 클라이언트 설정과 서비스를 통해 구글 서버와 통신하고, 사용자 정보를 가져와서 인증 및 권한을 부여합니다. 이렇게 간단한 설정으로 안전하고 효율적인 구글 로그인을 구현할 수 있습니다.

  

0개의 댓글