D+63-로그인,configuration.패스워드암호화,프론트제한 무시,service,controller,승인된 data 넣는 클래스, 승인기능 구현,jsp,CSRF해킹공격 방어,최종결과

Bku·2024년 3월 27일

학원 일기

목록 보기
61/67
post-thumbnail

로그인

Configuration 함수 추가

Configuration에 패스워드를 암호화하는 함수와 공통 jsp, img, css, js 등 : 인증 안 받는 것들은 무시하도록 설정하는 함수를 만들어줘야한다.

패스워드를 암호화 함수

 @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder(); // 암호화를 시켜주는 함수들 중 하나이다.
    }

아주 쉽다. PasswordEncoder타입의 함수로 BCryptPasswordEncoder()함수 리턴해주면된다. BCryptPasswordEncoder()는 여러 암호화 함수중 하나이다. 이것을 해주면 사용자가 입력한 암호가 데이터 베이스에 바록 입력이 되는 것이 아니라 그림과 같이 암호화가 되어 저장되어 보안력이 증가한다.

예전에는 생략이 가능했는데 이제는 패스워드를 암호화 하는 객체가 필수가 되었다.

jsp, img, css, js 등 : 인증 안 받는 것들은 무시하도록 설정하는 함수

이 기능들을 이용해야하지만 스프링 security는 기본적으로 이것들을 제한한다. 그래서 이것을 사용하기 위해 제한을 풀어줘야한다.

// todo 2-1) 공통 jsp, img, css, js 등 : 인증 안 받는 것들은 무시하도록 설정
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer(){ // 이걸 안하면 다 인증에 걸려서 나오지 않음
    //        (web) -> web.ignoring().requestMatchers("경로", "경로2" ...)
        return (web) -> web.ignoring().requestMatchers(
                "/resources/img/**",
                "/resources/css/**",
                "/resources/js/**",
                "/WEB-INF/views/**"
        );
    }

이 함수를 이용해서 제한을 풀어줄 수 있다. "(web) -> web.ignoring().requestMatchers()"함수를 이용하여 jsp, img, css, js 등이 존재하는 경로를 입력해주면 requestMatchers()안에 입력해주면 이 경로의 것들은 제한에서 자유로워져 사용이 가능해진다.

Service함수

@Service
public class MemberService {

    private final MemberRepository memberRepository;

    @Autowired
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    // todo : 로그인 관련 함수
    //      1)상세 조회
    public Optional<Member> findById(String email){
        Optional<Member> optionalMember = memberRepository.findById(email);
        return optionalMember;
    }

    // 기본키 테이터베이스에 존재하는지 확인하는 함수
    public boolean existsById(String email){
        boolean result = memberRepository.existsById(email);
        return result;
    }
    
}

서비스에는 두개의 함수만 만들어주면된다. 이건 스프링이 자동으로 컨트롤함수를 만들어서 사용할때 이용할 함수들이다. 하나는 findById이므로 많이 해본것이라 설명은 생략한다.

두번째 함수는 기본키가 데이터베이스에 존재하는지를 확인하는 함수이다. memberRepository의 existsById()함수를 사용하고 존재 여부에 따라 true, false를 반환한다.

Controller 함수

@Slf4j
@Controller
@RequestMapping("/auth")
public class MemberController {
    // 예전에는 생략이 가능했는데 이제는 필수가 됌 패스워드를 암호화 객체가 필수가 되었다.

    private final MemberService memberService;


    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }

    @Autowired
    PasswordEncoder passwordEncoder;

    // todo : 로그인 : 1) 로그인 페이지 열기 함수
    @GetMapping("/customLogin")
    public String login(){
        return "auth/customLogin.jsp";
    }

    // todo : 로그인 : 2) 로그인 버튼 클릭시 실행될 함수(이건 스프링이 해주니 안 만들어도 됌)
    //                => jsp : form action="/auth/login" = WebSecurityConfig.java의 filerChain() 함수안에 정의했다.
    //                => DB에 사용자가 있는지 확인하는 함수를 제작해야 스프링이 알아서 로그인을 처리할 때 이 함수를 실행에 DB를 인식하고 id/pw를 DB와 비교할 수 있게된다.
    //                     - 스프링은 DB에서 어떤 테이블에 id/pw가 있는지를 모르기 때문에 쿼리문을 작성해서 이 테이블이 무엇인지 인식시켜주는 것.
    //                => DB 확인해서 정상사용자인지 검증이 끝나면 => 검증객체에 넣어준다(검증을 통과했다는 표시).

}

컨트롤러에는 로그인 화면을 연결해주는 함수만 만들어주면된다. 로그인과 로그아웃은 스프링에서 알아서 만들어주기 때문이다.

일반 컨트롤러와의 차이점는 configuration 클래스에서 만들었던 passwordEncoder()함수를 @Autowired해야한다는 것이다. 이것은 함수로 di를 할때 생성자로 할 수없다(함수는 생성자가 없기때문). 이것을 이용해 스프링이 로그인시 암호화를 진행한다.

이제 남은건 스프링이 사용자를 확인할 수 있도록 해주는 것과, 승인이 된 친구들을 저장할 객체를 만들어야한다.

스프링 컨트롤러를 위한 설정

Security폴더의 dto폴더에 승인된 애들 넣어주는 클래스 만들기

id,pw가 일치하여 통과한 사용자들은 id/pw와 권한들을 객체에 모아서 보관한다. 이 객체에 있는 아이들만 자유롭게 권한이 주어진 곳을 접근하게 해주기 때문이다.

생성 위치

이 위치에 생성하면된다.

@Setter
@Getter
@ToString
public class MemberDto extends User { // 여기에 인증이 된 애들을 넣어주는 객체이다.
    // todo : 이 클래스는 명단 같은 클래스이다. 여기 명단에 있는 애들만 권한이 주어진다.
    //      1) 스프링에서 제공한 유저 클래스 : user, userDetails 둘 중하나만 상속해서 쓰면 되는데 user로 하는게 코딩이 길어지지 않는다.
    //      2) 검증된 유저 객체는 권한이 있음 : 권한 넣기

    // User에 있는 기능 말고 우리가 추가로 속성을 넣어보자. 우리는 email을 id로 사용하는데 user에는 email이 없어서 이걸 우리가 만들어보자.
    private String email;

    public MemberDto(String email, String password, Collection<? extends GrantedAuthority> authorities) {
        super(email, password, authorities);
        this.email = email;
    }
}
  1. 먼저 User객체를 상속받아야한다. 그래서 User의 속성까지 생성자로 넣어줘야한다.

  2. 이번 프로젝트에서 id는 email로 했으니 email이라고 username을 바꿔주자.

  3. id와 pw도 넣어줘야하지만, 사용자가 가지는 권한도 저장을 해야한다. 그래야 어디까지 접근할 수 있는지를 통행증을 주면서 확인이 가능하기 때문이다. 이 권한은 GrantedAuthority라는 객체에 배열의 형태로 저장된다.

생성자 만들면 이렇게 나온다. 근데 우리는 username울 id로 만드는 대신 email을 사용할 거라서 username을 지워주자

 public MemberDto(String password, Collection<? extends GrantedAuthority/*권한 클래스명*/> authorities, String email) {
        super(email, password, authorities);
        this.email = email;
    }

이렇게 생성자를 만들어주면 된다.

자 이제 이 객체에 승인된 아이들을 넣어보자. 넣기 전에 먼저 승인을 시켜야한다.

Security폴더에 services폴더에 DB에 사용자가 있는지 확인하는 클래스 만들기

로그인으로 들어온 id/pw가 DB에 존재하는 지를 확인하고 승인을 해중어얗나다. 이 클래스에서 그 기능을 구현할 것이다.

@Service
public class UserDetailsServiceImpl implements UserDetailsService { // 인터페이스라 구현을 해야함,DB에 사용자가 있는지를 확인하는 클래스

    private final MemberRepository memberRepository;

    @Autowired
    public UserDetailsServiceImpl(MemberRepository memberRepositoryl) {
        this.memberRepository = memberRepositoryl;
    }

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        //todo 1) 유저 DB 인증 : 상세 조회
        Member member // 얘는 optional 객체이지만 optional 함수를 사용하면 optional 타입으로 정의하지 않아도 된다.
                = memberRepository.findById(email)
                .orElseThrow(()->new UsernameNotFoundException("email 없음 : " + email)); // function(x){return x+1}; 이걸 function((x) -> x+1); 로만 표시할 수 있다.
        // orElseThrow() optional 에서 null 이 나오면 에러메세지를 나오게 하고, null이 아니면 객체에 값을 담는다.

        // todo 2) 검증 객체에 정보 넣기 -> 여기에 넣는 순간 검증 딱지가 붙어 권한이 생긴다.
        //        2-1) 권한을 생성해서 넣기 :  GrantedAuthority(스프링시큐리티 권한클래스)
        //        GrantedAuthority authority = new SimpleGrantedAuthority( 역할 ex) user, admin );
        GrantedAuthority authority = new SimpleGrantedAuthority(member.getCodeName());
        // todo  권한은 배열의 형태로 여러개를 가질 수 있다. 그래서 배열을 만들고 배열에 넣어야함
        //  2-2) 권한 배열(List, set )에 넣기, 그런데 스프링 시큐리티에서는 set을 이용

        Set<GrantedAuthority> authorities = new HashSet<>();
        authorities.add(authority); // set에 권한을 넣어주었다.

        return new MemberDto(member.getEmail(), member.getPassword(), authorities);
    }
}

이 함수는 UserDetailsService 라는 인터페이스를 구현하는 구현체이다.

  1. MemberRepository의 findById()함수를 사용해서 들어온 id가 있는지 없는지를 확인한다. 이때 orElseThrow()함수를 이용하여 null이나와도 에러가 나지 않게 해준다. orElseThrow()함수는 optional 에서 null 이 나오면 에러메세지를 나오게 하고, null이 아니면 객체에 값을 담는다.

  2. GrantedAuthority라는 검증 객체에 권한을 넣어야한다. 권한은 codeName을 넣는다. codeName은 user나 admin같은 것을 말한다. set의 형태로 넣고 넣는 즉시 검증딱지가 생기게 된다.

  3. 이 관문에서 통과가 되면 MemberDto객체에 id/pw와 권한들을 넣고 객체를 반환한다. 이 반환된 객체는 스프링이 로그인하는 데에 사용된다. (그래서 인터페이스 형태로 함수를 정의해서 구현하는것, 아니면 스프링이 인식을 못한다.)

여기까지 스프링 서버에서 로그인을 위해 할 일은 끝났다. 이제 새로 만들 로그인 페이지를 jsp로 만들어보자.

jsp 만들기

부트스트랩의 로그인 테마를 사용했다. 내가 변경한 부분만 가져왔다.
1. action에 configuration에서 설정한 로그인 버튼이 눌러지면 실행될 controller의 url을 적어주면된다.

  1. 로그인은 백엔드에서 보이지 않게 진행되어야하므로 post 방식으로 전송된다.

  2. 입렵 타입에 email을 넣어주면 email이 아닌 다른 형식은 입력이 되어도 전송이 안된다.

4.input의 name은 백엔드의 속성과 맞추어 주어야한다. 우리는 email을 사용했지만 spring security 는 username을 사용하기 때문에 username을 넣어주어야한다.

  1. 마지막으로 버튼의 typr을 submit으로 해주면 끝이다.

CSRF해킹공격 방어

localhost에서 해킹메일을 클릭하면 이상한 주소로 바껴서 작업이 이루어진다. 이때 쿠키 세션이 바뀐 주소로 넘어가면서 정보가 털릴수 있다.

해커 주소로 바껴도 spring은 인식을 못하게 되므로 이것을 방지하기위해 csrf토큰을 발행하여 이것을 가진애만 접근할 수 있게 해준다. 다른 사이트에서는 이것이 없으므로 쿠키가 다른 사이트인것을 알게된다.

토큰을 발행하는 방법을 알아보자

CSRF토큰 발행하기

<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />

이것을 jsp나 vue에 넣으면된다. post, put, delete방식 사용시 form 태그 안에 넣어주어야 인증이 된다.

잘 들어갔는지를 확인 할 수 있다.

최종결과

로그인 화면

configuration에서 설정한 로그인화면 url을 입력하여 로그인 화면으로 이동하는지 확인해보자.

로그인 url

결과 화면

로그인 성공 화면

로그인 성공을하면 configuration에서 설정한 화면으로 넘어가는지 확인해보자.

로그인 성공화면 url

결과화면

advanced화면 접근 가능 확인

advanced화면은 로그인을 해야만 접근을 할 수 있게 하였다.
advanced경로인 Gallery탭에 접근을 했다.

그럼 로그아웃을 하고도 접근이 가능한 지를 보자.

로그아웃 후 접근

로그아웃을 한 후에는 advanced경로에 접근이 어려운 것을 확인할 수 있다.




이렇게 로그인 기능을 구현했다. 스프링 시큐리티를 이용하면 스프링이 로그인을 구현해주지만 따로 권한 설정은 개발자가 해주어야한다. 그래도 jpa, security, 다형성으로 로그인을 쉽게 구현할 수 있었다고 생각한다.
profile
기억보단 기록

0개의 댓글