개발자의 오르막
Spring Security + JPA 로그인 구현 (String 아이디로 로그인 하기) 본문
제대로 된 기반이 안되 막히는 건지 모르겠지만.. 차근차근 정리하면서 진행해야겠다.
우선 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 개념 부분과 어디가 잘못된지 모르니까 , 이게 커스터마이징을 해야하는건지
안해야하는건지 잘 모르고, 그래서 시간이 많이 소요됐다..
찾으면서 알게된 개념부분은 다시 다른 파트로 정리해야겠다.
'ORM > JPA' 카테고리의 다른 글
[# JPA] Pageable (0) | 2020.06.06 |
---|---|
SpringBoot + JPA QueryDSL 연동 (0) | 2019.10.08 |
Spring Security Login 기능 구현 (0) | 2019.09.24 |
스프링 데이터 JPA 커스텀 인터페이스 만드는 방법 (0) | 2019.09.24 |
스프링데이터 JPA 기본 개념과 메소드 기능 확인 (0) | 2019.09.24 |