Day 99

ChangWoo·2023년 1월 25일
0

중앙 HTA

목록 보기
43/51
post-thumbnail

3Tier Model

객체의 초기화 방법

1. 생성자 메소드

public class Sample {
   private String url;
   private String username;
   private String password;
   
   public Sample(String url, String username, String password) {
      this.url = url;
      this.username = username;
      this.password = password;
   }
}
// 객체의 생성과 초기화
Sample sample = new Sample("jdbc:oracle:thin:@localhost:1521:xe", "hong", "abcd1234");
  • 생성자 메소드를 이용하는 객체의 초기화

    - 객체를 초기화하는 방법이 다양한 경우, 해당 경우의 수만큼 생성자 메소드를 중복정의해야한다.
    
       초기화하는 과정에서 전달되는 값을 서로 다르지만, 타입이 동일한 경우 생성자 메소드 중복정의로 해결할 수 없다.

    예시 )

    public Sample(String url, String username) { ... }

    public Sample(String username, String password) { ... }

2. setter 메소드

 * 생성자 메소드를 이용하는 객체의 초기화

       - 객체를 초기화하는 방법이 다양한 경우, 해당 경우의 수만큼 생성자 메소드를 중복정의해야한다.

          초기화하는 과정에서 전달되는 값을 서로 다르지만, 타입이 동일한 경우 생성자 메소드 중복정의로 해결할 수 없다.

     예시 )

      public Sample(String url, String username) { ... }

      public Sample(String username, String password) { ... } 
  • Setter 메소드를 이용하는 객체의 초기화

    • Setter 메소드가 public 메소드이기 때문에 객체의 생성 직후 Setter 메소드로 초기화가 완료된 이후에도

      Setter 메소드를 실행해서 객체의 값을 변경할 위험성이 존재한다.

3. 빌더패턴

public class Sample {
   private String url;
   private String username;
   private String password;

   public static class SampleBuilder {
     private String url;
     private String username;
     private String password;
  
     public SampleBuilder url(String url) {
         this.url = url;
         return this;
     }
     public SampleBuilder username(String usename) {
         this.username = username;
         return this;
     }
     public SampleBuilder password(String password) {
         this.password = password;
         return this;
     }
     public Sample build() {
        Sample sample = new Sample();
        sample.url = url;
        sample.username = username;
        sample.password = password;

        return sample;
     }
   }
} 

// 객체의 생성과 초기화
SampleBuilder builder = new Sample.SampleBuilder();
Sample sample = builder.url("jdbc:oracle:thin:@localhost:1521:xe");
                       .username("hong");
                       .password("abcd1234");
                       .build();

Spring Security 연동

1. spring boot 프로젝트 생성

Name : springboot-security
type : Maven
packaging : War
Java cersion : 11
Language : Java
Group : com.example
Artifact : springboot-security
Version : 0.0.1-SNAPSHOT
Description : spring-boot and spring-security
Package : com.example

2. 의존성 추가

Spring Boot Version : 2.7.8
Spring web
Spring Security
Lombok
Oracle Driver
Mybatis Framework
Validation
Spring Boot DevTools

3. pom.xml 파일 편집

spring boot version 변경 : 2.6.7

<!-- 
      jsp를 지원하는 내장형 톰캣 라이브러리 의존성 추가
    -->
   <dependency>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-jasper</artifactId>
   </dependency>
   <!-- 
      jstl 태그 라이브러리 의존성 추가
    -->
   <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
   </dependency>

주석처리
		<!--
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>
		-->

4. 사용자/사용자권한 테이블 생성

create table spring_users (
     user_id           varchar2(100) primary key,
     user_password     char(64) not null,
     user_name         varchar2(100) not null,
     user_email        varchar2(255) not null unique,
     user_tel          varchar2(20) not null,
     user_photo        varchar2(100) default 'default.png',
     user_deleted      char(1) default 'N',
     user_created_date default sysdate,
     user_updated_date default sysdate
);

create table spring_user_roles (
     user_id          varchar2(100) not null references spring_users (user_id),
     user_role_name   varchar2(20) not null,
     primary key (user_id, user_role_name)
);

5. User.java, UserRole.java, UserMapper.java, UserRoleMapper.java, users.xml, user-role.xml 파일을 정의한다.

VO 클래스

          User.java, UserRole.java

 mybatis mapper 인터페이스

          UserMapper.java, UserRoleMapper.java

 mybatis mapper 파일

          users.xml, user-role.xml

 * 사용자정보 추가, 사용자정보 조회관련 기능 정의.

6. UserDetails 인터페이스를 구현한 사용자정의 UserDetails 클래스를 정의한다.

// 아이디, 비밀번호, 이름을 표현하는 클래스
public class LoginUser {
   String id;
   String encryptPassword;
   String name;
   public LoginUser(String id, String encryptPassword, String name) {
       this.id = id;
       this.encryptPassword = encryptPassword;
       this.password = password;
   }
   // Getter 메소드
}

// UserDetails 인터페이스를 구현한 클래스.
// username(사용자 식별정보), password(암호화된 비밀번호), authorities(보유권한 정보)와 기타 정보를 제공한다.
public class CustomUserDetails extends LoginUser implements UserDetails {
   private Collection<? extends GrantedAuthority> authorities;

   public CustomUserDetails(String id, String encryptPassword, String name, Collection<? extends GrantedAuthority> authorities) {
       super(id, encryptPassword, name);
       this.authorities = authorities;
   }
	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		return authorities;
	}
	@Override
	public String getPassword() {
		return getEncryptPassword();
	}
	@Override
	public String getUsername() {
		return getId();
	}
    그 외 UserDetails 인터페이스의 추상메소드들은 전부 true값을 반환하도록 재정의한다.
}

7. UserDetailsService 인터페이스를 구현한 사용자정의 UserDetailsService 클래스를 정의한다.

@Service
public class CustomUserDetailsService implements UserDetailsService {

    private static final String DELETED_USER_STATUS = "Y";

	// 사용자정보와 사용자권한 정보를 조회하기 위해서 UserMapper와 UserRoleMapper를 주입받는다.
	@Autowired
	private UserMapper userMapper;
	@Autowired
	private UserRoleMapper userRoleMapper;
	
    // 전달받은 사용자식별정보로 사용자정보를 조회해서 사용자정의 UserDetails 객체를 반환한다.
	@Override
	public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException {
		// 사용자아이디는 인증작업을 수행하는 AuthenticationProvider로부터 전달받은 것이다.
		
		// 사용자아이디로 사용자 정보를 조회한다.
		User user = userMapper.getUserById(userId);
		// 사용자정보가 존재하지 않으면 예외를 던진다.
		if (user == null) {
			throw new UsernameNotFoundException("사용자 정보가 존재하지 않습니다.");
		}
		if ("Y".equals(user.getDeleted())) {
			throw new UsernameNotFoundException("탈퇴한 사용자입니다.");
		}
		// 사용자의 권한정보를 조회한다.
		List<UserRole> userRoles = userRoleMapper.getUserRolesByUserId(userId);
		// 조회된 권한정보로 GrantedAuthority객체를 생성한다.
		Collection<? extends GrantedAuthority> authorities = this.getAuthorities(userRoles);
		
		return new CustomUserDetails(
				user.getId(),				// 사용자 아이디 
				user.getEncryptPassword(), 	// 암호화된 사용자 비밀번호
				user.getName(), 			// 사용자이름
				authorities);				// 사용자가 보유한 권한정보
	}
	
	// 사용자 권한정보 목록을 전달받아서 GrantedAuthority객체의 집합으로 반환한다.
	private Collection<? extends GrantedAuthority> getAuthorities(List<UserRole> userRoles) {
		List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
		
		for (UserRole userRole : userRoles) {
			SimpleGrantedAuthority authority = new SimpleGrantedAuthority(userRole.getRoleName());
			authorities.add(authority);
		}
		
		return authorities;
	}
}

8. spring boot 프로젝트의 Bootstrap 클래스에 PasswordEncoder 빈을 추가하기

@SpringBootApplication
public class SpringbootSecurityApplication {

	public static void main(String[] args) {
        // 부트스트래핑 클래스를 로드해서 스프링 부트 어플리케이션을 실행시킨다.
		SpringApplication.run(SpringbootSecurityApplication.class, args);
	}

	// 비밀번호를 암호화하는 비밀번호 인코드 객체를 스프링 컨테이너에 등록시킨다.
	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
}

9. WebSecurityConfigurerAdapter 클래스를 상속받아서, 사용자정의 spring-security 설정 클래스를 정의한다.

@EnableWebSecurity
public class CustomSecurityConfig extends WebSecurityConfigurerAdapter {
	
	@Autowired
	private CustomUserDetailsService userDetailsService; // 사용자식별정보로 사용자정보를 반환하는 기능을 가진 객체 
	@Autowired
	private PasswordEncoder passwordEncoder; // 비밀번호 암호화 기능을 제공하는 객체
	
	/*
	 * HTTP 요청에 대한 인증/인가 관련 설정정보를 정의한다. <br />
	 * 1. HTPP 요청에 대한 인가 정책(권한체크 정책)을 설정한다.
	 * 2. 인증 정책(로그인 정책)을 설정한다.
	 * 3. 로그아웃 정책을 설정한다.
	 * 
	 */
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			// CSRF 토큰 사용여부 설정
			.csrf()	 // CSRF(사이트 간 요청위조) 방지를 위한 csrf토큰 사용여부 설정, CsrfConfigurer 객체를 반환한다.
			.disable() // csrf 토큰 사용을 비활성화한다. (기본값은 csrf 토큰을 사용한다.)
			// 1. 인가정책 설정 시작
			.authorizeHttpRequests() 	// 모든 요청에 대해서 아래에 설정된 인가정책을 적용하도록 한다.
			.antMatchers("/", "/register", "/registered", "/login").permitAll()	// 제시된 요청은 접근을 허용한다.
			.antMatchers("/post/**").hasAnyRole("GUEST", "USER")
			.antMatchers("/user/**").hasRole("USER")
			.antMatchers("/admin/**").hasRole("ADMIN")
			.anyRequest().authenticated()		// 위에서 제시된 요청 외에 모든 요청도 반드시 인증이 필요하다.
			// 1. 인가정책 설정 종료
		.and()
			// 2. 인증정책 설정 시작
			.formLogin()
			.loginPage("/login")
			.loginProcessingUrl("/login")
			.usernameParameter("id")
			.passwordParameter("password")
			.defaultSuccessUrl("/")
			.failureUrl("/login?error=fail")
			// 2. 인증정책 설정 종료
		.and()
			// 3. 로그아웃정책 설정 시작
			.logout()
			.logoutUrl("/logout")
			.logoutSuccessUrl("/")
			// 3. 로그아웃정책 설정 종료
		.and()
			// 4. 예외처리정책 설정 시작
			.exceptionHandling()
			.accessDeniedPage("/access-denied");
			// 4. 예외처리정책 설정 종료
	}
	
	@Override
	public void configure(WebSecurity web) throws Exception {
		web.ignoring().antMatchers("/resources/**", "/favicon.ico");
	}
	
	// 사용자정의 UserDetailsService 객체와 이 어플리케이션에서 사용하는 비밀번호 인코더를 AuthenticationManager에 등록시킨다.
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
	}
	
}

회원가입

1. 회원가입 폼 화면을 제공하는 요청핸들러 메소드를 정의한다.

@GetMapping("/register")  // 위에서 /register는 permitAll 설정함.
public String registerform() {
  return "register-form";
}

2. 회원가입 처리하기

@PostMapping("/register")
public String register(UserRegisterForm userRegisterForm) {
	userService.registerUser(userRegisterForm);
	return "redirect:registered";
}

@Service
@Transactional
public class UserService {
	
	@Autowired
	private UserMapper userMapper;
	@Autowired
	private UserRoleMapper userRoleMapper;
	@Autowired
	private PasswordEncoder passwordEncoder;
	public void registerUser(UserRegisterForm userRegisterForm) {
		User savedUser = userMapper.getUserById(userRegisterForm.getId());
        // 아이디, 이메일 중복여부 체크
		if (savedUser != null) {
			throw new ApplicationException("["+userRegisterForm.getId()+"] 사용할 수 없는 아이디입니다.");
		}
		savedUser = userMapper.getUserByEmail(userRegisterForm.getEmail());
		if (savedUser != null) {
			throw new ApplicationException("["+userRegisterForm.getEmail()+"] 사용할 수 없는 이메일입니다.");
		}
		// User객체를 생성하고, user객체에 사용자정보를 복사한다.
		User user = new User();
		BeanUtils.copyProperties(userRegisterForm, user);
        // 비밀번호를 암호화한다.
		user.setEncryptPassword(passwordEncoder.encode(userRegisterForm.getPassword()));
        // 사용자정보를 저장한다.
		userMapper.insertUser(user);
		
		// 사용자 보유 권한 정보를 저장한다.
		List<String> roleNames = userRegisterForm.getRoleName();
	      for (String roleName : roleNames) {
	         UserRole userRole = new UserRole(userRegisterForm.getId(), roleName);
	         userRoleMapper.insertUserRole(userRole);         
	      }
	}
  • user.setEncryptPassword(passwordEncoder.encode(userRegisterForm.getPassword())); 이부분만 다르고 원래 하던 방식과 동일하다.

로그인

1. 로그인 화면을 제공하는 요청핸들러 메소드를 정의한다.

@GetMapping("/login")
public String loginform() {
  return "login-form";
}
  • 로그인처리 구현할 내용이 없음.

로그아웃

// 로그아웃정책 설정 시작
.logout()    // 로그아웃을 요청하는 URI는 "/logout"이다.
.logoutUrl("/logout")
.logoutSuccessUrl("/")
// 로그아웃정책 설정 종료
  • 로그아웃처리 구현할 내용 없음.

컨트롤러에서 인증된 사용자 정보 이용하기

1. SecurityContext에 저장된 사용자 정보를 주입받을 수 있는 어노테이션 인터페이스를 작성한다.

@AuthenticationPrincipal  // SecurityContext에 저장된 Authentication 객체의 principal 정보를 가져오는 어노테이션이다.
@Retention(RUNTIME)
public @interface AuthenticatedUser {

}

2. 인증된 사용자 정보가 필요한 요청핸들러 메소드에서 인증된 사용자 정보 제공받기(@AuthenticatedUser)

예시
@GetMapping("/user/info")
public String info(@AuthenticatedUser LoginUser loginUser, Model model) {
    User user = userService.getUserDetail(loginUser.getId());
    model.addAttribute("user", user);

    return "user/detail";
}

JSP 에서 인증된 사용자 정보 이용하기

  • spring security 태그 라이브러리를 이용하면 인증여부, 인증된 사용자 정보, 인증된 사용자의 보유권한을 JSP에서 이용할 수 있다.
<!-- spring security tag library -->
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
  • spring security 주요태그
<sec:authorize />
   사용자 인증 여부 정보를 제공한다.
   사용자 보유 권한 정보를 체크한다.

<sec:authentication />
   인증된 사용자 정보를 제공한다.
profile
한 걸음 한 걸음 나아가는 개발자

0개의 댓글