'session'에 해당되는 글 1건
- 2015.12.30 :: Spring Security를 이용한 login(1) 1
Spring Security는 기존 mvc model2에서 일일히 login 관련 한 것들을 알아서 해준다. 예를들어 암호화(hash) 비교, session 체크(cookie 포함), 이중 로그인 체크등 이에 따른 예외 처리등을 관리해준다. 물론 더 많은 기능이 있겠지만, 여기서는 로그인 관련한 것들을 알아보자.
spring 4.x를 사용하였으며 derby db를 사용했으며 jpa를 사용하며 간단히 만든다.
먼저 pom 파일에 security관련 lib를 추가하자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <!-- Spring Security --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> <version>4.0.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>4.0.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>4.0.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> <version>4.0.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <version>4.0.3.RELEASE</version> </dependency> | cs |
그리고 web.xml에 security관련 context.xml과 security에 필요한 Filter를 추가한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/root-context.xml, /WEB-INF/spring/security-context.xml</param-value> </context-param> .... <!--생략 ----> ... <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> | cs |
그럼 이제 context.xml을 설정해보자. 먼저 jpa관련 설정을 먼저한다. jpa 관련 설정은 root_context.xml에 설정한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource"> <property name="driverClass" value="org.apache.derby.jdbc.ClientDriver"></property> <property name="username" value="user"></property> <property name="password" value="gnogun"></property> <property name="url" value="jdbc:derby://localhost:1527/txtest"></property> </bean> <jpa:repositories base-package="com.gno.sample.repository" entity-manager-factory-ref="entityManagerFactory"></jpa:repositories> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" /> </property> <property name="dataSource" ref="dataSource" /> <property name="jpaProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.DerbyDialect</prop> <prop key="hibernate.default_schema">txtest2</prop> <prop key="hibernate.connection.pool_size">1</prop> <prop key="hibernate.connection.shutdown">true</prop> <prop key="hibernate.show_sql">true</prop> <!-- SQL 출력 --> <prop key="hibernate.ddl_auto">auto</prop> <!-- <prop key="hibernate.hbm2ddl.auto">create</prop> --> </props> </property> <property name="packagesToScan" value="com.gno.sample.dto" /> </bean> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"></property> </bean> </beans> | cs |
jpa 설정은 찾아보기로 하고 여기에선 스킵한다.
servlet-context.xml 을 설정한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | <?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure --> <!-- Enables the Spring MVC @Controller programming model --> <annotation-driven /> <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory --> <resources mapping="/resources/**" location="/resources/" /> <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory --> <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <beans:property name="prefix" value="/WEB-INF/views/" /> <beans:property name="suffix" value=".jsp" /> </beans:bean> <context:component-scan base-package="com.gno.sample" /> <interceptors> <beans:bean class="com.gno.sample.security.CustomInterceptor" /> </interceptors> </beans:beans> | cs |
interceptors 라는 tag가 있는데 controller에서 오는 값을 aop처럼 관리 하지만 security에선 session에 관한 권한 및 session을 관리한다. 이후 소스 부분에 다시 설명하겠다.
이제 security-context.xml를 보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:security="http://www.springframework.org/schema/security" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config></context:annotation-config> <!-- user-expressions 는 intercept-url태그의 access가 true일 경우 정의된 함수 ( hasAnyRole, isAnonymous() 등등 ) 를 사용할 수 있게 해준다. --> <security:http auto-config='true' use-expressions="true"> <!-- spring 4.x때 추가된 옵션으로 ssl을 사용하지 않을 경우 csrf는 disalbed true로 해준다. --> <security:csrf disabled="true" /> <!-- autoconfig=false 면? filter도 --> <!-- <security:intercept-url pattern="/login" access="isAnonymous()" /> --> <!-- access 이름들은 prefix가 정해져 있음 (default값 ROLE_ ) 재정의 하는 방법은 찾아놨는데 이름을 뭘 붙일지 몰라서 그냥 default prefix 사용했음 --> <security:intercept-url pattern="/admin.do" access="hasAnyRole('ROLE_ADMIN')" /> <security:intercept-url pattern="/main.do" access="hasAnyRole('ROLE_ADMIN', 'ROLE_USER')" /> <security:intercept-url pattern="/user.do" access="hasAnyRole('ROLE_ADMIN', 'ROLE_USER')" /> <security:intercept-url pattern="/*" access="permitAll" /> <!-- access="hasAnyRole('ROLE_USER', 'ROLE_ADMIN')" --> <!-- <security:anonymous /> <security:x509/> <security:http-basic /> <security:session-management></security:session-management> <security:expression-handler ref=""/> <security:custom-filter ref=""/> <security:port-mappings></security:port-mappings> <security:request-cache ref=""/> <security:remember-me/> --> <!-- always-use-default-target='true' = 서버가 죽었다 살아났을때 기존 가려고 했던 페이지를 무시하고 무조건 handler에 정의된 페이지로 이동 --> <!--authentication-failure-handler-ref와 authentication-success-handler-ref를 사용하지 않을경우는 authentication-failure-url속성을 사용하여 리다이렉트를 해준다. --> <security:form-login login-page="/login.do" default-target-url="/main.do" authentication-success-handler-ref="loginSuccessHandler" authentication-failure-handler-ref="loginFailureHandler" always-use-default-target="true" login-processing-url="/loginProcess" username-parameter="username" password-parameter="password" /> <!-- authentication-failure-url="/login" login-processing-url="" password-parameter="" username-parameter="" --> <security:logout logout-url="/logout" invalidate-session="true" success-handler-ref="logoutSuccessHandler" /> <!-- delete-cookies="JSESSIONID,auth" logout-success-url="/login.do" /> delete-cookies="" logout-url="" invalidate-session="true" success-handler-ref="" --> </security:http> <security:authentication-manager> <!-- <security:authentication-provider ref="userProvider"> </security:authentication-provider> --> <security:authentication-provider ref="CustomAuthenticationProvider"> </security:authentication-provider> </security:authentication-manager> <!-- provider는 이미 form에서 id 및 pwd(암호화 된값)을 가져오고 db에서 가져온 값을 UserService를 통해 UserDetail을 저장을 하며 UserDetail은 인증정보(db에서 가져온 사용자 값) 과 권한정보를 가져와서 provider는 먼저 인증을 비교한후 true가 되면 권한(Grant Authority)을 부여한다. --> <bean id="CustomAuthenticationProvider" class="com.gno.sample.security.CustomAuthenticationProvider"> <property name="userDetailsService" ref="userService"></property> <property name="passwordEncoder" ref="passwordEncoder"></property> </bean> <!-- UserDeatilService(com.gno.sample.security.CustomUserDetailService) 클래스는 인증(authentication)에 사용할 UserDetails 객체를 생성하는 작업이고 , UserDetails는 db에서 id값으로 user의 정보 및 권한(authority)정보를 저장한다. 이상 스러운건 이미 암호화 값으로 변경이 되있다. --> <bean id="userService" class="com.gno.sample.security.CustomUserDetailService" /> <bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></bean> <bean id="loginSuccessHandler" class="com.gno.sample.security.LoginSuccessHandler"></bean> <bean id="loginFailureHandler" class="com.gno.sample.security.LoginFailureHandler"></bean> <bean id="logoutSuccessHandler" class="com.gno.sample.security.CustomLogoutSuccessHandler"></bean> </beans> | cs |
굉장히 많은거 같은데 한개씩 보도록 하자.
먼저 security:http 태그를 살펴보자. 속성으로 auto-config라는 넘이 있다
이넘이 true 일경우 filter는 defalut 값으로 되며 만약 false라면
<security:anonymous />
<security:x509/>
<security:http-basic />
<security:session-management></security:session-management>
<security:expression-handler ref=""/>
<security:custom-filter ref=""/>
<security:port-mappings></security:port-mappings>
<security:request-cache ref=""/>
<security:remember-me/>
위의 filter들을 셋팅해줘야 한다.
다음 속성으로 user-expressions 라는 속성은 만약 false라면 spring에서 제공하는 hasAnyRole,isAnonymous() 등 내부 함수를 사용하지 못한다. 하지만 보통은 사용하므로 true로 해준다.
csrf는 spring4.x에 추가된 태그로 ssl등을 사용하지 않을때는 disabled=true로 설정을 해준다.
다음은 interceptor-url 이다. 이 태그는 각 url별 권한( autority)를 부여해준다. 그리고 높은 권한일 경우 먼저 써주고 낮은 권한 일 경우 아래에 써주는 것을 권장 하고 있다. 위의 설정 파일을 보면 ADMIN 권한 먼저 그다음 ADMIN과 User 그다음은 permitAll 로 주고 있다. 만약 순서가 잘못되면 권한 문제로 페이지가 잘못 나올경우 있으니 주의 하자.
그리고 login과 logout은 각 handler가 존재한다. 먼저 login을 살펴보자.
속성 |
설명 |
login-page |
로그인 page |
default-target-url |
로그인 성공시 이동할 url 설정 |
authentication-success-handler-ref |
로그인 성공시에 대한 프로세스 정의 보통 권한이 많을 경우 이 핸들러에서 redirect로 설정하며, defalut-target-url은 사용하지 않는다. |
authentication-failure-handler-ref |
로그인 실패시에 대한 프로세스 정의 |
always-use-default-target |
WAS 서버가 죽었다 살아 났을때 기존 가려고 했던 페이지는 무시하고 무조건 핸들러에 정의된 페이지로 이동 |
login-processing-url |
로그인 처리에 대한 url 어떠한 controller 든지 이런것은 정의 되지 않지만 로그인 form 내에서 action url은 이 url로 정의 되야 하며 내부적으로 이 url로 로그인 processing이 진행된다. |
username-parameter, password-parameter |
만약 이 파라미터가 없다면 스프링에서 제공되는 j_username, j_password를 사용해야한다. |
logout은 invalidate-session의 경우 logout이 진행되면 session 정보를 설정값에 따라 삭제를 진행한다. true일경우 삭제.
암호화 방식은 bean으로 설정을 하며, 위의 설정 파일에는 id는 passwordEncoder이며 암호화 방식 bcrypt를 사용한다.
자 이제 마지막으로 3가지가 등장한다. AuthenticationProvider, UserService, UserDetail 이 존재한다.
provider는 이미 form에서 id 및 pwd(암호화된 값)과 db의 값을 비교한후 true이며 권한(Grant Autority)를 부여한다. 이때 비교를 하기위해 참조값으로 암호화 방식의 bean을 등록해야한다.
UserService는 인증(authentication)에 사용할 UserDetail 객체를 생성한다.
UserDeatil은 user의 정보 및 권한 정보를 저장한다.
여기까지 설정은 모두 끝났다. 다음 글에서 소스를 살펴보자.