개발자의 오르막

Spring Security + JPA 로그인 구현 (String 아이디로 로그인 하기) 본문

ORM/JPA

Spring Security + JPA 로그인 구현 (String 아이디로 로그인 하기)

계단 2019. 9. 26. 17:48

제대로 된 기반이 안되 막히는 건지 모르겠지만.. 차근차근 정리하면서 진행해야겠다.

우선 Register.html 에서 Post 방식으로 /create url 에 정보를 전송한다.

 

# MemberController

@Transactional
	  @PostMapping("/create")
	  public String joinPost(@ModelAttribute("member") Member member) {

	    log.info("MEMBER: " + member);

	    String encryptPw = BCrypt.hashpw(member.getUpw(),BCrypt.gensalt());

	    log.info("en: " + encryptPw);

	    member.setUpw(encryptPw);
	    
	    MemberRole role = new MemberRole();
	    role.setRoleName("BASIC");
	    
	    member.setRoles(Arrays.asList(role));
	    
	    repo.save(member);

	    return "redirect:/join/login";
	  }

 

 

- 이 클래스에서는 /create url 을 통해 회원가입을 진행한다.

 

- 위의 로그를 찍었을 때, 이미 Member의 내용이 저장되어 있다.

- @ModelAttribute("member") 때문인걸까?..  

 

- encryptPw 는 암호의 인코딩을 하는 작업이다. 스프링 시큐러티5에서 제공하는 BCrypt로,

  입력된 비밀번호를 인코딩 한 채로 DB에 저장할 수 있다.

  실제 비밀번호는 사용자만 알 수 있다.

 

- MemberRole의 관계를 맵핑하기 위해 객체를 선언하고, 기본 값인 BASIC을 설정한다.

 

- Repository의 save() 함수를 사용 후, redirect:/join/login 을 사용한다.

 

그렇다면 컨트롤러에서 사용되는 Member 도메인과 Repository를 보자.

 

# Member.class

 

@Getter
@Setter
@Entity
@Table(name = "tb_members")
@EqualsAndHashCode(of = "id")
@ToString
public class Member {
	
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	
	@Column(nullable = false, unique = true)
	private String uemail;
	
	private String upw;
	
	private String uname;
	
	private String uphone;
	
	private boolean uphoneCheck;
	
	private String uaddress1;
	
	private String uaddress2;
	
	private int ugrade;
 
	private String upicture;
	
	@Temporal(TemporalType.TIMESTAMP)
	private Date regdate;
	
	@Temporal(TemporalType.TIMESTAMP)
	private Date joinDate;
	
	@Temporal(TemporalType.TIMESTAMP)
	private Date updatedate;
	
	@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
	@JoinColumn(name = "member")
	private List<MemberRole> roles;
	
}

 

원래 스프링부트의 예제로 uemail 을 string 타입으로 id를 선언했다.

Test에서 임의로 집어넣은 데이터는 잘 들어갔지만 화면에서 넣은 데이터는

입력되지 않았다. ID 부분을 Long 타입으로 바꿔달라는 에러 메시지가 떴었다.

 

- 아이디를 구별한 ID는 Long 타입으로 선언하고

- 실제 사용자가 로그인할 ID는 uemail 로 지정한다. 

 

그럼 위의 controller에서 사용했던 repo.save 의 MemberRepository 를 살펴보자.

 

 

# MemberRepository

 

- 여기서 CrudRepsoitory<Member, Long> 타입으로 선언되어 있다.

- 원래 Long 타입이 아니라 String 타입으로, 로그인까지 되었으나,

  회원가입을 진행할 때 이 Long 타입이 아니라고 에러메시지를 계속 뱉었다.

- 따라서 아이디를 구별할 아이디는 Long타입으로 따로 명시하는 방향으로 갔다.

 

회원 가입에 대한 로직은, /create -> controller -> domain -> controller -> MemberRepository

-> save() -> controller -> 화면

이런 경로라고 생각한다. 그리고 여기까진 DB에 자료입력 등 무사히 완료되었다.

 

하지만.. 문제의 로그인이 되지 않았지!!!! 

 

 


# SecurityConfig 클래스

 

@Log
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter{
	
	@Autowired
	DataSource dataSource;
	
	@Autowired
	CatchUsersService catchUsersService;
	
	@Override
	protected void configure(HttpSecurity http) throws Exception{
		log.info("security config.........");
		
		http.authorizeRequests()
		.antMatchers("/qnaBoards/list").permitAll()
		.antMatchers("/qnaBoards/register")
		.hasAnyRole("BASIC", "MANAGER", "ADMIN");
		
		http.formLogin().loginPage("/join/login").successHandler(new LoginSuccessHandler())
		.and().logout().logoutUrl("/join/logout").logoutSuccessUrl("/").invalidateHttpSession(true);
		http.exceptionHandling().accessDeniedPage("/accessDenied");

		
		http.rememberMe()
		.key("catch")
		.userDetailsService(catchUsersService)
		.tokenRepository(getJDBCRepository())
		.tokenValiditySeconds(60*60*24);
	}
	
	private PersistentTokenRepository getJDBCRepository() {
		
		JdbcTokenRepositoryImpl repo = new JdbcTokenRepositoryImpl();
		repo.setDataSource(dataSource);
		return repo;
	}
	
	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}

	
	public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception{
	      auth.userDetailsService(catchUsersService).passwordEncoder(passwordEncoder());
	}
	
}

 

- Security 부분에서는 로그인 시 필요한 권한부여 및 로그인 기능이 들어있다.

- http.formLogin() 의 /join/login url이 들어오면, 로그인을 진행한다.

 

- 로그를 찍어놨던 순서를 보니 PasswrodEncoder() 메소드, configure() 메소드,

  getJDBCRepository() 메소드 순으로 실행됨을 알 수 있다.

  

즉, 사용자가 입력한 암호를 인코딩하고, configure 에서 formLogin() 실행할 때

DB의 인코딩된 암호와 비교하는 듯 하다. 

그리고 DataSource 의 토큰을 사용하는 듯 하는데, 어디에 사용하는지 잘 모르겠다.

이 순서의 부여 방법도!!

 

충격적인 건 configureGlobal 메소드가 실행되지 않았다.. 로그가 안찍힘.. 

그런데 CustomMemberRepositoryImpl 클래스가 실행되서

findByUemail() 메소드가 실행되며 에러가 찍혔다...

 

 

# CustomMemberRepositoryImpl  부분

@Repository
@Transactional
public class CustomMemberRepositoryImpl implements CustomMemberRepository{

	@Autowired
	EntityManager entityManager;
	
	@Override
	public Member findByUemail(String uemail) {
		System.out.println("findByUemail ===================");
		TypedQuery<Member> typedQuery = entityManager.createQuery("SELECT m FROM MEMBER m where m.uemail = ?1", Member.class);
		typedQuery.setParameter(1, uemail);
		Member member = typedQuery.getSingleResult();
		return member;
	}

}

 

#CustomMemberRepository 부분

public interface CustomMemberRepository {
	
	public Member findByUemail(String uemail);
}

 

일단 문제가 되는 부분은 JPQL 쿼리 부분이다.

unexpected token: MEMBER  // 에러가 뜬 상태이다.

 


하.. 정말.. findByUemail 은 필요가 없었다. 이유는 도메인에 변수만 등록되어있으면

JPA 에서 이름만으로 메소드를 생성해주기 때문이다.

 

Custom 부분을 다 없애고, JPA Repository 부분에 

 

이 메소드 그냥 정의만 하고,

 

이 부분에서, String 을 매개변수로 아이디를 찾는 findByUemail 만 사용하면 되는거였다.

 

 

JPA 개념 부분과 어디가 잘못된지 모르니까 , 이게 커스터마이징을 해야하는건지

안해야하는건지 잘 모르고, 그래서 시간이 많이 소요됐다.. 

 

찾으면서 알게된 개념부분은 다시 다른 파트로 정리해야겠다.

Comments