ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring security - 2. UserDetailsService를 사용하여 로그인 구현
    Spring Security/security 2023. 11. 8. 19:01
    728x90

    UserDetail과 UserDetailService는 Spring security에 들어있는 사용자 정보와 사용자 정보를 가져오는 인터페이스이다.

     

    UserDetail을 구현한 기본 클래스인 User를 사용해도 되고, CustomUser를 만들어 UserDetail을 구현하여 사용해도 된다. (나는 Security에 있는 User 사용)

     

    session에 저장하는 방식이기 때문에 로그인 성공시 session에 사용자 정보를 저장하고, Client에게 JSESSIONID 라는 key로 sessionId를 담아 Set-cookie에 전달한다. JSESSIONID는 톰켓 서블릿 컨테이너에서 세션을 유지하기 위해 발급하는 key라고 한다.

     

    github에서 코드를 확인 할 수 있다 


    * 코드 구현

     

    security config 설정

    • Spring 5. 버전 때 까지는 위 방식이 아닌 WebSecurityConfigurerAdapter를 상속 받아 configure를 override하여 구현 했지만, Spring 5.7? 버전부터 Deprecate 됐다가 Spring 6버전부터 완전 없어지게 되었다.
    • csrf는 GET이 아닌 다른 메소드 (POST, PUT, DELETE) 로 호출시 _csrf 값을 만들어 보내줘야한다.
      • Restful 하도록 api를 만들었다면 stateless 하기때문에 csrf를 disable 해도 상관없지만, 지금 작성한 방식은 session에 사용자의 정보를 저장했기 때문에 stateful 하다고 한다. 그래서 csrf를 설정해줘야함
    • 6버전으로 오면서 antMatchers 같은 Method가 없어지고 requestMatchers 하나만 남았다.
    • 로그인을 담당하는 /api/v1/users/signIn path는 인증 정보가 없는 상태에서 접근해야하기 때문에  접근을 허가하는 permitAll() 설정을 하였다
    • 그 외는 anyRequest로 authenticated()를 설정하여 인증을 거치도록 설정하였다.
    • formLogin을 사용하여 html form에서 로그인 정보를 받도록 하였다.
    • loginPage를 통해 custom login화면으로 연결시킬 수 있고, 설정하지 않으면 기본 login 화면으로 설정된다.
    • usernameParameter와 passwordParameter는 client에서 전달받은 사용자 아이디와 비밀번호에 대한 필드이름을 설정하는건데 username 대신 userId로 자주 사용했어서 설정하였고, password는 기본이 password이기 때문에 굳이 설정하지 않아도 됐었다.
    • loginProcessingUrl은 이 path로 POST 요청을 보냈을 때 UserDetailsService interface의 loadUserByUserName이라는 메소드를 실행시키도록 한다. 처음에는 restController에 PostMapping으로 해당 컨트롤러를 구현해야 하는줄 알았는데, Security에서 알아서 loadUserByUserName으로 연결시켜 준다고 한다.
    • 로그인 성공시 / 로 이동하도록 설정하였다.
    • UserDetailsService 에서 passwordEncoder를 사용하기 때문에 Bean으로 등록해줘야한다.

    View (webController)

    • 다른거없이 로그인화면 이동과 메인화면 이동 2가지만 구현하였고, 회원가입은 rest api 만 만들어 놓고 postman을 사용했다.

    Api controller

    • 회원가입을 위한 RestController로 signUpUserRequest에는 간단하게 userId와 password 2가지 값만 받도록 설정하였다.
    • bean으로 등록한 passwordEncoder를 가져와서 userEntity에 보내 비밀번호를 바로 암호화하여 사용하도록 하였다.

    entity

    • entity도 간단하게 public key 용 id, userId, password 3가지로만 구현했고 static method of를 만들어 UserEntity를 만들 때 비밀번호 암호화를 하도록 설정하였다.

    service

    • UserDetailsService를 구현한 서비스.
    • security에서 설정한 loginProsessingUrl 의 path로 POST요청이오면 해당 서비스의 loadUserByUsername 메소드로 연결되어 인증후 사용자 정보를 session에 저장한다.
    • 따로 영속성 유지를 할 필요가 없는 것 같아 Transactional 어노테이션은 붙이지 않았다.
    • loadUserByUsername에서의 User는 Spring security에서 UserDetails를 구현한 객체이고, Custom으로 만들어서 구현해도되나, 아직 필요하지 않아 사용했다.

    * 코드 실행

    회원가입 화면

    • 회원가입용 RestAPI, userId와 password만 보내면 회원가입 되는 것으로 작성함.

    데이터베이스

    • passwordEncoder 덕분에 비밀번호가 안전하게 저장됨.

    로그인 페이지
    로그인 후 메인 페이지
    로그인 API 호출에 대한 응답헤더

    • 별다른 설정없이 Set-Cookie에 JSESSIONID로 식별값이 넘어온다. HttpOnly 설정이 되어있는데, 이 설정을 하면 client의 javascript에서 접근 할 수 없다고 한다. (javascript를 이용한 XSS공격에 대한 방어)
    • HttpOnly외에 Secure 설정이 있는데, 이설정을 하면 Https 통신일때만 헤더에 값을 넣어 보낸다고 한다.

    session에 사용자 정보 등록 후 인증이 필요한 API 호출한 화면

    • 화면이 없지만 실제로 잘 이동했다.
    • 요청 헤더에 보면 Cookie로 아까 로그인에서 받았던 헤더 값을 그대로 넣어 보내는 것을 알 수 있다.

    이렇게 로그인 환경을 구성하면 Security는 requestMatchers에 permitAll()로 설정한 요청외에 모든 요청은 인증정보를 확인하게 되고, 인증정보가 없다면 설정한 로그인 화면으로 이동하게 된다.

     

    Security설정에서 loginProsessingUrl 을 사용하여 UserDetailsService interface를 호출 할 수 있고, 이 interface에는 사용자 인증에 대한 메소드가 있다.

     

    위 메소드를 구현하여 실제 사용자 정보가 저장된 DB와 연결하여 사용자를 인증 할 수 있고, 이 정보를 UserDetails interface를 구현한 객체에 저장하게 된다.

     

    해당 UserDetails 구현체는 session에 해당 사용자 정보를 저장하고, 그 식별 값을 Cookie에 담아 client로 전달한다.

     

    client는 해당 정보를 cookie에 가지고 있고, server에 요청시 해당 식별 값을 보내 server에서 해당 사용자인지 확인 후 API를 수행하게 된다.

    728x90
    반응형
    LIST

    댓글

Designed by Tistory.