(spring) Spring Security

4 분 소요

💼📝🔑⏰ 📙📓📘📒🎓

1 예제 프로젝트 : spring-security-a 
2 예제 프로젝트 : spring-security-b
3 예제 프로젝트 : spring-boot-demoweb2-starter

💼 Spring Security 란?

  • 자바 애플리케이션의 인증과 인가 기능을 지원하기 위한 프레임워크
  • 개별 애플리케이션의 요구사항을 반영하여 쉽게 확장 가능
  • 스프링 기반 애플리케이션 인증시스템의 사실상의 표준

📝주요 특징

  • 인증 및 인가 기능을 포괄적으로 그리고 확장 가능하게 지원
  • 세션 고정 공격, 클릭재킹, 사이트간 요청 위조(csrf) 등의 공격에 대응 가능
  • 서블릿 API 통합
  • 인증 정보에 기반한 화면 표시 제어를 위해 JSP 태그 라이브러리 지원
  • 스프링 웹 MVC와 선택적인 통합 가능

💼 인증과 인가

인증과 인가를 따로 구분할 필요는 없지만 스프링에서는 구현을 따로 해놓았기 때문에 구분을 해야한다

  • 인증 (Authentication)
    • 애플리케이션에 액세스하는 사용자를 특정하는 기능
    • 로그인 여부, 로그인 했다면 어떤 사용자인지 등 신원 확인 프로세스
    • 애플리케이션 자체 계정, LDAP, Active Directory 등의 방법 사용
  • 인가 (Authorization)
    • 신원 확인된 사용자에 대해 권한에 따라 가능한 동작을 제한하는 기능
    • 사용자별로 부여된 권한에 맞게 기능에 대한 접근 관리
    • 인증 기능은 인가 기능의 전제 조건

📝 pom.xml 추가

		<!-- spring security -->
		<dependency>
		    <groupId>org.springframework.security</groupId>
		    <artifactId>spring-security-web</artifactId>
		    <version>5.3.3.RELEASE</version>
		</dependency>
		<dependency>
		    <groupId>org.springframework.security</groupId>
		    <artifactId>spring-security-config</artifactId>
		    <version>5.3.3.RELEASE</version>
		</dependency>
		<dependency>
		    <groupId>org.springframework.security</groupId>
		    <artifactId>spring-security-taglibs</artifactId>
		    <version>5.3.3.RELEASE</version>
		</dependency>
		<!-- springBoot인 경우 -->
		<!-- spring security -->
		<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
		<dependency>
		    <groupId>org.springframework.boot</groupId>
		    <artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-taglibs -->
		<dependency>
		    <groupId>org.springframework.security</groupId>
		    <artifactId>spring-security-taglibs</artifactId>
		</dependency>

📝 web.xml 추가

	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
			/WEB-INF/spring/root-context.xml
			/WEB-INF/spring/security-context.xml
		</param-value>
	</context-param>

📝 security-context.xml 파일 추가

🔑 IN메모리 인증관리

실무에서 잘 사용안함.

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans 
	xmlns="http://www.springframework.org/schema/security"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
		http://www.springframework.org/schema/security 
		https://www.springframework.org/schema/security/spring-security.xsd
		http://www.springframework.org/schema/beans 
		https://www.springframework.org/schema/beans/spring-beans.xsd">
	
	<!-- access config 권한을 등록 -->
	<!-- 중복된 권한을 등록한 경우 밑에 있는 권한은 의미가 없음. 적용우선순위는 위에서 아래로 적용됨 -->
	<http>		
		<intercept-url pattern="/" access="permitAll()" /> <!-- permitAll() 은 스프링이 가지고 있는 EL 문법  -->
		<intercept-url pattern="/home" access="permitAll()" />
		<intercept-url pattern="/resources/**" access="permitAll()" /> 
		<intercept-url pattern="/admin" access="hasRole('ADMIN')" />
		<intercept-url pattern="/**" access="isAuthenticated()" /><!-- 인증된 사용자만 -->
		<form-login /> <!-- 내장 로그인 기능 -->
		<logout />		<!-- 내장 로그인 아웃 기능 -->
		<csrf disabled="true" />  
	</http>	
	
	<!-- provider -->
	<authentication-manager>
		<authentication-provider>
			<password-encoder hash="bcrypt" /> <!-- https://bcrypt-generator.com/ 에서 암호화 가능 --> 
			<user-service> <!-- IN메모리 인증관리 방법  -->
				<user name="user" 
					  password="$2y$12$L/Ty22sqcZiY336NvSa1XOVSAPFT3RWEidV2VxwB.BaFnruOkRzCG" 
					  authorities="ROLE_USER" />
				<user name="admin" 
					  password="$2y$12$L/Ty22sqcZiY336NvSa1XOVSAPFT3RWEidV2VxwB.BaFnruOkRzCG" 
					  authorities="ROLE_ADMIN" />
			</user-service>			
		</authentication-provider>	
	</authentication-manager>
		
</beans:beans>

🔑 데이터베이스를 사용한 인증관리

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
	xmlns="http://www.springframework.org/schema/security"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
		http://www.springframework.org/schema/security 
		https://www.springframework.org/schema/security/spring-security.xsd 
		http://www.springframework.org/schema/beans 
		https://www.springframework.org/schema/beans/spring-beans.xsd">
		
	<!-- access config -->
	<http>
		<intercept-url pattern="/" access="permitAll()" />
		<intercept-url pattern="/home" access="permitAll()" />
		<intercept-url pattern="/login" access="permitAll()" />
		<intercept-url pattern="/register" access="permitAll()" />
		<intercept-url pattern="/resources/**" access="permitAll()" />
		<intercept-url pattern="/admin" access="hasRole('ADMIN')" />
		<intercept-url pattern="/**" access="isAuthenticated()" />
		<form-login 
			login-page="/login" 
			username-parameter="memberId" password-parameter="passwd" 
			default-target-url="/home" /> <!-- 아예 처음부터 로그인을 클릭해서 들어온 경우 경로 설정해주는 것  -->
		<access-denied-handler error-page="/access-denied" />
		<logout 
			logout-url="/logout" 
			logout-success-url="/home" 
			invalidate-session="true"
			delete-cookies="JSESSIONID" />
		<csrf disabled="true" />
	</http>
	
	<!-- provider -->
	<authentication-manager>
		<authentication-provider>
			<password-encoder ref="bcryptEncoder" />	
			<jdbc-user-service data-source-ref="dataSource"
							   users-by-username-query="SELECT memberid, passwd, active
							   							FROM member WHERE memberid = ?" 
							   authorities-by-username-query="SELECT m.memberid, r.rolename
							   								  FROM member m
							   								  INNER JOIN member_role mr 
							   								  ON m.memberid = mr.memberid 
							   								  INNER JOIN t_role r 
							   								  ON r.roleno = mr.roleno
							   								  WHERE m.memberid = ? "/>
		</authentication-provider>
	</authentication-manager>
	
</beans:beans>

🔑 코드를 통해 데이터베이스를 사용한 인증관리

📝컨트롤러에서 security를 적용한 세션정보 받기

  • HttpServletRequest에서 받는 방법 : Authentication authentication = (Authentication)req.getUserPrincipal();;
  • 어노테이션으로 Controller에서 파라미터로 받기 : @AuthenticationPrincipal UsernamePasswordAuthenticationToken user
  • SecurityContextHolder를 사용해서 메서드로 가져오는 방법 : Authentication auth = SecurityContextHolder.getContext().getAuthentication();

📝 세션정보를 활용하기 위한 설정

위 방법을 적용하는 경우 세션정보를 활용할 수 없음. 따로 설정해야함

  • 위 데이터베이스를 사용한 인증관리 방법에서는 실질적인 정보는 memberId 밖에 없다는 것을 알 수 있다.
  • 이 문제를 해결하기 위한 방법은 UserDetails 인터페이스를 상속받아 사용자 정보 클래스 구현

🔑 사용자 정보 클래스의 구현


public class DemoWebUserDetails implements UserDetails {
	
	// Vo 객체는 Autowired 대상이 아니다 -> 계속 바뀌기 때문에
	private MemberEntity memberEntity;
	
	public DemoWebUserDetails() {
		
	}
	
	public DemoWebUserDetails(MemberEntity memberEntity) {
		this.memberEntity = memberEntity;
	}

	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		ArrayList<SimpleGrantedAuthority> authorities = new ArrayList<>();
		for (MemberRoleEntity role : memberEntity.getMemberRoles()) {
			authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
		}		
		return authorities;
	}

	@Override
	public String getPassword() {

		return memberEntity.getPasswd();
	}

	@Override
	public String getUsername() {
		// TODO Auto-generated method stub
		return memberEntity.getMemberId();
	}

	@Override
	public boolean isAccountNonExpired() {
		// TODO Auto-generated method stub
		return true;
	}

	@Override
	public boolean isAccountNonLocked() {
		// TODO Auto-generated method stub
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired() {
		// TODO Auto-generated method stub
		return true;
	}

	@Override
	public boolean isEnabled() {
		// TODO Auto-generated method stub
		return memberEntity.isActive();
	}

}

🔑 사용자 정보 관리 서비스 클래스 구현

public class DemoWebUserService implements UserDetailsService {
	
	@Autowired
	private AccountRepository accountRepository; //JPA를 사용했을 때 기준
	
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		
		
		MemberEntity member = 
			accountRepository.findById(username).orElse(null); // orElse(null) 쓰는이유는 널체크를 해서 보내라!
		
		if(member == null) {
			throw new UsernameNotFoundException(username);
		} else {
			return new DemoWebUserDetails(member);
		}
	}

}

🔑

💼 강사님 가라사대

  1. 회원의 정보를 등록
  2. 로그인에 대한 정보(접속) 기록하고 운영하는 방법에 대한 고찰
  3. 사용권한 확인
    • 페이지 별로 로그인이 되었나 확인 해야되는 노가다 작업…
    • 스프링시큐리티를 사용하는 대신 Filter or Intercepter를 사용하여 처리해도 된다.
  4. 특정페이지 정보를 가져가 로그인 후 이동시키는 작업이 필요 한 경우

📝 Spring Security 인증 비교 방식

  • 아이디를 먼저 보내고, 비밀번호를 꺼내와 비교하는 작업 (총 2번)

관리부담을 느끼지 않으려면 최대한 Security에서 제공하는 방법을 최대한 활용해서 적용시켜야한다. (버전이 바뀌면 전체 코드를 바꿔야할 수도 있기 때문에..) spring은 bcrypt을 내부적으로 사용한다 암호화 방식을 바꾸려면 새로 커스텀마이징 해야함.

댓글남기기