내가 보려고 정리하는/Spring

시큐리티 2 - db연결, 비밀번호 단방향 암호화(0503)

보동이용용 2023. 5. 3. 22:42
반응형
1. Filter, XML, In memory 기반의 인증구조
2. Role 기반의 선택적 랜더링 : ex) 관리자에게는 관리자에 맞는 메뉴구조 랜더링, 커스텀 태그 사용
3. MVC 모든 레이어에서 인증 객체를 확보하는 방법
4. 데이터베이스 기반의 인증구조
5. 비밀번호 단방향 암호화

시큐리티가 가장 적극적으로 이용하고 있는 filter

 

DelegateFilterProxy : 1. 요청을 잡는다. 2. 나는 진짜가아니다. 3. 요청을 떠넘긴다.

진짜는? 상위컨테이너의 SpringSecurityFilterChain 결과를 결정하고 다시 프록시에게 넘긴다.

프록시는 결과를 받아서 로그인폼으로 넘기거나 메인으로 넘기거나

 

HA에 왔을때는 이미 인증이 완료됨 그래서 인증객체가 이미 생성됨. 그래서 MVC 어디서든 Authenticate인증객체를 사용할 수 있음 

 

4. 데이터베이스 기반의 인증구조

1. 구현하려면 필요한 순서 : 도메인 > 퍼시스턴스 > 맵퍼프록시

 

VO package > MemberVO 만들기

package kr.or.ddit.vo;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;

@Data
public class MemberVO {
	@EqualsAndHashCode.Include
	private String memId;
	@ToString.Exclude
	private String memPass;
	private String memName;
	
	private String memRole;
}

 

DAOpakage >만들기

@Mapper
public interface MemberDAO {
	public MemberVO selectMember(String memId);
}

이거는 그냥 dao

AuthenticationProvider(신원확인) > userDetailService(사용자정보 조회) : memberDAO역

@Mapper
public interface MemberDAO extends UserDetailsService{
	public MemberVO selectMember(String memId);
}

상속받는다.

loadUserByUsername만 가지고 있다. username은 우리의 memId.반환타입이 userDetaile

Authorities는 role에 의해 결정

@Mapper
public interface MemberDAO extends UserDetailsService{
	@Override
	default UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		MemberVO member= selectMember(username);
		
		return member;
	}
	public MemberVO selectMember(String memId);
}

반환값이 안맞아서 오류난다.

MemberVO의 랩퍼를 만든다. 랩퍼의 가장 큰 특징 : 기본생성자가 없고 맵핑할 어뎁티를 받을 수 있는 생성자를 만들어준다. 

public class MemberVOWrapper {
	private MemberVO realUser;

	public MemberVOWrapper(MemberVO real) {
		super();
		this.realUser = realUser;
	}
	
	public MemberVO getRealUser() {
		return realUser;
	}
	
}

셋터를 안받는다.? >> 한번 결정난 어뎁티는 변경되지 않고 변경하려면 어뎁터 자체를 바꿔야한다.

 

UserDetails를 구현한다. > 사용하지 않을 정책까지 받아야한다.;

클래스로 정의되어있는 사용할 수 있는 중간자가 있다.

상속받자.

User
: Models core user information retrieved by a UserDetailsService.
Developers may use this class directly, subclass it, or write their own UserDetails implementation from scratch.
public User(String username, String password, Collection<? extends GrantedAuthority> authorities) {
		this(username, password, true, true, true, true, authorities);
	}

기본값이 설정된 User (이름, 비밀번호, 롤만 받으면 된다. 나머진 기본값. )

누가 가지고 있지? realUser.

 

GrantedAuthority : Represents an authority granted to an Authentication object.

MemberVOWrapper가 현재 할당받은 정보.Authentication 

~~~

구현체에 종속되는 코드가 되지 않기 위해 인터페이스 밑에 만들어두었다. 인터페이스 

~~~

맵퍼만들기

시큐리티를 써도 똑같은 모양 데코레이션이다.

 

security-context 수정하자!

프로바이더가 제공하니까 프로바이더에 ref를 넣어주자.

 

payload를 확인하니 csrf? 처음보는 프로퍼티가 하나 있네? 뭐지?

요청위조방식을 방지하기 위해 확인하려는 것 : 양 측에서 공통된 토큰을 하나 주고받자.

그렇다면 토큰의 발행구조는?

id랑 pass인풋태그 아래 hidden tag가 하나 있다. name은 _csrf

이게 바로 그 토큰 . 이건 누가준거? security가!

옆에 개발자도구로 이 태그의 벨류값을 지우면? 요청이 이상하다. > 공격을 받았다고 판단 > 막아버린다.

이건 spring이가지고 있는 form태그로 입력을 한다면 자동으로 맨 밑에 태그가 생성된다.

 

이 로그인/로그아웃 페이지를 우리것으로 바꿀 때 이 _csrf를 잘 신경써야한다.

ㅇㄴ

<form name="searchForm" method="post">
	<input type="hidden" name="page" placeholder="page">
	<input type="hidden" name="searchType" placeholder="searchType">
	<input type="hidden" name="searchWord" placeholder="searchWord">
</form>

이렇게 메쏘드를 넣어주면? 403

_csrf가 없어서 

해결방법? >> spring custom tag     또는   security custom tag

<form name="searchForm" method="post">
	<security:csrfInput/>
	<input type="hidden" name="page" placeholder="page">
	<input type="hidden" name="searchType" placeholder="searchType">
	<input type="hidden" name="searchWord" placeholder="searchWord">
</form>

<security:csrfInput/> 추가

post요청, _csrf 완성됨.

현직에 나가서 post요청만 하라고 가이드라인이 온다면 이 토큰을 사용할 수 있다.

 


5. 비밀번호 단방향 암호화

AuthenticationProvider가 신원확인을 잘 할 수 있도록 passwordEncoder를 사용했으나 아무것도 하지 않는아이이다.

트랜잭션관리를 위해 aop-context를 사용했는데 test case는 적용되지 않는다. 어노테이션으로 해결하자.

<tx:annotation-driven/> aop-context에 추가.

AbstractModelLayerTest 에 @Transactional 추가 > 이걸 상속받는애는 트랜잭션이 관리된다.

NoOpPasswordEncoder우리가 사용하는 것. 어떤 암호화작업도 수행하지 않는다.

ngrok 오고가는 모든 데이터 시큐리티 해준다.

encoder. 하면 decoder가 없다. 단방향 암호화. 암호화는 되는데 디코딩은 안되서 새로운 비번으로 바꿔야하는 상

public class PasswordEncoderTest extends AbstractModelLayerTest{
	@Inject
	private PasswordEncoder encoder;
	
	@Test
	public void encodeTest() {
		String plain = "7777";
		String encoded = encoder.encode(plain);
	}
}

 

// encrypt(암호화)/decrypt vs encode(부호화)/decode
// 공통점 : 데이터를 변환한다. 차이점 : 변환의 목적이 다름
// encrypt : 적절한 권한이 없는 사람이 해당 대상을 못읽게 하려는 것.
// 권한이 없는 소유자 : key를 가지지 않은 소유자
// encode : 매체가 이해할 수 있는 방식으로 데이터를 변환해주는 것. << 언제든지 바꾼방식을 안다면 다시 원래대로 바꾸기 가능
// encrypt는 모든 단위가 바이트 배열이다.
// encrypt -> encode모두 이루어져서 그냥 encode로 부르는것, 암호화 후 부호화

입력받은 평문을 한번 암호화하고 그걸 내가 암호화한거랑 비교한다.>>안된다....시간데이터가 암호화에 사용된다.

String saved = "{bcrypt}$2a$10$wRLH2qz0Pxa.OcCS.FqJmOJSxBHMfxWL0W12Z1GcVa3.DnUlJpAri";
log.info("인증 성공 여부 : {}", encoder.matches(plain, saved));

사용했던 알고리즘 그대로 사용하여 matches를 호출한다.

 

db에도 평문이 저장되어서는 안된다.

NoOpPasswordEncoder 대신에 passwordEncoderFactories를 넣자.

팩토리에서 만들어진 패스워드 인코더가 등록되게 하자.

<bean id="passwordEncoder" class="org.springframework.security.crypto.factory.PasswordEncoderFactories" 
    factory-method="createDelegatingPasswordEncoder"
/>

1.글작성/수정/삭제 >> BoardServiceImp에서 하는 작업들

 

private void boardAuthenticated(BoardVO inputBoard, String savedPass) {
    if( !inputBoard.equals(savedPass) ) {
        throw new BoardInvalidPasswordException(inputBoard.getBoNo());
    }
}

이렇게 평문으로 비교해서는 안된다.

@Inject
private PasswordEncoder encoder;

인젝션 추가하고 

private void boardAuthenticated(BoardVO inputBoard, String savedPass) {
    if( !encoder.matches(inputBoard.getBoPass(), savedPass) ) {
        throw new BoardInvalidPasswordException(inputBoard.getBoNo());
    }
}

matches를 사용한다.

private void encryptBoard(BoardVO board) {
    //입력받은 평문 비번을 암호화
    String encoded = encoder.encode(board.getBoPass());
    board.setBoPass(encoded);
}

작성한글이 insert될 때 암호화하여 insert하기 

 


요거 더 중점으로 보기!

2. Role 기반의 선택적 랜더링 : ex) 관리자에게는 관리자에 맞는 메뉴구조 랜더링, 커스텀 태그 사용

3. MVC 모든 레이어에서 인증 객체를 확보하는 방법


 

6. 설정 커스터마이징

login, logout만 해보자.

<security:form-login default-target-url="/" always-use-default-target="true"/> 
<security:logout/> 얘네를 고쳐야

<security:form-login 
    default-target-url="/" always-use-default-target="true"
    login-page="/login"
    username-parameter="memId"
    password-parameter="memPass"
/>

▶servlet-context.xml

<mvc:view-controller path="/login" view-name="login"/>

컨트롤러에 리퀘스트매핑하나한거. 

▶definition 

<definition name="login" extends="parent">
   <put-attribute name="body" value="/WEB-INF/jsp/login.jsp" />
  </definition>

반응형