[C-3-1] 스프링 웹 시큐리티를 이용한 로그인 처리

2020. 1. 21. 22:10Project C (SBMS)/Project C 파트3

반응형

src/main/java

com.sbms.domain

AuthVO.java

 

더보기
package com.sbms.domain;

import lombok.Data;

@Data
public class AuthVO {

  private String userid;
  private String auth;
  
}

 


src/main/java

com.sbms.domain

MemberVO.java

 

더보기
package com.sbms.domain;

import java.util.Date;
import java.util.List;

import lombok.Data;

@Data
public class MemberVO {

	private String userid;
	private String userpw;
	private String userName;
	private boolean enabled;

	private Date regDate;
	private Date updateDate;
	private List<AuthVO> authList;

}

 


src/main/java

com.sbms.mapper

MemberMapper.java

 

더보기
package com.sbms.mapper;

import com.sbms.domain.MemberVO;

public interface MemberMapper {
	public MemberVO read(String userid);
	public void createMember(MemberVO member);
	public void createMemberAuth(MemberVO member);
}

 


src/main/resources

com

sbms

mapper

MemberMapper.xml

 

더보기
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sbms.mapper.MemberMapper">
	<resultMap type="com.sbms.domain.MemberVO" id="memberMap">
		<id property="userid" column="userid"/>
		<result property="userid" column="userid"/>
		<result property="userpw" column="userpw"/>
		<result property="userName" column="username"/>
		<result property="regDate" column="regdate"/>
		<result property="updateDate" column="updatedate"/>
		<collection property="authList" resultMap="authMap">
		</collection> 
	</resultMap>
  
	<resultMap type="com.sbms.domain.AuthVO" id="authMap">
		<result property="userid" column="userid"/>
		<result property="auth" column="auth"/>
	</resultMap>
  
	<select id="read" resultMap="memberMap">
		SELECT 
			mem.userid,  userpw, username, enabled, regdate, updatedate, auth
		FROM 
			tbl_member mem LEFT OUTER JOIN tbl_member_auth auth on mem.userid = auth.userid 
		WHERE mem.userid = #{userid} 
	</select>
	
	<!-- 등록 (회원) -->
	<insert id="createMember">
		insert into tbl_member (
			USERID, USERPW, USERNAME, REGDATE, UPDATEDATE, ENABLED
		) 
		values (
			#{userid}, #{userpw}, #{userName}, SYSDATE, SYSDATE, '1'
		)
	</insert>

	<!-- 등록 (회원 권한) -->
	<insert id="createMemberAuth">
		insert into TBL_MEMBER_AUTH (
			USERID, AUTH
		) values (
			#{userid}, 'ROLE_USER'
		)
	</insert>
		
</mapper>

 


src/main/java

com.spms.security.domain

CustomUser.java

 

더보기
package com.spms.security.domain;


import java.util.Collection;
import java.util.stream.Collectors;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;

import com.sbms.domain.MemberVO;

import lombok.Getter;

@Getter
public class CustomUser extends User {

	private static final long serialVersionUID = 1L;

	private MemberVO member;

	public CustomUser(String username, String password, 
			Collection<? extends GrantedAuthority> authorities) {
		super(username, password, authorities);
	}

	public CustomUser(MemberVO member) {
		super(member.getUserid(), member.getUserpw(), member.getAuthList().stream().map(auth -> new SimpleGrantedAuthority(auth.getAuth())).collect(Collectors.toList()));
		this.member = member;
	}
}

 


src/main/java

com.sbms.security

CustomAccessDeniedHandler.java

 

더보기
package com.sbms.security;


import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

import lombok.extern.log4j.Log4j;

@Log4j
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

  @Override
  public void handle(HttpServletRequest request, 
      HttpServletResponse response, AccessDeniedException accessException)
      throws IOException, ServletException {
    log.error("Access Denied Handler");
    log.error("Redirect....");
    response.sendRedirect("/accessError");

  }

}

 


src/main/java

com.sbms.security

CustomLoginSuccessHandler.java

 

더보기
package com.sbms.security;


import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import lombok.extern.log4j.Log4j;

@Log4j
public class CustomLoginSuccessHandler implements AuthenticationSuccessHandler {

	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth)
			throws IOException, ServletException {

		log.warn("Login Success");
		List<String> roleNames = new ArrayList<>();
		auth.getAuthorities().forEach(authority -> {
			roleNames.add(authority.getAuthority());

		});

		log.warn("ROLE NAMES: " + roleNames);

		if (roleNames.contains("ROLE_ADMIN")) {
			response.sendRedirect("/sample/admin");
			return;
		}

		if (roleNames.contains("ROLE_MEMBER")) {
			response.sendRedirect("/sample/member");
			return;
		}
		response.sendRedirect("/");
	}
}

 


src/main/java

com.sbms.security

CustomNoOpPasswordEncoder.java

 

더보기
package com.sbms.security;


import org.springframework.security.crypto.password.PasswordEncoder;

import lombok.extern.log4j.Log4j;

@Log4j
public class CustomNoOpPasswordEncoder implements PasswordEncoder {

	public String encode(CharSequence rawPassword) {
		log.warn("before encode :" + rawPassword);
		return rawPassword.toString();
	}

	public boolean matches(CharSequence rawPassword, String encodedPassword) {
		log.warn("matches: " + rawPassword + ":" + encodedPassword);
		return rawPassword.toString().equals(encodedPassword);
	}

}

 


src/main/java

com.sbms.security

CustomUserDetailsService.java

 

더보기
package com.sbms.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import com.sbms.domain.MemberVO;
import com.sbms.mapper.MemberMapper;
import com.spms.security.domain.CustomUser;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@Log4j
public class CustomUserDetailsService implements UserDetailsService {

	@Setter(onMethod_ = { @Autowired })
	private MemberMapper memberMapper;

	@Override
	public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {

		log.warn("Load User By UserName : " + userName);
		MemberVO vo = memberMapper.read(userName);
		log.warn("queried by member mapper: " + vo);
		return vo == null ? null : new CustomUser(vo);
	} 
}

 


src/main/java

com.spms.config

SecurityConfig.java

 

더보기
package com.sbms.config;

import javax.sql.DataSource;

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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.web.filter.CharacterEncodingFilter;

import com.sbms.security.CustomLoginSuccessHandler;
import com.sbms.security.CustomUserDetailsService;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@Configuration
@EnableWebSecurity
@Log4j
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Setter(onMethod_ = { @Autowired })
	private DataSource dataSource;

	@Bean
	public UserDetailsService customUserService() {
		return new CustomUserDetailsService();
	}

	// in custom userdetails
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {

		auth.userDetailsService(customUserService()).passwordEncoder(passwordEncoder());
	}

	@Bean
	public AuthenticationSuccessHandler loginSuccessHandler() {
		return new CustomLoginSuccessHandler();
	}

	@Override
	public void configure(HttpSecurity http) throws Exception {

		http.authorizeRequests()
			.antMatchers("/sample/all").permitAll()
			.antMatchers("/sample/admin").access("hasRole('ROLE_ADMIN')")
			.antMatchers("/sample/member").access("hasRole('ROLE_MEMBER')");

		http.formLogin()
			.loginPage("/customLogin")
			.loginProcessingUrl("/login");

		http.logout()
			.logoutUrl("/customLogout")
			.invalidateHttpSession(true)
			.deleteCookies("remember-me","JSESSION_ID");
		
		http.rememberMe()
			.key("sbms")
			.tokenRepository(persistentTokenRepository())
			.tokenValiditySeconds(604800);
		
		// UTF-8 캐릭터 인코딩 처리
		CharacterEncodingFilter filter = new CharacterEncodingFilter();
		filter.setEncoding("UTF-8");
		filter.setForceEncoding(true);
		http.addFilterBefore(filter, CsrfFilter.class);
		
	}

	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}

	@Bean
	public PersistentTokenRepository persistentTokenRepository() {
		JdbcTokenRepositoryImpl repo = new JdbcTokenRepositoryImpl();
		repo.setDataSource(dataSource);
		return repo;
	}

}

 


src/main/java

com.spms.config

SecurityInitializer.java

 

더보기
package com.sbms.config;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
	
}

 


src/main/java

com.spms.config

WebConfig.java

 

더보기
package com.sbms.config;

import javax.servlet.Filter;

import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class WebConfig extends AbstractAnnotationConfigDispatcherServletInitializer   {

	@Override
	protected Class<?>[] getRootConfigClasses() {
	    return new Class[] { RootConfig.class, SecurityConfig.class };
	}

	@Override
	protected Class<?>[] getServletConfigClasses() {
		return new Class[] { ServletConfig.class };
	}

	@Override
	protected String[] getServletMappings() {
		return new String[] { "/" };
	}

	@Override
	protected Filter[] getServletFilters() {
		CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
		characterEncodingFilter.setEncoding("UTF-8");
		characterEncodingFilter.setForceEncoding(true);
		return new Filter[] { characterEncodingFilter };
	}
	
}

 


src/main/java

com.sbms.controller

CommonController.java

 

더보기
package com.sbms.controller;

import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import lombok.extern.log4j.Log4j;

@Controller
@Log4j
public class CommonController {

	@GetMapping("/accessError")
	public void accessDenied(Authentication auth, Model model) {

		log.info("access Denied : " + auth);

		model.addAttribute("msg", "Access Denied");
	}

	@GetMapping("/customLogin")
	public void loginInput(String error, String logout, Model model) {

		log.info("error: " + error);
		log.info("logout: " + logout);

		if (error != null) {
			model.addAttribute("error", "Login Error Check Your Account");
		}

		if (logout != null) {
			model.addAttribute("logout", "Logout!!");
		}
	}

	@GetMapping("/customLogout")
	public void logoutGET() {

		log.info("custom logout");
	}

	@PostMapping("/customLogout")
	public void logoutPost() {

		log.info("post custom logout");
	}

}

 


src/main/java

com.sbms.controller

SampleController.java

 

더보기
package com.sbms.controller;

import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import lombok.extern.log4j.Log4j;

@Log4j
@RequestMapping("/sample/*") 
@Controller
public class SampleController {
  
  @GetMapping("/all")
  public void doAll() {
    
    log.info("do all can access everybody");
  }
  
  @GetMapping("/member")
  public void doMember() {
    
    log.info("logined member");
  }
  
  @GetMapping("/admin")
  public void doAdmin() {
    
    log.info("admin only");
  }  
  
  @PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MEMBER')")
  @GetMapping
  public void doMember2() {
	  log.info("logined annocation member");
  }
  
  @Secured({"ROLE_ADMIN"})
  @GetMapping("/annoAdmin")
  public void doAdmin2() {
	  log.info("admin annotation only");
  }
}

 


권한 테이블 생성

 

create table TBL_MEMBER_AUTH (
     USERID varchar2(50) not null,
     AUTH varchar2(50) not null,
     constraint FK_MEMBER_AUTH foreign key(USERID) references TBL_MEMBER(USERID)
);

 


테스트 데이터(회원/권한) 준비

 

1. testInsertMember() : 먼저 JUnit 실행

2. testInsertAuth() : 1번이 끝나면, JUnit 실행

 

src/test/java

com.spms.security

MemberTests.java

 

더보기
package com.spms.security;

import java.sql.Connection;
import java.sql.PreparedStatement;

import javax.sql.DataSource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.sbms.config.RootConfig;
import com.sbms.config.SecurityConfig;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { RootConfig.class, SecurityConfig.class })
@Log4j
public class MemberTests {

	@Setter(onMethod_ = @Autowired)
	private PasswordEncoder pwencoder;

	@Setter(onMethod_ = @Autowired)
	private DataSource ds;

	@Test
	public void testInsertMember() {

		String sql = "insert into tbl_member(userid, userpw, username) values (?,?,?)";

		for (int i = 0; i < 100; i++) {

			Connection con = null;
			PreparedStatement pstmt = null;

			try {
				con = ds.getConnection();
				pstmt = con.prepareStatement(sql);

				pstmt.setString(2, pwencoder.encode("pw" + i));

				if (i < 80) {

					pstmt.setString(1, "user" + i);
					pstmt.setString(3, "일반사용자" + i);

				} else if (i < 90) {

					pstmt.setString(1, "manager" + i);
					pstmt.setString(3, "운영자" + i);

				} else {

					pstmt.setString(1, "admin" + i);
					pstmt.setString(3, "관리자" + i);

				}

				pstmt.executeUpdate();

			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				if (pstmt != null) {
					try {
						pstmt.close();
					} catch (Exception e) {
					}
				}
				if (con != null) {
					try {
						con.close();
					} catch (Exception e) {
					}
				}

			}
		} // end for
	}

	//@Test
	public void testInsertAuth() {

		String sql = "insert into tbl_member_auth (userid, auth) values (?,?)";

		for (int i = 0; i < 100; i++) {

			Connection con = null;
			PreparedStatement pstmt = null;

			try {
				con = ds.getConnection();
				pstmt = con.prepareStatement(sql);

				if (i < 80) {

					pstmt.setString(1, "user" + i);
					pstmt.setString(2, "ROLE_USER");

				} else if (i < 90) {

					pstmt.setString(1, "manager" + i);
					pstmt.setString(2, "ROLE_MEMBER");

				} else {

					pstmt.setString(1, "admin" + i);
					pstmt.setString(2, "ROLE_ADMIN");

				}

				pstmt.executeUpdate();

			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				if (pstmt != null) {
					try {
						pstmt.close();
					} catch (Exception e) {
					}
				}
				if (con != null) {
					try {
						con.close();
					} catch (Exception e) {
					}
				}

			}
		} // end for
	}
}

 


views/includes

header.jsp

 

더보기
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<title>Simple Book Manager System</title>
<meta charset="UTF-8">
<link rel="stylesheet" href="/sbms/resources/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script type="text/javascript" src="/sbms/resources/js/book-info.js"></script>
<link href="/sbms/resources/vendor/font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css">
<link href="/sbms/resources/vendor/metisMenu/metisMenu.min.css" rel="stylesheet">
</head>
<body>
		<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
			<a class="navbar-brand" href="/sbms/book/list">SBMS</a>
			<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarColor01" aria-controls="navbarColor01" aria-expanded="false" aria-label="Toggle navigation">
				<span class="navbar-toggler-icon"></span>
			</button>
	
			<div class="collapse navbar-collapse" id="navbarColor01">
				<ul class="navbar-nav mr-auto">
					<li class="nav-item active"><a class="nav-link" href="#">Home
							<span class="sr-only">(current)</span>
					</a></li>
					<li class="nav-item"><a class="nav-link" href="/sbms/book/list">BookManagement</a></li>
					
                        <li class="nav-item">
                        	<a class="nav-link" href="/sbms/customLogout">Logout</a>
                        </li>
                        
                        <li class="nav-item">
                        	<a class="nav-link" href="/sbms/customLogin">Login</a>
                        </li>
					
					<li class="nav-item"><a class="nav-link" href="#">About</a></li>
				</ul>
			</div>
		</nav>

 


 

views/

customLogin.jsp

 

더보기
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<%@ include file="includes/header.jsp"%>	

<div style="margin : 200px 600px 100px 600px;">

						<form role="form" method='post' action="/sbms/login">
							<fieldset>
								<div class="form-group">
									<input class="form-control" placeholder="userid" name="username" type="text" autofocus>
								</div>
								<div class="form-group">
									<input class="form-control" placeholder="Password" name="password" type="password" value="">
								</div>
								<div class="checkbox">
									<label>
										<input name="remember-me" type="checkbox">Remember Me
									</label>
								</div>
								<!-- Change this to a button or input when using this as a form -->
								<a href="index.html" class="btn btn-lg btn-success btn-block">Login</a>
								<a href="/customSignup" class="btn btn-lg btn-primary btn-block">Sign Up</a>
							</fieldset>
							<input type="hidden" name="${_csrf.parameterName}"
								value="${_csrf.token}" />
						</form>

</div>
	<!-- jQuery -->
	<script src="/sbms/resources/vendor/jquery/jquery.min.js"></script>

	<!-- Bootstrap Core JavaScript -->
	<script src="/sbms/resources/vendor/bootstrap/js/bootstrap.min.js"></script>

	<!-- Metis Menu Plugin JavaScript -->
	<script src="/sbms/resources/vendor/metisMenu/metisMenu.min.js"></script>

	<!-- Custom Theme JavaScript -->
	<script src="/sbms/resources/dist/js/sb-admin-2.js"></script>
  <script>
  
  
  
  
  $(".btn-success").on("click", function(e){
    
    e.preventDefault();
    $("form").submit();
    
  });
  
  </script>
  
<c:if test="${param.logout != null}">
      <script>
      $(document).ready(function(){
      	alert("로그아웃하였습니다.");
      });
      </script>
</c:if>  

</body>

</html>

 


로그인 화면 : http://localhost/sbms/customLogin

 

반응형