Spring Security를 이용한 login 이해
출처 : http://zest133.tistory.com/entry/Spring-Security%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-login1
Spring Security를 이용한 login(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의 정보 및 권한 정보를 저장한다.
여기까지 설정은 모두 끝났다. 다음 글에서 소스를 살펴보자.