실제로 로그인을 진행하는 PrincipalDetailService 클래스(UserDetailsService 타입인) 만들어서

스프링이 로그인 요청을 가로채어 username,password 변수2개를 PrincipalDetailService에 던져주는데

이때 password 부분처리는 스프링이 인코딩해서 알아서 하고 username이 DB에 있는지만 확인해주면 된다

 

 

 

[PrincipalDetailService]

여기서 username이 DB에 있는지만 확인하기

 

1. loadUserByUsername 오버라이드해준다.(UserDetails 타입인 loadUserByUsername 를 통해서 로그인을 한다.)

@Service
public class PrincipalDetailService implements UserDetailsService{

   @Autowired
   private UserRepository userRepository;

   @Override
   public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
     User principal = userRepository.findByUsername(username)
          .orElseThrow(()->{    
             return new UsernameNotFoundException("해당 사용자를 찾을 수 없습니다. : " + username);
          });
      return new PrincipalDetail(principal);  //시큐리티의 세션이 유저 정보가 저장이 됨.
   }
}

 

* 람다식 표현

 User principal = userRepository.findByUsername(username)
          .orElseThrow(()->{
             return new UsernameNotFoundException("해당 사용자를 찾을 수 없습니다. : " + username);
          });
      return new PrincipalDetail(principal);

username이 없으면 .orElseThrow(()->{});

username이 있으면 new PrincipalDetail(principal) 리턴한다.

 

 

 

userRepository 에 findByUsername 함수가 없으니 직접 만들어준다.

 

3. [UserRepository] findByUsername 함수이름 만들기

 

  • 네이밍쿼리 방식

      findByUsername => SELECT * FROM user WHERE username=?;  (해당 쿼리 자동 실행)

public interface UserRepository extends JpaRepository<User, Integer>{
	//SELECT * FROM user WHERE username=?;
	Optional<User> findByUsername(String username);
}

 

 

4. return new PrincipalDetail(principal); 리턴할때 user가 null이니까 생성자가 있어야한다. 

 

UserDetails 타입인 User를 리턴 할 수 없고 UserDetails 타입인 PrincipalDetail 을 리턴한다

 public class PrincipalDetail implements UserDetails

 

[PrincipalDetail] 

user가 null이니까 생성자 생성하기

public class PrincipalDetail implements UserDetails{
  private User user; // 콤포지션( 객체를 들고 있는)

  //생성자
  public PrincipalDetail(User user) {
     this.user = user;
  }
...

 

리턴과 동시에 시큐리티의 세션에 유저 정보가 저장이 된다.

 

 

 

만약 loadUserByUsername 오버라이드를 안해준다면?

 

아래의 [PrincipalDetail] 에 user 생성자없고 

return new PrincipalDetail() 으로 리턴이 되는데  내가 회원가입한 정보의 아이디와 패스워드가 아닌 아이디 user , 비밀번호는 콘솔창에 있는걸로 로그인이 된다

 

어렵다..

 

 

 

 

해쉬로 암호화 된 비밀번호 비교하기

시큐리티가 대신 로그인 할때 password 를 가로채는데 password가 어떻게 암호화가 되어 회원가입이 되었는지 알아야

로그인 할때도 같은 해쉬로 암호화해서 DB에 있는 해쉬랑 비교할 수 있다. 

 

 

[SecurityConfig]

- configure(AuthenticationManagerBuilder auth) 로 오버라이드

@Autowired
private PrincipalDetailService principalDetailService;

@Bean  

public BCryptPasswordEncoder encodePWD() // 시큐리티가 들고있는 함수
    return new BCryptPasswordEncoder();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  auth.userDetailsService(principalDetailService).passwordEncoder(encodePWD());
}

principalDetailService 을 통해 로그인 하면 패스워드를 encode 처리하여

해쉬로 암호화 된 비번 vs DB에 있는 해쉬로 암호화 된 패스워드 를 비교할 수 있다. 

 

패스워드가 같으면 로그인 되는것!

 

 

 

과정 간단히 다시 설명

 

<form action="/auth/loginProc" method="post"> 

해당 주소로 로그인 요청이 들어오면 스프링 시큐리티는 action ="/auth/loginProc" 주소로 가는 로그인 요청을 가로챈다

post 방식 X  ▶  .loginProcessingUrl("/auth/loginProc")

 

[SecurityConfig]

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .csrf().disable() // csrf 토큰 비활성화 (테스트시 걸어두는게 좋음)
        .authorizeRequests()
            .antMatchers("/","/auth/**", "/js/**", "/css/**","/image/**")
            .permitAll()
            .anyRequest() // 다른 요청들은
            .authenticated()// 인증이 되어야한다.
        .and()
            .formLogin()
            .loginPage("/auth/loginForm")
            .loginProcessingUrl("/auth/loginProc")  // 스프링 시큐리티가 해당 주소로 오는 로그인 요청을 가로채어 로그인한다.
            .defaultSuccessUrl("/"); // 정상적으로 요청이 완료되면 "/" 주소로 이동
}

 

 

username,password 정보를 PrincipalDetailService 클래스의 loadUserByUsername 에 던져준다

 

 

[PrincipalDetailService]

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User principal = userRepository.findByUsername(username)
      .orElseThrow(()->{
           return new UsernameNotFoundException("해당 사용자를 찾을 수 없습니다. : " + username);
    });
return new PrincipalDetail(principal);

username 정보를 DB에 찾아 유저 obj을 만들어 넣어주고 해당 obj을 PrincipalDetail에 담아 리턴을 해줄때 

 

[SecurityConfig]

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
     auth.userDetailsService(principalDetailService).passwordEncoder(encodePWD());
}

principalDetailService가 로그인 요청을 하고 리턴시

사용자가 적은 패스워드를 해쉬로 암호화하고 VS DB의 회원가입 되어 암호화 된 비밀번호와 비교를 먼저해준다.

 

끝나고 정상일때 세션이 만들어진다. 

스프링 시큐리티 세션에 PrincipalDetail로 감싸져서 - PrincipalDetail(principal) - 유저 정보가 저장이 된다. 

 

 

 

 

 .defaultSuccessUrl("/");  로그인이 되고나면 "/" 주소로 이동한다.

/ 주소는 BoardController에 get매핑 주소이다.

@GetMapping({"", "/"})
public String index() {  
    return "index"; 
}

 

 

 

컨트롤러에서 세션에 접근하는 법

 

@AuthenticationPrincipal PrincipalDetail principalDetail 쓰면 된다.

@Controller
public class BoardController {

	@GetMapping({"","/"})
	public String index(@AuthenticationPrincipal PrincipalDetail principalDetail) { // 컨트롤러에서 세션을 찾는법
		System.out.println("로그인 사용자 아이디 : "+ principalDetail.getUsername());
		return "index";
	}
}

 

 

아래처럼 안쓰면 에러가 나서 못찾는다.

@Controller
public class BoardController {
	
	private PrincipalDetail principal;

	@GetMapping({"","/"})
	public String index() { // 컨트롤러에서 세션을 찾는법
		System.out.println("로그인 사용자 아이디 : "+ principal.getUsername());
		return "index";
	}
}

 

 

 

하지만 메인페이지에서 로그인 인증이 필요없어서 @AuthenticationPrincipal PrincipalDetail principalDetail 지운다

 

 

 

** 로그아웃을 하고싶을땐 주소 뒤에 /logout만 붙여도 된다. 스프링 시큐리티에서 정한 default 값이다.

 

반응형
LIST

+ Recent posts