현실적으로 JDBC를 이용하는 방식을 공부해보자
스프링 시큐리티에서는 사용자를 확인하는 인증과 권한 등을 부여하는 인가 과정으로 나누었다.
인증과 권한에 대한 처리는 크게 보면 Authenitacation Manager를 통해서 이루어진다.
이때, 인증이나 권한 정보를 제공하는 존재가 필요하고, 다시 이를 위해서 UserDetailsService라는 인터페이스를 구현한
존재를 활용한다.
유저 상세서비스는 스프링 시큐리티 API내에 이미 CahingUserDeatilsService, InmemoryIserDetailsmanager, jdbcDaoImpl, jdbcUserDetailManager, LdapUserDetailsManager, LdapUserDetailsService와 같은
구현 클래스들을 제공하고 있다. 이전의 예제에서 security-context.xml에 문자열로 고정한 방식은
사실 InMemoryUserDatailsmanager를 이용한 것이다.
이번에는 기존에 DB가 존재하는 상황에서 MyBatis나 기타 프에임워크 없이 사용하는 방식을 알아보자
security-context.xml를 조금 변경해주자
기존의 <security:authentication-manager>
를 수정한다.
<security:authentication-manager>
<security:authentication-provider>
<security:jdbc-user-service
data-source-ref="dataSource" />
<security:jdbc-user-service
data-source-ref="dataSource"
users-by-username-query="select userid , userpw
, enabled from tbl_member where userid = ? "
authorities-by-username-query="select
userid, auth from tbl_member_auth where userid = ? " />
<security:password-encoder
ref="customPasswordEncoder" />
<security:password-encoder
ref="bcryptPasswordEncoder" />
</security:authentication-provider>
</security:authentication-manager>
jdbc-user-service는 기본적으로 datasource가 필요하므로 root-context.xml에 있는 설정을 추가한다.
JDBC를 이용하기 위한 TABLE 설정
jdbc를 이용해서 인증/권한을 체크하는 방식은 크게
1. 지정된 형식으로 테이블을 생성해서 사용하는 방식
2. 기존에 작성된 DB를 이용하는 방식
스프링 시큐리티에서 지정된 sql을 그대로 이용하고 싶다면 지정된 형식으로 테이블을 생성한다.
create table users(
username varchar2(50) not null primary key,
password varchar2(50) not null,
enabled char(1) default '1');
create table authorities (
username varchar2(50) not null,
authority varchar2(50) not null,
constraint fk_authorities_users foreign key(username) references users(username));
create unique index ix_auth_username on authorities (username,authority);
insert into users (username, password) values ('qwe','qwe');
insert into users (username, password) values ('asd','asd');
insert into users (username, password) values ('zxc','zxc');
insert into authorities (username, authority) values ('zxc','ROLE_USER');
insert into authorities (username, authority) values ('asd','ROLE_MANAGER');
insert into authorities (username, authority) values ('qwe','ROLE_MANAGER');
insert into authorities (username, authority) values ('qwe','ROLE_ADMIN');
commit;
security-context.xml의 <security:authentication-manager>내용은 아래와 같이 작성한다.
<security:authentication-provider>
<security:jdbc-user-service
data-source-ref="dataSource" />
</security:authentication-provider>
</security:authentication-manager>
PasswordEncoder문제해결
스프링 시큐리티 5부터는 기본적으로 PasswordEncoder를 지정해야하낟.
임시로 {noop}의 접두어를 이용했지만 DB의 경우에는 PasswordEncoder라는 것을 이용해야하한다.
PasswordEncoding을 처리하고 나면 사용자의 계정들을 입력할 때부터 인코딩 작업이 추가되어야 하기 때문에
이것저것 추가해야할것이 많다. 스프링 시큐리티의 PasswordEncoder는 인터페이스로 설계되어있고, 이미
여러 종류의 구현 클래스가 존재한다.
이제는 직접 암호화가 없느느 PasswordEncoder를 구현해서 사용한다.
security패키지에 CustomNoOpPasswordEncoder 클래스를 생성한다.
package org.study.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);
}
}
PasswordEncoder인터페이스에는 encode()와 matches()매서드가 존재한다.
시큐리티컨텍스트xml에 해당 클래스를 빈으로 등록한다.
<bean id="customPasswordEncoder"
class="org.study.security.CustomNoOpPasswordEncoder"></bean>
<security:authentication-manager>
<security:authentication-provider>
<security:jdbc-user-service
data-source-ref="dataSource" />
<security:password-encoder
ref="customPasswordEncoder" />
</security:authentication-provider>
</security:authentication-manager>
이제 톰캣을 실행해서 로그인을 확인해보면 정상적으로 로그인 처리가 JDBC를 이용해서 처리되는 것을 볼 수 있다.
기존의 테이블을 이용하는 경우
스프링 시큐리티가 기본적으로 이용하는 테이블 구졸르 그대로 생성해서 사용하는 방식도 나쁘지는 않지만, 기존의 회원 관련
DB가 구축되어 있었다면, 이를 사용하는 것은 오히려 더 복잡하게 느껴질 수 있다.
JDBC를 이용하고, 기존에 테이블이 있다면 약간의 지정된 결과를 반환하는 쿼리를 작성해 주는 작업으로도 처리가 가능하다.
<security:jdbc-user-service>태그에는 아래와 같은 속성들을 지정할 수 있다.
속성들 중에서 users-by-username-query속성과 authorities-by-user-name-query 속성에 적당한 쿼리문을 지정해 주면 jdbc를 이용하는 설정을 그대로 사용할 수 있다.
인증/권한을 위한 테이블 설계
일반적으로 사용하는 회원 관련 테이블, 권한 테이블을 설계해서 이를 활용할 수 있다.
이전과 달리 인코딩된 패스워드를 활용해서 좀 더 현실적인 예제를 작성하도록 한다.
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는 태생 자체가 패스워드를 저장하는 용도로 설계된 해시 함수로 , 특정 문자열을 암호화하고, 체크하는 쪽에서
암호화된 패스워드가 가능한 패스워드인지만 확인하고 , 다시 원문으로 되돌리지는 못한다.
BcryptPasswordEncoder는 이미 스프링 시큐리티의 API안에 포함되어있으므로, 이를 활용해서 security-context.xml 에 설정한다.
<bean id="bcryptPasswordEncoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
<security:authentication-manager>
<security:authentication-provider>
<security:jdbc-user-service
data-source-ref="dataSource" />
<security:password-encoder
ref="bcryptPasswordEncoder" />
</security:authentication-provider>
</security:authentication-manager>
bcrypt방식을 이용하는 PasswordEncoder는 이미 스프링 시큐리티에서 제공하므로, 이를 빈으로 추가하고
PasswordEncoder는 org.springframework.security.crypto.bcrypt.BcryptPasswordEncoder로 지정한다.
인코딩된 패스워드를 가지는 사용자 추가
실제 DB에 기록하는 회원 정보는 BcryptPasswordEncoder를 이용해서 암호환된 상태로 넣어주어야 한다.
그래서 test코드를 작성해서 처리한다.
클래스이름은 MemberTests로 지어준다 .
package org.study.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 lombok.Setter;
import lombok.extern.log4j.Log4j;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({
"file:src/main/webapp/WEB-INF/spring/root-context.xml",
"file:src/main/webapp/WEB-INF/spring/security-context.xml"
})
@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
}
MemberTests에는 PasswordEncoder와 DataSource를 주입해서 100명의 회원정보를 기록했다.
PasswordEncoder를 이용해서 암호화된 문자열을 추가하는 과정을 통하기 때문에 해당 코드를 실행하고나면
BcryptPasswordEncoder를 이용해서 암호화된 패스워드가 기록된 것을 확인 할 수 있따
생성된 사용자에 권한 추가하기
사용자 생성이 완료되었다면, tbl_member_auth테이블에 사용자의 권한에 대한 정보도 tbl_member_auth테이블에 추가하자
user00~user79까지는 user권한을, 80~89까지는 멤버권한을, 90~99까지는 admin권한을 주도록 하자
@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
}
해당 쿼리를 이용해서 DB에는 해당 권한이 모두 들어가었다.
쿼리를 이용하는 인증
테이블 구조를 이용하는 경우에는 인증을 하는데 필요한 쿼리(user-by-username-query)와 권한을 확인하는데 필요한 쿼리(authorities-by-username-query)를 이용해서 처리한다.
<security:authentication-manager>
<security:authentication-provider>
<security:jdbc-user-service
data-source-ref="dataSource"
users-by-username-query="select userid , userpw
, enabled from tbl_member where userid = ? "
authorities-by-username-query="select
userid, auth from tbl_member_auth where userid = ? " />
<security:password-encoder
ref="bcryptPasswordEncoder" />
</security:authentication-provider>
</security:authentication-manager>
이제 로그인을 해보자
샘플아이디 비밀번호는
admin90 / pw90으로 실행해보겠다.
로그인처리가 되었다.
'Spring공부 > 4-스프링시큐리티' 카테고리의 다른 글
스프링 시큐리티와 JSP (0) | 2021.10.19 |
---|---|
커스텀 UserDetailsService (0) | 2021.10.19 |
로그인과 로그아웃처리(2) (0) | 2021.10.18 |
로그인과 로그아웃 처리 (0) | 2021.10.18 |
Spring Web Security (0) | 2021.10.18 |
댓글