SPRING(자바) 로 login 구현
1. 로그인 테이블 만들기(DB)
- 회원테이블을 만든다. 물론 그외에 칼럼이 있지만 생략 하였다.
CREATE TABLE user ( userNo int(11) NOT NULL AUTO_INCREMENT, userEmail varchar(50) NOT NULL, userPwd varchar(50) DEFAULT NULL )
2. 로그인창 jsp 만들기
- 로그인폼을 기본적인 형식으로 만들었다. 아이디저장, 자동로그인는 추후 할 예정이라서 만들었다.
<form action="/login/loginPost" method="post" id="loginForm">
<div class="form-group label-floating">
<label class="control-label">이메일 주소</label> <input name="userEmail" type="text" id="exampleInputEmail1" class="form-control" value="${cookie.rememberID.value}">
</div>
<div class="form-group label-floating">
<label class="control-label">비밀번호</label> <input name="userPwd" type="password" id="exampleInputPassword1" class="form-control">
</div>
<div class="checkbox">
<label> <input type="checkbox" name="rememberEmail"> 아이디저장 </label>
<label> <input type="checkbox" name="useCookie"> 자동로그인 </label>
</div>
<button type="submit" class="btn btn-default">로그인</button>
</form>
3. 필요한 pom.xml에 메이븐파일 작성
- Mybatis 를 사용하기 위해 아래 4개 메이븐파일을 pom.xml에 추가
<!-- jdbc 연결 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.41</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.1</version>
</dependency>
<!-- mybatis-spring 연결 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
4. 로그인 Controller 작성
- loginGet 메서드에서는 이전 페이지 기억을 위해 request.getHeader(“referer”) 를 썼다.
- 허나 예외적으로 바로 로그인 URI 접근이라든지 로그인창 막클릭 했을때는 저장을 안한다.
- dest 세션은 기존 페이지URI 를 저장하고있다.
@Controller
@RequestMapping("/login")
public class LoginController {
@Inject
private BCryptPasswordEncoder pwdEncoder;
@Inject
private UserService userService;
// 로그인창
@RequestMapping(value = "/loginGet", method = RequestMethod.GET)
public String loginGet(HttpServletRequest request) throws Exception {
HttpSession session = request.getSession();
// requestUrl 가 null 이 아니거나 referer 가 null이 아닐경우
if (request.getRequestURI() != null && request.getHeader("referer") != null) {
// 이전페이지가 loginGet이거나 직접 로그인URL에 접속하지 않았을때만 referrer 저장
if (!(request.getRequestURI().equals("/login/loginGet") && request.getHeader("referer").equals("http://localhost/login/loginGet"))) {
// dest 세션에 이전 페이지 정보 저장
session.setAttribute("dest", request.getHeader("referer"));
}
}
return "/user/login";
}
// 로그인 submit
@RequestMapping(value = "/loginPost", method = RequestMethod.POST)
public void loginPost(LoginDTO dto, Model model, HttpSession session) throws Exception {
UserVO vo = null;
// DB 비밀번호와 로그인 비밀번호가 틀릴경우 loginFail 모델은 내려준다.
if (!pwdEncoder.matches(dto.getUserPwd(), userService.getPwd(dto))) {
model.addAttribute("loginFail", true);
return;
}
// vo에 userNo, userEmail, UserNick, userAuth 저장
UserVO vo = userService.login(dto);
model.addAttribute("userVO", vo); // model에 {userVO : vo} 저장
}
// 로그아웃
@RequestMapping(value = "/logout", method = RequestMethod.GET)
public String logout(HttpServletRequest request, HttpServletResponse response, HttpSession session) throws Exception {
Object obj = session.getAttribute("login");
// 세션 제거
if (obj != null) {
UserVO vo = (UserVO) obj;
session.removeAttribute("login");
session.invalidate();
}
return "/login/logout";
}
}
5. loginInterCepter.class 작성
- servlet-context.xml 에 아래내용을 추가한다.
- /login/loginPost URI 들어갈때 인터셉터를 거치도록 한다.
<beans:bean id="loginInterceptor" class="패키지명.LoginInterceptor" />
<interceptors>
<interceptor>
<mapping path="/login/loginPost" />
<beans:ref bean="loginInterceptor" />
</interceptor>
</interceptors>
- 그리고나서 HandlerInterceptorAdapter 를 상속받으면 인터셉터를 사용 할수있다.
- ‘preHandle’은 컨트롤러를 거치기 전이고, ‘postHandle’은 컨트롤러를 거친 후 뷰를 거치기 전에 실행된다.
- 인터셉터때문에 자바단에서 alert창을 띄어야 하기 때문에 PrintWriter 를 썼다. (추천하지 않습니다.)
param을 던지고 javascript가 param을 받고 처리하는게 나을듯…
public class LoginInterceptor extends HandlerInterceptorAdapter {
private static final String LOGIN = "login";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
// 기존 로그인정보 삭제
if (session.getAttribute(LOGIN) != null) {
session.removeAttribute(LOGIN);
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HttpSession session = request.getSession();
ModelMap modelMap = modelAndView.getModelMap();
Object userVO = modelMap.get("userVO"); // userVO 모델속성 가져옴
UserVO vo = (UserVO) userVO;
response.setContentType("text/html; charset=UTF-8");
PrintWriter out = response.getWriter();
// 아이디,비밀번호가 일치하지 않을 경우
if (modelMap.get("loginFail") != null) {
// 실패알림 후 로그인창 가기
out.println("<script>alert('아이디와 비밀번호가 일치하지 않습니다.'); location.href = '/login/loginGet'</script>");
out.close();
return;
}
// 로그인 성공시(객체가 있을 경우)
if (userVO != null) {
session.setAttribute(LOGIN, userVO); // userVO 세션저장
String dest = (String) session.getAttribute("dest"); // URI 세션 저장
// 아이디 비밀번호가 실패할 경우 다시 로그인할때 referer를 loginPost 저장하기떄문에 체크
if (dest.equals("http://localhost/login/loginPost")) {
dest = "/";
}
response.sendRedirect(dest != null ? (String) dest : "/");
} else {
response.sendRedirect("/login/loginGet");
}
}
}
6. userMapper.xml 작성
- 로그인할때에는 userNo, UserEamil, userNick, userAuth (유저권한) 을 가져온다.
<select id="org.sbang.mapper.UserMapper.login" resultType="UserVO"> <!-- 로그인 -->
select userNo, userEmail, userNick, userAuth from user where userEmail = #{userEmail}
</select>
7. userDAO, userService 작성
- DAO는 DB등 하나의 데이터 접근 및 갱신만 처리하며, Service 는 DAO들을 호출하여 읽은 데이터에 대한 비지니스 로직을 수행하며 트랜잭션으로 처리 하기 위함이다.
// userDAO
@Repository
public class UserDAOImpl implements UserDAO {
@Inject
private SqlSession session;
@Override
public UserVO login(LoginDTO dto) throws Exception {
return session.selectOne("org.sbang.mapper.UserMapper.login", dto);
}
}
// userService
@Service
public class UserServiceImpl implements UserService {
@Inject
private UserDAO userDAO;
@Override
public UserVO login(LoginDTO dto) throws Exception {
return userDAO.login(dto);
}
}
- 기본적인 로그인 방식을 알아보았다. (+기존페이지 기억 ) 하지만 이외에도 아이디 저장 및 자동로그인 또는 권한에 따른 로그인 접근 등등 그외에도 예외적인것을 다 처리 해야한다. 스프링 시큐리티를 좀더 공부하여 시큐리티로 간단히 로직을 짤수 있다고 한다. 시도는 해봤는데 잘 안되어서 포기를 여러번했다. 언젠가는 시큐리티를 넘어야 할 산이기 때문에 공부가 꾸준히 필요하다.
- (추가) 스프링 시큐리티는 [오픈스터디 개편] 프로젝트 에 소스가 있으니 확인해보세요. (실무에서 자주 사용합니다.)