[신세계I&C KDT][Spring Boot] #42 Spring Security (0613, 0614)

박현아·2024년 7월 1일
0

신세계아이앤씨 KDT

목록 보기
46/56

Spring Security

1. 제공 파일

1) pom.xml 의존성 설정

<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
	       <groupId>org.springframework.boot</groupId>
	       <artifactId>spring-boot-devtools</artifactId>
</dependency> 
	    
<!-- JSP 사용 -->
<dependency>
		<groupId>org.apache.tomcat.embed</groupId>
		<artifactId>tomcat-embed-jasper</artifactId>
		<scope>provided</scope>
</dependency> 
<dependency>
		<groupId>javax.servlet</groupId>
		<artifactId>jstl</artifactId>
</dependency>
<dependency>
		<groupId>org.webjars</groupId>
		<artifactId>bootstrap</artifactId>
		<version>5.1.3</version>
</dependency>
<dependency>
		<groupId>org.webjars</groupId>
		<artifactId>jquery</artifactId>
		<version>3.6.0</version>
</dependency>

2) application.properties

# tomcat의 기본 port 변경
server.port=8090
server.servlet.context-path=/app

# JSP 설정
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

3) MemberDTO.java

 public class Member {

	@NotBlank(message = "적어도 한 글자 이상")
	String userid;
	
	String passwd;
	
	@NotBlank(message = "적어도 한 글자 이상")
	String username;

2. Spring Security

1) 개요

  • 사용자의 인증 (Authentication)과 권한 (Authorization)을 활용하는 기술로 Spring의 서브 프로젝트이다
  • 인증 : ID/PW 이용해서 체크하는 작업
  • 권한 : 인증 후 접근 가능한 자원을 구분하는 작업
    ROLE 이용해서 구분한다

2) 실습 1

: security 의존성 설정, 계정과 비밀번호 설정

(1) 의존성 설정

org.springframework.boot spring-boot-starter-security #### (2) 설정 이후 바뀐 것 - 자동으로 로그인/로그아웃 관리 로그인은 /login 로그아웃은 /logout - 콘솔에 비밀번호를 다음과 같이 알려준다 Using generated security password: eff519a6-b10a-4575-87ce-f26a1ee32201 (비밀번호는 재실행시마다 바뀐다) 계정명 : user /home으로 요청하면 로그인 화면이 나오고 인증하면 /home으로 이동한다 - 인증 결과는 세션에 저장이 된다 ⭐ #### (3) 계정과 비밀번호를 임의로 지정할 수 있다 * application.properties \# 계정 및 비번 임의 지정 (재실행해도 고정됨) spring.security.user.name=parcc spring.security.user.password=1234

3) 실습 2

: 회원가입 & 비밀번호 암호화

(1) member 테이블 작성

-- use testdb;
-- show full columns from todo;

drop table if exists member;

create table member
( userid    varchar(255) not null COMMENT '아이디',
  passwd  varchar(255) not null COMMENT '비밀번호',
  username varchar(255) not null COMMENT 'TODO 작성자',
  primary key(userid)
);

(2) 의존성 설정 pom.xml

<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
		 <groupId>org.mybatis.spring.boot</groupId>
		 <artifactId>mybatis-spring-boot-starter</artifactId>
		 <version>2.1.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
		 <groupId>mysql</groupId>
		 <artifactId>mysql-connector-java</artifactId>
		 <version>8.0.33</version>
</dependency>

(3) application.properties DB 연동 설정

# DB 연동 위한 4가지 정보 설정
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/testdb
spring.datasource.username=root
spring.datasource.password=1234

# 별칭
mybatis.type-aliases-package=com.exam.dto

# mapper 등록 (하나로 퉁치기)
mybatis.mapper-locations=com/exam/mapper/*Mapper.xml

(3) 비밀번호 암호화 구현

// 비밀번호 암호화 (필수 !!!!!!!!!!)
String encptPw = new BCryptPasswordEncoder().encode(member.getPasswd());
member.setPasswd(encptPw);

4) 실습 3

: 기본적으로 동작되는 security의 FilterChain을 커스터마이징이 가능하다
: 이전에는 인증이 필요없는 요청도 (/home, /signup) 인증하도록 강제함
예> 불필요한 인증 제거
로그인 화면을 사용자가 만든 화면으로 변경
인증 실패시 / 인증 성공시 사용자가 특정 위치로 리다이렉트 가능
로그아웃 커스터마이징
: 커스터마이징 시작했으면 인증 제거 ~ 로그아웃 끝까지 재설정 작업이 필요함

@Configuration
public class SecurityFilterChainConfig {

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
		
		// 커스터마이징 처리
		
		// 1. 불필요한 인증 제거
		http.authorizeRequests()
		    .antMatchers("/login","/home","/signup","/webjars/**","/images/**").permitAll()
		    .anyRequest()
		    .authenticated();
		
		// 2. 로그인 관련 작업
		http.formLogin()     // 사용자가 만든 로그인화면으로 인증처리 하겠음
		    .loginPage("/login") // 로그인 페이지로 갈수 있는 요청맵핑값 <a href="login">로그인
		    .loginProcessingUrl("/auth") // <form  action="auth"  method="post"
		    .usernameParameter("userid")    // <input name="userid">
		    .passwordParameter("passwd")       // <input name="passwd">
		    .failureForwardUrl("/login_fail")        // 로그인 실패시 리다이렉트되는 요청맵핑값
		    .defaultSuccessUrl("/login_success", true); // 로그인 성공시 리다이렉트되는 요청맵핑값
		    
	     // 3. csrf 비활성화
		http.csrf().disable();

		 //4. 로그아웃 관련 작업
		 http.logout()
		     .logoutUrl("/logout")   // security가 자동으로 로그아웃 처리해주는 요청맵핑값
		     .logoutSuccessUrl("/home");  // logout 성공시 리다이렉트 되는 요청맵핑값
		     
		return http.build();
	}
}
  • CSRF (Cross Site Request Forgery : 요청 사이트간 위조)
    - POST와PUT할 때 신경써야 된다.
    - security가 자동으로 CSRF 방지하도록 자동으로 활성화 되어 있음
    - 정상적으로 로그인 (쿠키에 정보가 저장됨)하고 로그아웃하지 않은 상태에서 악성사이트에 접속하면 정상적인 작업의 쿠키정보를 악용할 수 있다

5) 실습 4

  • loginForm에서 입력한 userid와 passwd를 DB와 비교하는 작업
    인증이 성공하면 세션에 Member 저장
    인증이 실패하면 에러를 명시적으로 발생
@Component
public class AuthProvider implements AuthenticationProvider {

	@Autowired
	MemberService memberService;
	
	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {

		String userid = (String)authentication.getPrincipal(); // name="userid" 값
		String passwd = (String)authentication.getCredentials(); // name="passwd" 값
		
		Member mem = memberService.findById(userid);
//		String encrptPw = mem.getPasswd();  // NullPointerExcepion 발생되기 때문에 사용안됨.
		
		//Authentication 하위클래스
		//로그인 성공시
		UsernamePasswordAuthenticationToken token=null;
		if(mem!=null && new BCryptPasswordEncoder().matches(passwd, mem.getPasswd())) {
			
			List<GrantedAuthority> list = new ArrayList<>();
			//ROLE 설정시 사용됨
			list.add(new SimpleGrantedAuthority("USER")); // ADMIN
			
			//암호화된 비번대신에 raw 비번으로 설정
			mem.setPasswd(passwd);
			token = new UsernamePasswordAuthenticationToken(mem, null, list);
//			token = new UsernamePasswordAuthenticationToken(mem, null);
//			new UsernamePasswordAuthenticationToken(Principal, Credentials);
//			new UsernamePasswordAuthenticationToken(Principal, Credentials, List<GrantedAuthority>);
			
			return token;
		}
		//로그인 실패시
		throw new BadCredentialsException("비밀번호가 일치하지 않습니다. 다시 확인하세요.");
		
	}

	@Override
	public boolean supports(Class<?> authentication) {
		return true;
	}

}

6) 실습 5

  • 로그아웃 화면 처리

(1) security 관련 taglib 필요

 <dependency>
	    <groupId>org.springframework.security</groupId>
	    <artifactId>spring-security-taglibs</artifactId>
</dependency>

(2) taglib 설정

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>

(3) header.jsp 수정

<ul class="navbar-nav">
	<!--  인증이 안된 사용자 --> 
	<sec:authorize access="isAnonymous()">
		<li class="nav-item">
			<a class="nav-link" href="login">login</a>
		</li>
	</sec:authorize>
	<sec:authorize access="isAnonymous()">
		<li class="nav-item">
			<a class="nav-link" href="signup">signup</a>
		</li>
	</sec:authorize>
                            
	<!--  인증이 된 사용자 --> 
	<sec:authorize access="isAuthenticated()">
		<li class="nav-item">
			<a class="nav-link" href="logout">logout</a>
		</li>
	</sec:authorize>
	<sec:authorize access="isAuthenticated()">
		<li class="nav-item">
			<a class="nav-link" href="mypage">mypage</a>
		</li>
	</sec:authorize>
</ul>

(4) mypage에서 Authentication 객체 참조 방법

=> SecurityContextHolder.getContext().getAuthentication() 사용

@GetMapping(value={"/mypage"})
public String mypage() {
		
	// AuthProvider에 저장된 Authentication 얻자
		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
		Member xxx = (Member)auth.getPrincipal();
		
		return "redirect:home";
}

0개의 댓글