[B -2-52] 스프링 웹 시큐리티를 이용한 로그인 처리 2

2019. 10. 10. 23:11Project B (SPMS)/Project B 파트6

반응형

인증 및 권한을 위한 테이블 설계

 

회원 테이블 생성

 

create table TBL_MEMBER(
      USERID varchar2(50) not null primary key,
      USERPW varchar2(100) not null,
      USERNAME varchar2(100) not null,
      REGDATE date default SYSDATE, 
      UPDATEDATE date default SYSDATE,
      ENABLED char(1) default '1'
);

권한 테이블 생성

 

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)
);

 


BCryptPasswordEncoder 클래스를 이용한 패스워드 보호

 

스프링 시큐리티에서 제공하는 BCryptPasswordEncoder 클래스를 이용해서 패스워드를 암호화해서 처리하도록 한다.

bcrypt는 태생 자체가 패스워드를 저장하는 용도로 설계된 해시함수로 특정 문자열을 암호화하고, 체크하는 쪽에서는 암호화된 패스워드가 가능한 패스워드인지만 확인하고 다시 원문으로 되돌리지는 못한다.

 

src/main/java

com.spms.config

SecurityConfig.java

더보기
package com.spms.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 com.spms.security.CustomLoginSuccessHandler;
import com.spms.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("spms")
			.tokenRepository(persistentTokenRepository())
			.tokenValiditySeconds(604800);
	}

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

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

}

 


인코딩된 패스워드를 갖는 사용자 추가

생성된 사용자에 권한 추가

 

src/test/java

com.spms.security

MemberTests.java

 

[실행 순서 주의 !]

1. testInsertMember 메소드를 먼저 JUnit 실시할 것 : (@Test)

2. TBL_MEMBER 테이블에 회원이 다 들어갔는지 확인 후

3. testInsertAuth 메소드를 JUnit 실시할 것 : (@Test)

4. TBL_MEMBER_AUTH 테이블에 회원에 대한 권한이 다 들어갔는지 확인

더보기
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.spms.config.RootConfig;
import com.spms.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
	}
}

MemberTests.java 를 JUnit으로 실행해서 대량의 회원들을 암호화된 패스워드로 데이터베이스에 저장한다.

 

테이블에 데이터가 잘 들어갔는지 확인

<!> 이후에 있을 파일 생성부터 할 것. EX) 회원 도메인 설계 등등.. 순서가 잠시 꼬임 ㅎ

 

select a.userid, a.userpw, a.username, a.regdate, b.auth
from TBL_MEMBER a, TBL_MEMBER_AUTH b
where a.userid = b.userid;

 


회원 도메인 설계

 

src/main/java

com.spms.domain

MemberVO.java

더보기
package com.spms.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.spms.domain

AuthVO.java

더보기
package com.spms.domain;

import lombok.Data;

@Data
public class AuthVO {

  private String userid;
  private String auth;
  
}

 


멤버 매퍼

 

src/main/java

com.spms.mapper

MemberMapper.java 인터페이스

 

더보기
package com.spms.mapper;


import com.spms.domain.MemberVO;

public interface MemberMapper {

	public MemberVO read(String userid);
}

 


 

src/main/resources

com

spms

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.spms.mapper.MemberMapper">
	<resultMap type="com.spms.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.spms.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>

</mapper>

 


멤버 매퍼 유닛테스트

 

src/test/java

com.spms.mapper

MemberMapperTests.java

 

더보기
package com.spms.mapper;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

import com.spms.config.RootConfig;
import com.spms.domain.MemberVO;

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

@RunWith(SpringRunner.class)
@ContextConfiguration(classes= {RootConfig.class})
@Log4j
public class MemberMapperTests {

  @Setter(onMethod_ = @Autowired)
  private MemberMapper mapper;
  
  @Test
  public void testRead() {
    MemberVO vo = mapper.read("admin90");
    log.info(vo);
    vo.getAuthList().forEach(authVO -> log.info(authVO));
  }
}

 


멤버 매퍼 유닛 테스트 결과

 

|--------|-------------------------------------------------------------|---------|---------|----------------------|----------------------|-----------|
|userid  |userpw                                                       |username |enabled  |regdate               |updatedate            |auth       |
|--------|-------------------------------------------------------------|---------|---------|----------------------|----------------------|-----------|
|admin90 |$2a$10$xiSjJ6uuoNcZTtCG8QMS2.WhC.xBdrserZi6mfnPMoUurpBsYjtO. |관리자90    |[unread] |2019-10-10 22:09:52.0 |2019-10-10 22:09:52.0 |ROLE_ADMIN |
|--------|-------------------------------------------------------------|---------|---------|----------------------|----------------------|-----------|

 


 

 

반응형