허락된 사용자에게만 공개되는 컨텐츠(정보 또는 기능)에 접근하기 위해 반드시 아이디와 암호를 입력하는 로그인 과정을 거치는데 이러한 과정이 인증(authentication)이다.
즉, 인증은 특정 사용자가 유효한 사용자인지를 판단하는 과정을 의미한다.
본 가이드에서는 인증을 위한 기본적인 환경 및 전자정부 개발프레임워크에서 사용된 인증 방법을 설명한다.
전자정부 개발프레임워크의 인증은 XML기반의 인증이 아닌 DB기반의 JDBC인증을 사용한다.
기본적인 인증 메커니즘은 인증 주체가 인증을 시도하는 초기에 오직 한 번만 인증 메커니즘이 사용되며 그 이후로는 인증 메커니즘이 정보를 필터에 유지하여 요구되는 요청을 필터 체인상의 다음 필터로 전달하기만 한다.
Spring Security 에서 제공하는 인증을 사용하기 위해서는 오직 하나의 AuthenticationManager 구현체만이 포함되어 있으며 AuthenticationProvider의 설정이 필요하다.
AuthenticationManager는 요청을 AuthenticationProvider 체인에 전달해야 할 임무가 있다.
AuthenticationProvider는 기본적으로 UserDetails 와 UserDetailsService 인터페이스를 이용한다.
UserDetailsService 인터페이스 내 loadUserByUsername 메소드의 리턴된 UserDetails은 사용자명(username), 패스워드(password), 허가권한(GrantedAuthority[]) 및 사용여부(enabled)와 같은 기초적인 인증 정보들을 제공한다.
이에 전자정부 개발프레임워크에서는 UserDetails 인터페이스를 확장하였으며 JDBC 기반으로 사용자 테이블로부터 사용자 정보를 VO형태로 사용할 수 있도록 하였다.
최소환경설정으로 사용자 인증을 설정하는 예제이다.
<http auto-config='true'> <intercept-url pattern="/**" access="ROLE_USER"/> </http>
인증요청을 처리하기 위해 authentication manager가 xml(또는 DB, LDAP 등)에 정의된 사용자 정보를 사용한다.
<authentication-provider> <user-service> <user name="jimi" password="jimispassword" authorities="ROLE_USER, ROLE_ADMIN" /> <user name="bob" password="bobspassword" authorities="ROLE_USER" /> </user-service> </authentication-provider>
JdbcDaoImpl은 SQL 문장을 조정할 수 있도록 해주는 두 개의 프로퍼티를 제공하며 좀더 커스터마이징이 필요할 경우 JdbcDaoImpl를 별도로 구현클래스를 사용할 수도 있다.
Server Security에는 AuthenticationProvider 인터페이스의 구현체인 DaoAuthenticationProvider 클래스가 포함되어 있으며 DB기반의 인증을 처리할 수 있다.
사용자명, 패스워드 및 권한 정보를 DB에서 얻어오기 위하여 UserDetailsService 를 이용한다.
<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider"> <property name="userDetailsService"><ref bean="inMemoryDaoImpl"/></property> <property name="saltSource"><ref bean="saltSource"/></property> <property name="passwordEncoder"><ref bean="passwordEncoder"/></property> </bean>
PasswordEncoder와 SaltSource는 선택사항이다. PasswordEncoder는 설정된 UserDetailsService로부터 반환된 UserDetails 객체에 들어있는 비밀번호의 인코딩과 디코딩을 제공한다.
SaltSource는 패스워드에 “양념(salt)“을 뿌릴 수 있도록 하는데, 이러한 salt는 인증 저장소에 들어있는 패스워드의 보안을 강화한다.
Spring Security에서 제공되는 PasswordEncoder 구현체로는 MD5, SHA, 클리어텍스트(cleartext) 인코딩들이 있다. Spring Security에는 두 개의 SaltSource 구현체가 제공되는데, 모든 비밀번호에 대하여 동일한 salt로 인코딩을 수행하는 SystemWideSaltSource와, 반환된 UserDetails 객체의 프로퍼티를 검사하여 salt를 획득하는 ReflectionSaltSource가 있다. 선택적인 기능들에 관해 자세히 알아보려면 JavaDoc을 참조하길 바란다.
HTTP 폼 인증은 AuthenticationProcessingFilter를 이용하여 로그인 폼을 처리하는 것을 수반한다.
HTTP 폼 인증은 어플리케이션에서 가장 널리 사용되는 최종 사용자에 대한 인증 방법이다.
로그인 폼은 단순히 j_username과 j_password 입력 필드를 포함하며, 필터에 의해 모니터링되고 있는 URL로 게시한다.기본값은 j_spring_security_check 이다.
BASIC 인증
다이제스트 인증
익명 인증
Remember-Me 인증
X509 인증
LDAP 인증
CAS 인증
컨테이너 어댑터 인증
<http access-denied-page="/system/accessDenied.do" path-type="regex" lowercase-comparisons="false"> <form-login login-processing-url="/j_spring_security_check" authentication-failure-url="/cvpl/EgovCvplLogin.do?login_error=1" default-target-url="/index.jsp?flag=L" login-page="/cvpl/EgovCvplLogin.do" /> <anonymous/> <logout logout-success-url="/cvpl/EgovCvplLogin.do"/> </http>
<authentication-provider user-service-ref="jdbcUserService"> <password-encoder hash="md5" base64="true"/> </authentication-provider>
사용자 정보를 얻어오는 user service를 DB에 저장된 사용자 정보를 이용하여 인증할 수 있다.
<jdbc-user-service id="jdbcUserService" data-source-ref="dataSource" users-by-username-query="SELECT USER_ID,PASSWORD,ENABLED,BIRTH_DAY FROM USERS WHERE USER_ID = ?" authorities-by-username-query="SELECT USER_ID,AUTHORITY FROM AUTHORITIES WHERE USER_ID = ?"/>
<form action="<s:url value='/j_spring_security_check'/>" method="POST"> <table> <tr><td>User:</td><td><input type='text' name='j_username'></td></tr> <tr><td>Password:</td><td><input type='password' name='j_password'></td></tr> <tr><td colspan='2' align="center"><input name="submit" type="submit" value="로그인"> <input name="reset" type="reset" value="취소"></td></tr> </table> </form>
Security Service의 세션 기능은 Spring Security의 JdbcUserDetailsManager 인터페이스를 확장하여 EgovJdbcUserDetailsManager 클래스를 구현하였으며
기본 테이블에 기재된 username, password, enabled 필드 외에 다른 사용자 정보를 추가하여 세션정보를 관리할 수 있다.
세션 기능을 사용하기 위해서는 JDBC 인증의 환경설정 부분을 전자정부 개발프레임워크 Server Security의 환경으로 수정해야한다.
변경전(사용안함)
<jdbc-user-service id="jdbcUserService" data-source-ref="dataSource" users-by-username-query="SELECT USER_ID,PASSWORD,ENABLED FROM USERS WHERE USER_ID = ?" authorities-by-username-query="SELECT USER_ID,AUTHORITY FROM AUTHORITIES WHERE USER_ID = ?"/>
변경후(사용)
<b:bean id="jdbcUserService" class="egovframework.rte.fdl.security.userdetails.jdbc.EgovJdbcUserDetailsManager" > <b:property name="usersByUsernameQuery" value="SELECT USER_ID,PASSWORD,ENABLED,USER_NAME,BIRTH_DAY,SSN FROM USERS WHERE USER_ID = ?"/> <b:property name="authoritiesByUsernameQuery" value="SELECT USER_ID,AUTHORITY FROM AUTHORITIES WHERE USER_ID = ?"/> <b:property name="roleHierarchy" ref="roleHierarchy"/> <b:property name="dataSource" ref="dataSource"/> <b:property name="mapClass" value="egovframework.rte.fdl.security.userdetails.EgovUserDetailsMapping"/> </b:bean>
public class EgovUserDetailsVO { private String userId; private String passWord; private String userName; private String ssn; private String lsYn; private String birthDay; private Integer age; private String cellPhone; private String addr; private String email; public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getPassWord() { return passWord; } public void setPassWord(String passWord) { this.passWord = passWord; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getSsn() { return ssn; } public void setSsn(String ssn) { this.ssn = ssn; } . . .
public class EgovUserDetailsMapping extends EgovUsersByUsernameMapping { public EgovUserDetailsMapping(DataSource ds, String usersByUsernameQuery) { super(ds, usersByUsernameQuery); } @Override protected Object mapRow(ResultSet rs, int rownum) throws SQLException { String userid = rs.getString("user_id"); String password = rs.getString("password"); boolean enabled = rs.getBoolean("enabled"); String username = rs.getString("user_name"); String birthDay = rs.getString("birth_day"); String ssn = rs.getString("ssn"); EgovUserDetailsVO userVO = new EgovUserDetailsVO(); userVO.setUserId(userid); userVO.setPassWord(password); userVO.setUserName(username); userVO.setBirthDay(birthDay); userVO.setSsn(ssn); return new EgovUserDetails(userid, password, enabled, userVO); } }
import egovframework.rte.fdl.security.userdetails.util.EgovUserDetailsHelper; . . . EgovUserDetailsVO user = (EgovUserDetailsVO)EgovUserDetailsHelper.getAuthenticatedUser(); assertEquals("jimi", user.getUserId()); assertEquals("jimi test", user.getUserName()); assertEquals("19800604", user.getBirthDay()); assertEquals("1234567890123", user.getSsn());
Boolean isAuthenticated = EgovUserDetailsHelper.isAuthenticated(); assertFalse(isAuthenticated.booleanValue());
또는
assertNull(EgovUserDetailsHelper.getAuthenticatedUser());
<http access-denied-page="/system/accessDenied.do" path-type="regex" lowercase-comparisons="false"> <form-login login-processing-url="/j_spring_security_check" authentication-failure-url="/cvpl/egovCvplLogin.do?login_error=1" default-target-url="/index.jsp" login-page="/cvpl/egovCvplLogin.do" /> <anonymous/> <logout logout-success-url="/index.jsp"/> <concurrent-session-control max-sessions="1" exception-if-maximum-exceeded="false" expired-url="/EgovCvplLogout.jsp" /> </http>
concurrent-session-control