일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- SQL 처리
- 천공의 시간
- DBMS 구성요소
- 니가 참 좋아
- I'm fine thank you
- db
- 인덱스
- 러블리즈
- oracle
- 말 더듬
- 신입
- 봄 사랑 벚꽃 말고
- 장범준
- 개발자
- 오라클
- IT
- 데이터베이스
- Inside Of Me
- 오라클 아키텍처
- 슬픔의 후에
- nginx
- DBMS
- index
- 핑거스타일
- 6학년 8반 1분단
- 레이디스코드
- 스위트라떼
- 아이유
- 기타
- 악보
취미로 음악을 하는 개발자
[Spring Boot] Security Form, Status Check 본문
프로젝트 생성
코드 구현
* 이전 프로젝트의 파일을 그대로 복사하되 아래 있는 파일들이 추가 및 수정되었다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | package com.study.springboot.auth; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/").permitAll() .antMatchers("/css/**", "/js/**", "/img/**").permitAll() .antMatchers("/guest/**").permitAll() .antMatchers("/member/**").hasAnyRole("USER", "ADMIN") .anyRequest().authenticated(); http.formLogin() .loginPage("/loginForm") // default : /login .loginProcessingUrl("/j_spring_security_check") .failureUrl("/loginError") // default : /login?error //.defaultSuccessUrl("/") .usernameParameter("j_username")// default : j_username .passwordParameter("j_password")// default : j_password .permitAll(); http.logout() .logoutUrl("/logout") // default .logoutSuccessUrl("/") .permitAll(); // ssl을 사용하지 않으면 true로 사용 http.csrf().disable(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("user").password(passwordEncoder().encode("1234")).roles("USER") .and() .withUser("admin").password(passwordEncoder().encode("1234")).roles("ADMIN"); // ROLE_ADMIN 에서 ROLE_는 자동으로 붙음 } @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } | cs |
[25~32번 줄]
원래 loginPage의 디폴트는 "/login"인데 여기서는 "/loginForm"으로 바꿈
그리고 Security 프레임워크가 처리하는 url 그대로인 "/j_spring_security_check"로 하고
로그인 실패했을 때는 "/loginError"를 호출
jsp의 form에서 "j_username"과 "j_password"의 값을 ID와 패스워드로 지정
로그인은 누구나 할 수 있으므로 permitAll()
[34~37번 줄]
로그아웃 요청은 디폴트 값인 "/logout"으로, 작업이 성공적으로 끝나면 루트 페이지로 감
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | package com.study.springboot; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class MyController { @RequestMapping("/") public @ResponseBody String root() throws Exception { return "Security"; } @RequestMapping("/guest/welcome") public String welcome1() { return "guest/welcome1"; } @RequestMapping("/member/welcome") public String welcome2() { return "member/welcome2"; } @RequestMapping("/admin/welcome") public String welcome3() { return "admin/welcome3"; } @RequestMapping("/loginForm") public String loginForm() { return "security/loginForm"; } @RequestMapping("/loginError") public String loginError() { return "security/loginError"; } } | cs |
이전 프로젝트와 달리 "/loginForm", "loginError"가 추가되었다.
// loginForm.jsp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>LoginForm</title> </head> <body> <h1>loginForm.jsp</h1> <form action="<c:url value="j_spring_security_check" /> " method="post"> ID : <input type="text" name="j_username"> <br /> PW : <input type="text" name="j_password"> <br /> <input type="submit" value="LOGIN"> <br /> </form> </body> </html> | cs |
// loginError.jsp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>LoginForm</title> </head> <body> <h1>loginError.jsp</h1> 로그인 실패 <br><p> <a href=loginForm>로그인 페이지로 가기</a> </body> </html> | cs |
결과 화면
guest는 누구나 접근 가능하기 때문에 로그인 없이 들어갈 수 있다.
member부터는 로그인이 필요한데 먼저 "USER"의 권한을 가진 user로 들어간다.
member에는 접근이 가능하지만 admin으로 들어갈 때는 "ADMIN" 권한을 가진 유저만 접근이 되므로
403 Forbidden 에러가 뜬다.
/logout을 하면 로그아웃이 되고 다시 admin 계정으로 로그인한다.
그러면 Admin에도 접근이 가능하게 된다.
Status Check
프로젝트 생성
코드 구현
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | package com.study.springboot.auth; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.authentication.AuthenticationFailureHandler; @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ @Autowired public AuthenticationFailureHandler authenticationFailureHandler; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/").permitAll() .antMatchers("/css/**", "/js/**", "/img/**").permitAll() .antMatchers("/guest/**").permitAll() .antMatchers("/member/**").hasAnyRole("USER", "ADMIN") .antMatchers("/admin/**").hasRole("ADMIN") .anyRequest().authenticated(); http.formLogin() .loginPage("/loginForm") // default : /login .loginProcessingUrl("/j_spring_security_check") //.failureUrl("/loginForm?error") // default : /login?error //.defaultSuccessUrl("/") .failureHandler(authenticationFailureHandler) .usernameParameter("j_username")// default : j_username .passwordParameter("j_password")// default : j_password .permitAll(); http.logout() .logoutUrl("/logout") // default .logoutSuccessUrl("/") .permitAll(); // ssl을 사용하지 않으면 true로 사용 http.csrf().disable(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("user").password(passwordEncoder().encode("1234")).roles("USER") .and() .withUser("admin").password(passwordEncoder().encode("1234")).roles("ADMIN"); // ROLE_ADMIN 에서 ROLE_는 자동으로 붙음 } @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } | cs |
[34번 줄]
원래는 32번 줄에서 failure를 지정했지만 이제는 AuthenticationFailureHandler를 선언하고
의존성을 자동으로 주입받고 failureHandler로 지정해준다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | package com.study.springboot; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class MyController { @RequestMapping("/") public @ResponseBody String root() throws Exception { return "Security"; } @RequestMapping("/guest/welcome") public String welcome1() { return "guest/welcome1"; } @RequestMapping("/member/welcome") public String welcome2() { return "member/welcome2"; } @RequestMapping("/admin/welcome") public String welcome3() { return "admin/welcome3"; } @RequestMapping("/loginForm") public String loginForm() { return "security/loginForm"; } } | cs |
로그인 에러는 따로 설정해주기 때문에 해당 코드는 없앴다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | package com.study.springboot.auth; import java.io.IOException; import javax.security.auth.login.CredentialExpiredException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.CredentialsExpiredException; import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; @Configuration public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler{ @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { String loginid = request.getParameter("j_username"); String errormsg = ""; if (exception instanceof BadCredentialsException) { loginFailureCount(loginid); errormsg = "아이디나 비밀번호가 맞지 않습니다. 다시 확인해주세요."; } else if (exception instanceof InternalAuthenticationServiceException) { loginFailureCount(loginid); errormsg = "아이디나 비밀번호가 맞지 않습니다. 다시 확인해주세요."; } else if (exception instanceof DisabledException) { errormsg = "계정이 비활성화되었습니다. 관리자에게 문의하세요."; } else if (exception instanceof CredentialsExpiredException) { errormsg = "비밀번호 유효기간이 만료되었습니다. 관리자에게 문의하세요."; } request.setAttribute("username", loginid); request.setAttribute("error_message", errormsg); request.getRequestDispatcher("/loginForm?error=true").forward(request, response); } // 비밀번호 3번 이상 틀릴 시 계정 잠금 처리 protected void loginFailureCount (String username) { // // 틀린 횟수 업데이트 // userDao.countFailure(username); // // 틀린 횟수 조회 // int cnt = userDao.checkFailureCount(username); // if(cnt == 3) { // // 계정 잠금 처리 // userDao.disabledUsername(username); // } } } | cs |
[29~38번 줄]
에러 메시지를 직접 만드는데 특히, 29번과 23번 줄의 에러는 서로 다른 에러지만
너무 구체적인 에러를 알려주면 보안 위협이 있으므로 대략적인 에러 내용만 알려준다.
[41~44번 줄]
request의 속성 "username"에 loginid 값을, "error_message"에 errormsg 값을 넣어준다.
* loginFailureCount 메소드는 "이런 식으로 처리되는구나~" 정도로만 보고 직접 구현은 하지 않습니다.
// loginForm.jsp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>LoginForm</title> </head> <body> <h1>loginForm.jsp</h1> <c:url value="j_spring_security_check" var="loginUrl" /> <form action="${loginUrl }" method="post"> <c:if test="${param.error != null }"> <p> Login Error! <br /> ${error_message } </p> </c:if> ID : <input type="text" name="j_username" value="${username }"> <br /> PW : <input type="text" name="j_password"> <br /> <input type="submit" value="LOGIN"> <br /> </form> </body> </html> | cs |
[15~18번 줄]
파라미터의 에러가 null이 아니면 에러 메시지 출력
error_message는 에러 핸들러에서 정해준 조건에 따른 에러 메시지가 출력될 것이다.
// welcome3.jsp, welcome2.jsp도 같은 코드를 넣어주면 된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE html> <html> <head> <meta http-equiv="content-Type" content="text/html; charset=UTF-8"> <title>Welcome</title> </head> <body> welcome : Admin <hr> <c:if test="${not empty pageContext.request.userPrincipal }"> <p> is Log-In </p> </c:if> <c:if test="${empty pageContext.request.userPrincipal }"> <p> is Log-Out </p> </c:if> USER ID : ${pageContext.request.userPrincipal.name } <br /> <a href="/logout">Log Out</a> <br /> </body> </html> | cs |
15번 줄은 로그인 정보가 있을 때, 19번 줄은 없을 때의 코드로 보면 된다.
결과 화면
원래 ID/PW는 user/1234지만 일부러 로그인 실패를 불러본다.
그러면 관련 에러 메시지가 뜨면서 다시 로그인하라고 나온다.
다시 user/1234로 로그인하고 성공하면 유저의 ID가 출력되고 로그아웃 버튼이 활성화되어 있다.
로그아웃하면 다시 홈 화면으로 돌아가고 admin도 마찬가지로 로그인 성공하면 위와 같이 뜨고
실패하면 에러 메시지를 출력한다.
'공대인 > Spring[Boot]' 카테고리의 다른 글
[Spring Boot] Webjars (0) | 2019.08.29 |
---|---|
[Spring Boot] Security Taglibs, Database (2) | 2019.08.26 |
[Spring Boot] Security (0) | 2019.08.22 |
[Spring Boot] Transaction (Manager, Template, Propagation) (0) | 2019.08.22 |
[Spring Boot] logback (0) | 2019.08.16 |