-
[SpringBoot] 스프링 시큐리티 적용하기Spring 2019. 12. 24. 11:15
개요
토이 프로젝트를 진행하면서 사용자 권한 관리 기능을 맡게 되었다. 팀원들과 기획을 하는 중에 스프링 시큐리티를 사용하면 사용자 권한 관리 및 보안 등을 별도의 복잡한 로직 없이 간단한 설정으로 빠르게 구현할 수 있다고 하여 이번 프로젝트에 적용해 보았다.
1. pom.xml
스프링 시큐리티를 사용하기 위해서는 pom.xml에 아래와 같이 작성해 주어야한다.
<!-- 스프링 시큐리티 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
2. 로그인 페이지
해당 사용자의 아이디와 비밀번호를 SpringSecurityConfig에서 구분할 수 있게 각각 id값을 셋팅해주었다.
login.jsp 3. SpringSecurityConfig
@Configuration @EnableWebSecurity @EnableGlobalAuthentication @ComponentScan(basePackages = {"com.kh.*"}) public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { //로그인과 관련된 Provider 의존성 주입 @Autowired AuthProvider authProvider; //로그인 실패시 이동할 Handler 의존성 주입 @Autowired LoginFailureHandler authFailureHandler; //로그인 성공시 이동할 Handler 의존성 주입 @Autowired LoginSuccessHandler authSuccessHandler; //비밀번호 BCrypt 암호화를 위한 Bean 등록 @Bean public BCryptPasswordEncoder passwordEncoder() { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); return bCryptPasswordEncoder; } //로그인 필요없이 특정 path("/**/*.css", "/**/*.png", "/**/*.jpg", "/**/*.gif")에 접근이 가능하도록 설정 @Override public void configure(WebSecurity web) { web.ignoring().antMatchers("/**/*.css", "/**/*.png", "/**/*.jpg", "/**/*.gif"); } @Override protected void configure(HttpSecurity http) throws Exception { //로그인 설정 http.authorizeRequests() //permitAll() 메소드는 어떠한 보안 요구 없이 요청을 허용해준다. .antMatchers("/login", "/login/**", "/users/**", "/error", "/error/**").permitAll() //hasAnyRole() 메소드는 여러개의 권한 설정가능 .antMatchers("/common/**").hasAnyRole("AUTH0000", "AUTH0100", "AUTH0101", "AUTH0001") .antMatchers("/seller/**").hasAnyRole("AUTH0000", "AUTH0100") //hasRole() 메소드는 하나의 권한만 설정가능 .antMatchers("/admin/**").hasRole("AUTH0000") .and() //csrf 비활성화 .csrf().disable() //로그인 폼 사용 .formLogin() //로그인 URL 설정 이 설정이 없으면 스프링 시큐리티에서 제공해주는 로그인 페이지로 이동 .loginPage("/login") //authProvider에서 로그인 성공시 이동할 Handler 설정 .successHandler(authSuccessHandler) //authProvider에서 로그인 실패시 이동할 Handler 설정 .failureHandler(authFailureHandler) //해당 URL 요청이 들어오면 authProvider로 로그인 정보를 전달하여 로그인 로직 수행될 수 있도록 설정 .loginProcessingUrl("/j_spring_security_check") //이 설정을 통해 사용자가 전달한 key 값이 아이디인지 알 수 있다.(login.jsp 정의) .usernameParameter("adminId") //이 설정을 통해 사용자가 전달한 key 값이 패스워드인지 알 수 있다.(login.jsp 정의) .passwordParameter("adminPw") .and() //로그아웃 관련 설정 .logout() //로그아웃을 요청할 URL .logoutUrl("/logout") .invalidateHttpSession(true) //로그아웃 성공시 보낼 페이지 설정 .logoutSuccessUrl("/login") .and() //로그인 프로세스가 진행될 provider 설정 .authenticationProvider(authProvider); } }
hasAnyRole 부분을 보면 권한 값이 "AUTH0000"로 설정 되어있다.
하지만 실제 DB에서는 해당 권한 값을 "ROLE_AUTH0000"으로 저장하여 데이터를 관리하였다.
왜냐하면 해당 권한 값을 비교할때 시큐리티가 자동적으로 "ROLE_"라는 값을 prefix로 가져와 비교하기 때문이다.
여기서 AUTH0000(관리자), AUTH0100(판매자), AUTH0101(일반 사용자)로 권한을 지정하였다.
4. AuthProvider
SpringSecurityConfig 작성을 작성하였으면 아이디 존재 여부 및 패스워드 일치 여부 Check를 진행할 AuthProvider를 아래와 같이 작성해준다.
@Component public class AuthProvider implements AuthenticationProvider{ @Autowired HttpServletRequest request; @Autowired LoginService loginService; @Autowired BCryptPasswordEncoder passwordEncoder; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { //로그인 성공시 획득할 권한 리스트 Collection<GrantedAuthority> authorities = new ArrayList<>(); //유저 정보 객체 User userVo = new User(); //권한 정보 값이 담길 변수 String authority = null; String id = (String) authentication.getPrincipal(); String pw = (String) authentication.getCredentials(); //유저 정보조회 userVo = loginService.selectUsersById(id); // 아이디 존재하지 않는 경우 if(userVo == null) { throw new UsernameNotFoundException(""); } //비밀번호 불일치 if(!passwordEncoder.matches(pw, userVo.getUserPwd())) { throw new InvalidPasswordException(""); } authority = userVo.getAuth().getAuthRole(); authorities.add(new SimpleGrantedAuthority(authority)); return new UsernamePasswordAuthenticationToken(userVo, null, authorities); } @Override public boolean supports(Class<?> authentication) { return true; } }
패스워드 일치 여부 비교시 주의할 점은 BCrypt 암호화 방식은 키 값이 계속 바뀌기 때문에 equal 메소드로 비교시 비밀 번호를 맞게 입력해도 계속 false 값을 return하기 때문에 matches라는 메소드를 사용하여 패스워드 일치 여부를 비교해 줄 수 있도록 하였다.
5. LoginSuccessHandler
@Component public class LoginSuccessHandler implements AuthenticationSuccessHandler{ @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { ResultVo resultVo = new ResultVo(); ObjectMapper mapper = new ObjectMapper(); String jsonInString = null; Object vo = authentication.getPrincipal(); User userVo = (User) vo; HttpSession session = request.getSession(); session.setAttribute("sessionUser", userVo.getUserId()); session.setAttribute("userNm", userVo.getUserNm()); session.setAttribute("userSeq", userVo.getUserSeq()); // 권한 체크 후 default URL 세팅 String adminAuth = userVo.getAuth().getAuthRole(); String homeUrl = null; //로그인 성공시 사용자의 권한별로 homeUrl 설정 if (adminAuth.equals("ROLE_AUTH0000") || adminAuth.equals("ROLE_AUTH0100") || adminAuth.equals("ROLE_AUTH0101")) { homeUrl = "/main"; } session.setAttribute("homeUrl", homeUrl); resultVo.setResult("로그인 성공"); resultVo.addResult("homeUrl", request.getContextPath() + homeUrl); jsonInString = mapper.writeValueAsString(resultVo); response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); PrintWriter out = response.getWriter(); out.print(jsonInString); out.flush(); out.close(); } }
AuthProvider에서 로그인이 성공하게되면 SpringSecurityConfig의 설정에 의해 LoginSuccessHandler로 이동하게된다. LoginSuccessHandler에서는 사용자의 Session을 생성해주고 권한별로 이동할 패이지를 설정해 이동할 수 있게 구현하였다.
결론
기존에 스프링 시큐리트를 사용하기 전에는 사용자의 권한을 Check 부분을 각각의 페이지 별로 설정해 주는 방법을 사용하였기 때문에 권한이 추가되거나 하는 변동 사항이 생기면 각 페이지 별로 수정을 해줘야하는 번거로움이 있었다. 스프링 시큐리티를 적용하니 이러한 번거로운 작업들을 단순하게 구현하여 유저의 인증과 인가 부분을 쉽고 빠르게 구현할 수 있었다. 인증과 인가에 대한 유지보수 측면에서도 편리할 것 같다.
'Spring' 카테고리의 다른 글
Spring Boot와 AWS로 혼자 구현하는 웹 서비스 - 2장 Spring Boot에서 테스트 코드를 작성하자 (1) 2022.02.20 [Spring] Maven, pom.xml (0) 2021.04.13 쿼리 로그 찍기 (0) 2020.11.27 iBatis, myBatis 에서 Date형 시분초 잘리는 문제 (0) 2020.10.27 Client Connection Pool Manager 설정(1) (1) 2020.03.17