반응형

출처: http://syaku.tistory.com/278


개발환경

Mac OS X 10.9.4
JAVA 1.6
Apache Tomcat 7.x
Spring 3.1.1
Spring Tool Suite 3.5.1
Maven 2.5.1

스프링 시큐리티(Spring Security)는 스프링 서브 프로젝트 중 하나로 스프링 기반의 어플리케이션을 보호하기 위한 필수적인 프레임워크이다. 스프링을 사용하면서 자체적으로 세션을 이용한 인증방식을 구현한다면 바보같은 짓일 것이다. 스프링 시큐리티는 보안을 체계적으로 관리하며 개발한 스프링 어플리케이션들과 유연하게 연결된다. 그리고 오랜기간 다양한 피드백으로 개발되어 신뢰도가 높을 것이다.
스프링에 최적화된 스프링 시큐리티 보다 안정적인 프레임워크는 아마 없을 것이다. 무엇보다 스프링에서는 스프링 시큐리티를 표준으로 정의하고 있다.

공식 사이트 : http://projects.spring.io/spring-security

스프링 시큐리티의 기본 예제를 먼저 알아보도록하겠다. 실무에서 사용할 수 없겠지만 스프링 시큐리트가 어떻게 구현되는 지 알 수 있는 예제이다.
프로젝트를 생성하고 스프링 시큐리티 라이브러리를 설치한다. Maven 설정에 2개의 dependency 를 추가한다.

@소스 pom.xml

<!-- Spring Security -->
<dependency>
     <groupId>org.springframework.security</groupId>
     <artifactId>spring-security-web</artifactId>
     <version>${org.springframework-version}</version>
</dependency>
<dependency>
     <groupId>org.springframework.security</groupId>
     <artifactId>spring-security-config</artifactId>
     <version>${org.springframework-version}</version>
</dependency>

<!— CGLib —>
<dependency>
     <groupId>cglib</groupId>
     <artifactId>cglib</artifactId>
     <version>3.1</version>
     <type>jar</type>
     <scope>compile</scope>
</dependency>

스프링 시큐리티를 사용하기 위해 아래와 같이 환경설정을 추가한다.

@소스 web.xml

<context-param>
     <param-name>contextConfigLocation</param-name>
     <param-value>
     /WEB-INF/spring/root-context.xml,
     classpath*:com/syaku/config/security-context.xml
     </param-value>
</context-param>

<!-- spring security -->
<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>

<filter-name>는 springSecurityFilterChain 이름으로 해야한다. 스프링에서 의존하는 필터이기 때문이다.

다음은 스프링에 스프링 시큐리티 설정정보를 추가한다. /src/main/resources/com/syaku/config/security-context.xml 파일을 생성한다.

@소스 security-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
     xmlns="http://www.springframework.org/schema/security"
     xmlns:beans="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="
     http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/security
     http://www.springframework.org/schema/security/spring-security.xsd
     ">

     <http auto-config="true">
          <intercept-url pattern="/**" access="ROLE_USER" />
     </http>

     <authentication-manager>
          <authentication-provider>
               <user-service>
                    <user name="guest" password="guest" authorities="ROLE_USER"/>
               </user-service>
          </authentication-provider>
     </authentication-manager>

</beans:beans>

테스트를 위해 웹페이지에서 아래와 같이 접속을 하면 로그인 화면이 출력된다.

http://localhost:8080/security

security-context.xml 에서 설정한 계정과 암호를 입력하여 로그인한다. authentication-manager 태그를 확인하면 된다.

http 태그는 접근 권한설정하는 부분이고, authentication-manager 태그는 접근 권한을 부여하는 부분이다.
그래서 guest 계정으로 로그인 하면 ROLE_USER 라는 권한을 부여받게 된다.

스프링 시큐리티에는 기본적인 로그인 부분을 제공하고 있기때문에 기본 설정만으로 로그인 로직을 구현할 수 있다. 하지만 실무에 사용하긴 힘들고 스프링 서큐리티가 어떻게 작동하는 지 알 수 있는 참고용으로 사용할 수 있다.

만약 다양한 접근 권한을 구현하기를 원한다면 인터셉터를 더많이 추가하면 된다. 현재는 모든 애플리케이션에 ROLE_USER 권한이 있는 사용자만 접근할 수 있게 설정된 것이다.

관리자 계정을 추가하고 관리자 계정만 접근할 수 있는 애플리케이션을 만들어 테스트해보기로 한다.

@소스 security-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
     xmlns="http://www.springframework.org/schema/security"
     xmlns:beans="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="
     http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/security
     http://www.springframework.org/schema/security/spring-security.xsd
     ">

     <http auto-config="true">
          <intercept-url pattern="/" access="ROLE_USER" />
          <intercept-url pattern="/admin" access="ROLE_ADMIN" />
     </http>

     <authentication-manager>
          <authentication-provider>
               <user-service>
                    <user name="guest" password="guest" authorities="ROLE_USER"/>
                    <user name="admin" password="admin" authorities="ROLE_ADMIN"/>
               </user-service>
          </authentication-provider>
     </authentication-manager>

</beans:beans>

여기서 또 중요한 것이 access 의 첫문자 패턴이 ROLE_ 시작해야한다. 변경이 가능하나, 구지 그럴필요가 없기때문에 모든 권한은 ROLE_ 시작할 수 있게 구성하도록한다.

권한을 추가하였다면 admin 컨트롤러도 추가한다. HomeController.java 파일을 열어 아래의 메서드를 추가한다.

@소스 HomeController.java

@RequestMapping(value = "/admin", method = RequestMethod.GET)
public String admin() {
     return "admin";
}

그리고 컨트롤러의 뷰페이지 파일을 추가한다. src/main/webapp/WEB-INF/views/admin.jsp 소스 내용은 알아서 추가한다.
http://localhost:8080/admin 으로 접근하면 권한이 없다는 오류메세지가 출력된다.

근데 문제는 admin/ 접근하면 권한 체크를 하지 않고 접속이 가능해진다. 이럴때 패턴을 /admin/** 주면된다. 하지만 하위 폴더에는 다른 권한을 줘야할 경우가 생길수가 있다. 이럴때는 최상 권한을 제일 하위에 등록하고 그 위에 추가할 경로의 권한을 추가하면 된다.


<intercept-url pattern="/" access="ROLE_USER" />
<intercept-url pattern="/admin/test" access="ROLE_USER" />
<intercept-url pattern="/admin/**" access="ROLE_ADMIN" />

이처럼 접근 권한은 위에서 아래로 순차적으로 인터셉터가 처리하게 된다. 그리고 intercept-url 의 pattern 은 말 그대로 경로의 패턴 값을 입력하는 것이다. 경로 값으로 오해하지 않도록한다.

Spring EL 표현식

http 설정에서 use-expressions=true 한 경우 SpEL(Spring EL expressions) 를 사용할 수 있다. 기본 값은 false 이다.

스프링 표현 언어(SpEL) 에 대한 자세한 설명은 아래와 같다.

ExpressionDescription
hasRole([role])Returns true if the current principal has the specified role.
hasAnyRole([role1,role2])Returns true if the current principal has any of the supplied roles (given as a comma-separated list of strings)
principalAllows direct access to the principal object representing the current user
authenticationAllows direct access to the current Authentication object obtained from the SecurityContext
permitAllAlways evaluates to truedenyAllAlways evaluates to false
isAnonymous()Returns true if the current principal is an anonymous user
isRememberMe()Returns true if the current principal is a remember-me user
isAuthenticated()Returns true if the user is not anonymous
isFullyAuthenticated()Returns true if the user is not an anonymous or a remember-me user

출처 : 스프링 프레임워크 시큐리티 도움말 3.1.x

SpEL 방식이 아닌 경우 아래와 같은 설정할 수 있다.

AUTHORITYDESCRIPTION
IS_AUTHENTICATED_ANONYMOUSLY익명 사용자
IS_AUTHENTICATED_REMEMBEREDREMEMBERED 사용자
IS_AUTHENTICATED_FULLY인증된 사용자

출처 : 전자정부프레임워크 도움말

스프링 서큐리티 어노테이션

좀 더 유연하게 접근 권한을 설정하기 위해 어노테이션을 사용할 수 있다. 한 곳에서 권한을 설정한다면 어플리케이션이 추가될때마다 수정되어야 하는 문제가 발생한다. 여러 어플리케이션을 개발하는 프로젝트라면 각각의 어플리케이션에 맞게 개별적으로 설정하는 것이 효과적이다.
[참고] 스프링 서큐리티 어노테이션을 사용하기 위해 CGLib 가 필요하다.

기존에 생성했던 security-context.xml 에 http 태그를 <http auto-config="true" /> 수정한다.
security-context.xml 파일은 공통적인 보안에 대한 설정을 담당하는 파일이라고 생각하면 될 것 같다.
그리고 servlet-context.xml 파일을 열어 아래와 같이 수정한다.

@소스 servlet-context.xml

<?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"
     xmlns:security="http://www.springframework.org/schema/security"
     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
     http://www.springframework.org/schema/security
     http://www.springframework.org/schema/security/spring-security.xsd
     ">

     <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->

     <!-- Enables the Spring MVC @Controller programming model -->
     <annotation-driven />

     <security:global-method-security secured-annotations="enabled" />

     <!-- 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.syaku.security" />

</beans:beans>

기본 소스에서 security 스키마와 <security:global-method-security secured-annotations="enabled" /> 설정이 추가되었고, @Secured 어노테이션을 사용할 수 있다. 본 어노테이션에는 SpEL 표현식은 사용할 수 없다. 기본적인 룰과 생성한 룰만을 사용할 수 있고, 여러가지 룰을 적용하려면 배열을 사용하면 된다. @Secured({"ROLE_ADMIN","ROLE_USER"})

컨트롤러 파일을 열어 접근 권한 어노테이션을 추가하면 된다.

@소스 HomeController.java

package com.syaku.security;

import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * Handles requests for the application home page.
 */
@Controller
public class HomeController {

     private static final Logger logger = LoggerFactory.getLogger(HomeController.class);

     /**
      * Simply selects the home view to render by returning its name.
      */
     @Secured("ROLE_USER")
     @RequestMapping(value = "/", method = RequestMethod.GET)
     public String home(Locale locale, Model model) {
          logger.info("Welcome home! The client locale is {}.", locale);

          Date date = new Date();
          DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);

          String formattedDate = dateFormat.format(date);

          model.addAttribute("serverTime", formattedDate );

          return "home";
     }

     @Secured("ROLE_ADMIN")
     @RequestMapping(value = "/admin", method = RequestMethod.GET)
     public String admin() {
          return "admin";
     }

     @Secured("ROLE_USER")
     @RequestMapping(value = "/admin/test", method = RequestMethod.GET)
     public String admin_test() {
          return "admin";
     }

}

기존 소스에서 @Secured 가 각 메서드마다 추가되었다. 접근 권한에 대해서는 이전에 설명했던것 처럼 룰을 적용하면 된다.

만약 로그인도 했고 해당 메서드에 권한도 있는 데… 접근할 수 없는 곳이 있다면?
쉽게말해… 게시판을 예로들어 글에 대한 제어 권한은 작성한 사용자만이 글을 수정 및 삭제할 수 있게 해야한다면 @Secured 만으로 구현할 수 없다. @Secured 는 SpEL 표현식을 지원하지 않기 때문이다.

그래서 스프링 시큐리티에서 지원하는 새로운 어노테이션을 사용해야 한다.
어노테이션을 사용하기 위해 다음과 같이 설정해야 한다.

<security:global-method-security pre-post-annotations="enabled" />

@Pre* : 메서드 인자값에 접근할 수 있다.
@Post* : 메서드 반환값에 접근할 수 있다.

어노테이션설명
@PreFilter메서드 인자 값을 필터한다.
@PreAuthorize메서드 인자 값을 검증한다.
@PostFilter메서드 반환 값을 필터한다.
@PostAuthorize메서드 반환 값을 검증한다.

@PerAuthorize

간단한 예제로 계정을 비교하는 프로그램을 아래와 같이 만들어 본다.

Service 와 VO 두개의 클래스 파일을 생성한다.

@소스 UserVO.java

package com.syaku.security;

public class UserVO {
     private String user_name;
     private String password;

     public void setUser_name(String user_name) {
          this.user_name = user_name;
     }

     public String getUser_name() {
          return this.user_name;
     }

     public void setPassword(String password) {
          this.password = password;
     }

     public String getPassword() {
          return this.password;
     }
}

@소스 HomeService.java

package com.syaku.security;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;

@Service
public class HomeService {
     private static final Logger logger = LoggerFactory.getLogger(HomeService.class);

     @PreAuthorize("#userVO.user_name == authentication.name or hasRole(‘ROLE_ADMIN')")
     public void getUser(UserVO userVO) {
          // 테스트를 위한 로그 출력
          logger.info("getUser success");
     }
}

만약 디비로 구성한다면 @Service 계층에 CURD 메서드들을 구성하면 된다. 이제 컨트롤러에서 서비스 메서드를 호출하면 된다.
테스트를 위해 동적인 계정을 얻기 위해 파라메터 값을 사용해 비교하였다.

@소스 HomeController.java


@RequestMapping(value = "/", method = RequestMethod.GET)
public String home(Locale locale, Model model,@RequestParam(value="user", defaultValue="", required=true) String user) {
     logger.info("Welcome home! The client locale is {}.", locale);

     // 파라메터 값을 얻어 삽입
     UserVO userVO = new UserVO();
     userVO.setUser_name(user);

     // 서비스 호출
     homeService.getUser(userVO);

     Date date = new Date();
     DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);

     String formattedDate = dateFormat.format(date);

     model.addAttribute("serverTime", formattedDate );

     return "home";
}

http://localhost:8080/security/ 를 접속하여 테스트한다. 로그인은 guest 로 한다.
위 경로로 접속하면 익셉션이 발생한다. 비교대상인 계정의 파라메터가 없기 때문이다. 하지만 admin 계정으로 접속하면 정상적인 페이지가 출력된다.

http://localhost:8080/security/?user=guest 접속하면 @Service 계층의 테스트 로그가 출력되고 정상적인 화면이 출력된다. 로그인한 계정과 파라메터로 넘긴 계정이 일치하기 때문이다.

로그인 정보 얻기

3가지 방법이 있고, 조금식 다른 방식과 결과를 출력한다. 그에 맞게 사용하면 될 것 같다.

@소스 HomeController.java

@Secured({"ROLE_USER","ROLE_ADMIN"})
@RequestMapping(value = "/user", method = RequestMethod.GET)
public String user(Principal principal) {

     // 첫번째 방법
     Authentication auth = SecurityContextHolder.getContext().getAuthentication();
     logger.info(auth.toString());

     // 두번째 방법
     User user = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
     logger.info(user.toString());

     // 세번째 방법
     logger.info(principal.toString());

     return "home";
}

첫번째 결과

INFO : com.syaku.security.HomeController - org.springframework.security.authentication.UsernamePasswordAuthenticationToken@be01b6d0: Principal: org.springframework.security.core.userdetails.User@5e22dd8: Username: guest; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@fffe9938: RemoteIpAddress: 0:0:0:0:0:0:0:1%0; SessionId: 02543D109730FC954434CFFE9B3E4D5E; Granted Authorities: ROLE_USER

두번째 결과

INFO : com.syaku.security.HomeController - org.springframework.security.core.userdetails.User@5e22dd8: Username: guest; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER

세번째 결과

INFO : com.syaku.security.HomeController - org.springframework.security.authentication.UsernamePasswordAuthenticationToken@be01b6d0: Principal: org.springframework.security.core.userdetails.User@5e22dd8: Username: guest; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@fffe9938: RemoteIpAddress: 0:0:0:0:0:0:0:1%0; SessionId: 02543D109730FC954434CFFE9B3E4D5E; Granted Authorities: ROLE_USER

첫번째와 세번째 결과는 같다. 하지만 호출방식이 틀리니 참고한다.

참고 : http://www.mkyong.com/spring-security/get-current-logged-in-username-in-spring-security/

반응형
반응형

출처: http://springmvc.egloos.com/516241


로그아웃 커스터마이징

이제 로그아웃 페이지를 커스터마이징 해볼까 합니다. 만약 로그아웃 주소를 커스터마이징 하지 않는다면 기본적으로 "/j_spring_security_logout"이란 URL을 통해 사용자의 권한을 해제하는 작업을 시작할 수 있습니다. 그러나 이런 눈에 뻔히 보이는 URL을 이용하면 악의적인 공격자가 해당 어플리케이션의 보안이 스프링 시큐리티로 이루어진 것을 알아채고 쉽게 보안취약점을 찾아낼 지도 모릅니다.

<http>에 다음과 같은 요소를 추가함으로서 손쉽게 로그아웃 경로를 커스터마이징 할 수 있습니다.

<logout invalidate-session="true" logout-url="/unAuthentication" logout-success-url="/" />

invaldate-session : 세션을 모두 무효로 할 것인지를 사용자에게 묻습니다.
logout-url : 로그아웃 경로를 설정합니다.
logout-seccess-url : 로그아웃이 성공한 뒤에 이동한 경로를 설정합니다.

MySQL로 Authentication 구현하기

2장에서 말했다시피 서비스를 상용화 하기 위해선 Authentication(인증) 부분을 DB로 이전해야 합니다. 이게 무슨 말이냐면 해당 사용자의 권한(Authority)과, 세부정보(UserDetails)들을 모두 DB에 기록하고 스프링 시큐리티는 그 정보들을 쿼리를 이용해 가져오는 방식으로 바뀐다는 뜻입니다. 당연한 듯 해 보일 수도 있겠지만 이게 기본적인 인증과 권한부여에 대한 개념이 잡혀있지 않으면은 금새 삼천포로 빠지기 쉽상입니다.
더욱이 상용 서비스로 오픈하기 위해서는 인증 자료의 DB이전은 필수 중 하나이며 Authentication을 위한 정보들을 하나도 남기지 않고 말끔히 DB로 옮기기 때문에 이런 과정들이 조금 복잡하고 이해가 가지 않을 수도 있습니다. 일단 쿼리문에 익숙하지 않은 사용자들은 이 부분에서 많이 답답하실 수도 있으므로 MySQL과 쿼리문에 대해서는 따로 사이드 패널을 만들어 정보를 올리도록 하겠습니다.

CREATE TABLE users (
principle int auto_increment primary key,
id varchar(50) not null,
crc_id int not null,
password varchar(50) not null,
enabled boolean not null,
INDEX(crc_id)
);

먼저 위와 같이 사용자의 정보를 담고 있는 테이블을 만들어 봅시다. 더 필요한 정보가 있으시다면 ALTER를 이용해 추가하셔도 상관없지만 지금은 우선적으로 로그인과 권한의 DB이전을 목적으로 하므로 필요한 최소한의 정보만 기입하도록 합시다. 위와 같은 인덱스 구조는 필자가 주로 사용하는 방식이므로 마음에 안드신다면 문서를 모두 읽으신 뒤에 인덱스 설정을 바꾸셔도 상관 없습니다.

'principle' 은 사용자의 유일값을 담는 컬럼입니다. 1장에서 필자가 아이디를 principle이라고 부르기도 한다 했지만 정확히 말하자면 어떠한 값과도 일치하지 않는 사용자의 고유값을 나타내므로 위처럼 id와 분리하여 사용해도 괜찮습니다. principle은 동일한 컬럼 내에서 어떠한 값과도 일치해서는 안되므로 해당 컬럼에 primary key 속성을 부여하였고 별도의 유일값 인증 로직을 구현하지 않기 위해 auto_increment로 자동증가값을 삽입하였습니다. (만약 principle이 공개키가 아닌 보안키라면은 auto_increment로 구현해서는 안됩니다.)

crc_id는 id 컬럼을 crc32로 암호화한 데이터입니다. 왜 해당 컬럼을 crc32로 암호화하여 따로 저장하느냐면 바로 속도 때문인데요. 'id'같이 스트링 값이 들어가는 컬럼을 primary key나 index로 설정한다면 인덱싱 속도가 엄청나게 느려지게 됩니다. 왜 지대한 영향을 끼치게 되는지는 추후에 다른 문서를 통해 설명하도록 하고 여러분은 일단 varchar과 char를 인덱스로 설정하는 것은 가급적 피해야될 사항이라는 것만 기억하시면 됩니다. 

CRC32는 MD5나 SHA1보다 가벼운 데이터 암호화를 위해 만들어진 기법입니다. CRC32의 암호화 값은 모두 int 범위 안에 속하므로 주로 데이터 인덱싱을 위해 자주 사용되곤 합니다. 다만 주의할 것은 이 값을 유일값으로 설정하면은 안되는데, 그 이유는 CRC32는 확률적으로 동일한 암호화 값이 나올 가능성이 다른 암호화 기법에 비해 매우 높기 때문입니다.

enabled는 해당 유저를 활성화할 것인지 비활성화 할 것인지를 묻는 컬럼입니다. 이 컬럼은 필수컬럼이며 향후 해당 사용자의 기록을 지우지 않고서도 비활성화 할 수 있는 기능을 제공할 것입니다.


필자는 스프링 시큐리티를 공부하면서 바로 이 테이블을 작성하는 과정에서 너무 많은 의문이 들어 쉽사리 다음 과정으로 이동할 수가 없었습니다. 거의 몇일을 여기에 목매며 왜 꼭 이래야만 하냐는… 고민을 수없이 되뇌였습니다. 이유는 알고 싶었는데 어떻게 알아가야 하는지 방법은 도통 알 수가 없었죠. 그러다가 문득 든 생각이 스프링 시큐리티의 커맨드 오브젝트(테이블과 매핑되는 자바빈 클래스가)를 역추적하면 이유를 알 수 있겠다는 것이었습니다.

이와 관련해서 잘 이해가 가지 않으신다면 위의 링크된 문서를 읽어보시는 것도 좋습니다. 일단은 여기서 대략적으로 설명드리자면 자바 프로그래밍에서는 자바빈 클래스는 대개 테이블과 밀접한 관계를 맺곤 합니다. 그렇다면… 만약 스프링 시큐리티에서도 테이블의 정보를 담는 빈이 있고 우리가 이 빈을 참조할 수 있다면 왜 이런 테이블 구조를 구성해야 하는지 알 수 있지 않을까요?

이 생각은 그대로 통했습니다. 스프링 시큐리티에서는 테이블에 매핑되는 정보를 User란 자바빈 클래스를 가지고 있었고 테이블과 관계된 내용만 요약해서 보자면 아래의 코드를 통해 이해할 수 있었습니다.

public User(String username, String password, Collection<? extends GrantedAuthority> authorities) {
        this(username, password, true, true, true, true, authorities);
    }

public User(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {

        if (((username == null) || "".equals(username)) || (password == null)) {
            throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
        }

        this.username = username;
        this.password = password;
        this.enabled = enabled;
        this.accountNonExpired = accountNonExpired;
        this.credentialsNonExpired = credentialsNonExpired;
        this.accountNonLocked = accountNonLocked;
        this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
    }

User 클래스는 UserDetails란 인터페이스를 상속한 클래스이며 위와 같은 생성자를 갖고 있습니다. User 클래스는 오로지 생성자만을 통해 프로퍼티의 설정이 가능하므로 굳이 클래스 전체를 보지 않더라도 위의 생성자만으로 이해가 가능합니다. 여기서 enabled를 제외한 나머지 불리언 값들은 계정의 만료와 락 설정이며 기본적으로 true로 설정되고 있으므로 여기에서는 설명을 생략합니다.

차근차근 프로퍼티를 하나씩 살펴보자면 먼저 username은 테이블의 principle과 일치합니다. 이름이 달라 헷갈릴 수도 있지만 다른 값과 구별되는 유일한 값이라 생각하면 이해하기 쉬우실 겁니다. 물론 principle 컬럼 보다는 crc_id가 유일값으로 적당하지 않겠냐고 생각할 수도 있지만 crc32로 만든 암호화 코드는 데이터가 1만개 이상일 때 약 1%의 확률로 서로 일치하는 값이 생기므로 유일값으로 적당하지 않습니다. (그렇기 때문에 unique index가 아닌 일반 index로 설정해둔 것입니다.)

password는 username의 principle을 확인하는 크리덴셜이며 테이블의 password와 동일합니다. 그 다음 Collection<? extends GrantedAuthority> authorities는 이 유저의 권한을 뜻하는데 좀 이상하게 authority(권한)의 복수형인 authorities(권한들)로 되어 있는데다 Collection 인터페이스 형태로 되어 있습니다. 눈치 채셨겠지만 그 이유는 한 사용자가 하나 이상의 권한을 가질 수 있게 설정할 수 있게 하기 위해서입니다.

근데 authorities를 보면서 조금 싸한 느낌과 함께 이상한 의문이 번뜩 미간을 스칩니다. 왜냐하면 우리가 설정한 테이블에서 authorities는 없었거든요! 스프링 시큐리티에서 users 테이블에 authorities를 함께 설정하지 없는 이유는 하나 이상의 권한을 설정하기 위해서이기도 하지만 또 하나의 이유는 확장성 때문입니다. 지금은 자세히 설명할 수 없지만 스프링 시큐리티에서는 사용자에게 직접 권한을 부여하는 방식과 그룹으로 묶어 권한을 부여하는 방식, 2가지를 제공합니다.

그리고 우리가 지금부터 할 것은 사용자에게 직접 권한을 부여하는 방식입니다. 다음과 같은 권한 테이블을 하나 더 만들어 보도록 합시다.

create table authorities (
principle int not null,
authority varchar(50) not null
unique index ix_auth_principle (principle, authority)
);

이 테이블은 users 테이블과 밀접한 관계를 맺고 있으며 authorities.principle과 users.principle은 서로 일치하므로 레퍼런스 키로 두 키를 묶어줄 수도 있지만 원할한 속도와 MySQL 기능에 너무 의존하지 않기 위해 이런 밀접한 관계는 트랜잭션으로 해결하는 것이 좋습니다.

이제 어느 정도 감을 잡으셨으리라 생각합니다. 글이 길어지는데다 제 지식이 바닥나가는 관계로 오늘은 여기까지 하고 다음 장에서 스프링 시큐리티의 JdbcDaoImpl을 MyBatisDaoImpl로 대체해보고 스프링 시큐리티의 확장 가능성에 대해 이야기 해보도록 하겠습니다.


반응형
반응형

출처: http://springmvc.egloos.com/504862


드디어 대망의 스프링 시큐리티입니다! 이것이 정녕 막판 보스는 아니지만 현재까지의 개발환경에서 스프링 시큐리티만 어느 정도 가닥이 잡힌다면 웹서비스 제작에 필요한 대부분의 요소는 자리를 잡게 되는 셈입니다. 게다가 스프링 시큐리티는 매우 지능적이며 대부분 간단한 선언만으로 작동하므로 대량의 코드를 손쉽게 절약해줍니다.


그러므로 스프링 시큐리티를 이용하는 것만으로도 고철자물쇠에서 최첨단 10 중 보안장치를 설치하는 것과 동일한 효과를 얻을 수 있겠다 말하겠습니다.
스프링 시큐리티는 강력하면서도 쉽습니다. 게다가 단 몇십줄의 코드만으로도 대형 웹서비스사와 비슷한 수준의 보안을 유지할 수 있다는 장점이 있습니다. 물론 진짜로 비슷한 수준을 유지하려면 적정 수준의 튜닝이 필요하겠지만 그 튜닝의 기반으로 삼기에 스프링 시큐리티는 정말 최상 중 최상의 선택입니다.

한가지 단점은 아직 한국에서 스프링 시큐리티에 대한 활용이 미비한 상태인데다 제대로된 포럼글이나 최신버전에 맞는 설명이 많지 않다는 것입니다. (덕분에 저도 문서를 작성하기 위해 필요한 정보를 찾느라 정말 애먹었습니다. 영어 모르는게 정말 서럽더군요.) 스프링 시큐리티가 ACEGI란 이름으로 시작해, 세상에 나온지 벌써 10년 가까이 됬음에도 아직까지 큰 관심이 없다는 것은 한국이 아직도 웹서비스의 발전이 미미하거나 보안에 대해 크게 간과하면서 많은 관심이 두고있지 않다는 사실일지도 모릅니다.

앞으로 이 포스트는 최소 3부 이상의 기나긴 마라톤 포스트가 될 예정이며 단순히 스프링 시큐리티를 활용하는데 그치지 않고 보안의 기본상식이나 개념에 대해 알아가는 시간이 되도록 할 예정입니다. 

그리고 이건 개인적인 이야기지만 시중에 나온 스프링 시큐리티3란 서적이 있긴 있는데 한글을 읽으면서도 외계어 읽는 듯한 기분을 느끼길 좋아하신다면 구매하셔도 상관은 없습니다… 그런 이유가 아니시라면 개인적으로 구매는 권장하지 않습니다. (현재까지 프로그래밍 서적에서 비추하는 책은 iBATIS 인 액션과 더불어 바로 이 책입니다. iBATIS 인 액션은 개발진이 직접 쓴 책이지만 솔직히 책이 필요없을 정도로 라이브러리가 너무 쉽습니다...;;)


보안이란?
위의 두 단어는 스프링 시큐리티 뿐만이 아니라 일반 보안에서도 핵심 축입니다. 개인적으로 너무나 중요하다 생각되어 하단 한글명에 욕심을 부려보았습니다. 게다가 많은 사람들이 Authentication과 Authorization의 이름이 비슷해 "명사형, 동사형 정도의 차이겠지"라며 착각하실 때가 종종 있는데 두 단어의 뜻은 전혀 다릅니다. 그러므로 글을 읽으시면서 비슷한 단어니까 비슷한 뜻이겠지… 라고 넘어가서는 절대 안됩니다.  한글 발음으로는 아우덴티케이션, 아우토리제이션입니다.

먼저 인증의 종류부터 알아봅시다. 인증에는 여러가지 종류가 있지만 보통 3가지로 분류하곤 합니다.

크리덴셜(Credential:자격) 기반 인증 : 우리가 웹에서 사용하는 대부분의인증 방식은 크리덴션 기반의 인증 방식입니다. 즉 권한을 부여받는데 1차례의 인증과정이 필요하며 대개 사용자명과 비밀번호를 입력받아 입력한 비밀번호가 저장된 비밀번호와 일치하는지 확인합니다. 일반적으로 스프링 시큐리티에서는 아이디를 프린시플(principle)비밀번호를 크리덴셜(credential)이라고 부르기도 합니다.
이중 인증(Two-factor authentication) : 한번에 2가지 방식으로 인증을 받는 것을 말합니다. 예를 들어 금융, 은행 웹어플리케이션을 이용해 온라인 거래를 하실 때에는 로그인과 보안 인증서, 2가지 방법으로 인증을 받곤 합니다. 별 것 아닌 것 같지만 Authentication이 하나 추가됨으로서 프로그래밍 적으로 변화해야 할 부분은 상당히 광범위해집니다.
물리적인 인증 : 이 부분은 웹의 영역을 벗어난 것이지만 가장 효과적인 보안 수단 중에 하나입니다. 예를 들어 컴퓨터를 킬 때 지문을 인식받는다거나 키를 삽입해야 하는 것들 말입니다. 

앞으로 우리가 스프링 시큐리티를 이용해 구현해나갈 인증(Authentication)은 눈치채셨겠지만 바로 크리덴셜(Credential) 인증입니다. 그리고 한가지 말씀드리고 싶은 것은 보안용어에 대해 잘 모르신다면 여기서 나오는 영어 단어들을 유심히 살펴보고 한번씩 써보는 것이 좋습니다.

왜냐하면 이런 용어들이 앞으로 스프링 시큐리티의 클래스, 또는 메서드 명으로 나오게 되며 보안용어에 익숙치 않은 독자는 앞으로 문서를 읽어 나가면서 굉장히 혼란스러울 수도 있기 때문입니다. 그러므로 이런 문제를 미리 방지하기 위해서라도 사전적 의미만으로 표현할 수 없는 보안용어의 참 의미를 미리 숙지할 수 있도록 주의깊게 읽으셔야 합니다.

이제 인증(Authentication)의 종류에 대해 어느 정도 이해가 되셨면 다음은 권한부여(Authorization) 차례입니다. 권한부여에는 크게 2가지로 나뉠 수 있습니다.

부여된 권한(Granted Autority) : 적절한 절차로 사용자가 인증되었다면 권한을 부여(Granted Authority)해야 할 것입니다. 회원가입 등을 통해 반영구적인 권한이 부여됬다면 우리는 이 회원에게 부여된 권한을 어딘가에 저장해야 하구요. 만약 해당 사용자가 로그인을 했는데 메인 페이지로 넘어갈 수 없다면 권한부여에 문제가 있다는 것이겠죠.
리소스의 권한(Intercept) : 사용자의 권한만 있다고 보안이 제대로 동작할리는 없습니다. 보안이란 본래 권한이 없는 자들이 원천적으로 리소스에 접근할 수 없도록 막아내는 것이기 때문입니다. 그런 의미에서 적절한 권한을 가진자만 해당 자원에 접근할 수 있도록 자원의 외부요청을 원천적으로 가로채는 것(Intercept)이 웹보안, 그 중 권한부여(Authorization)의 핵심 원칙이라 할 수 있겠습니다.

위의 보안 용어들은 중요한 개념이라고도 할 수 있지만 한편으로는 골치아픈 해결과제라고도 할 수 있습니다. 즉 우리가 위와 같은 주요 개념들에 입각해 구현해야 할 보안을 생각해본다면 일단 우선적으로 "어떤 방식으로 권한을 부여할까?""해당 리소스에 어떻게 권한수준을 부여하지?""인증받은 사람은 어떻게 인증받았다는 정보를 지속적으로 유지할 수 있을까?"와 같은 골치아픈 과제들이 산더미같이 존재할 수 있을 것입니다. 게다가 문제들을 해결하는 것도 힘든 일이지만 일을 더욱 더욱 어렵게 만드는 것은 보안이 아무리 잘해도 티 안나고 한번만 실수해도 독박은 다 뒤집어쓰는 전업주부 방식의 프로그래밍 분야이라는 것입니다.

그러므로 위와 같은 문제들의 올바른 해결을 위해서라도 스프링 시큐리티의 손을 한번 더 들어주고 싶어집니다. 스프링 시큐리티는쉽고 편리하다는 장점도 있지만 이런 보안 개념이 없는 자가 보안이 필요한 쇼핑몰이나 주요 웹사이트를 설계했다고 가정했을 때 발생할 막대한 피해들을 방어할 수 있는 최선의 선택입니다. 스프링 시큐리티는 이런 과제들을 거의 10년 가까이 연구하며 발전해온 뛰어난 보안 프레임워크라는 점을 볼 때 우리에게 스프링 시큐리티는 선택이 아니라 필수라고도 할 수 있습니다.

이제 보안의 핵심용어와 스프링 시큐리티의 중요성에 대해 어느 정도 설명했으니 스프링 시큐리티에 대해 좀 더 깊이 들어가 발생하는 문제에 어떤 해결방식을 선택했는지 차근차근 집어나가보려고 합니다.

리소스의 권한(Intercept)

리소스의 권한이 영어로 Intercept가 아니라는 것쯤은 저도 스타크래프트를 조금 해봤으므로 알고 있습니다. 그럼에도 Intercept라는 말을 리소스 권한 옆에 당당히 붙여놓는 것은 보안에서 리소스에 접근권한을 설정하는 것이 바로 Intercept 방식으로 작동하고 있기 때문입니다. 아무리 서버 성능이 좋고 직원이 많더라도 우리가 가지고 있는 모든 리소스에 일일이 권한을 설정할 수는 없는 노릇입니다. 대신에 우리가 @MVC에서 보았듯 DispatcherServlet처럼 멋지게 클라이언트의 요청을 가로챌 수만 있다면 간단히 문제를 해결할 수 있을 것입니다.

개인적으로 @MVC의 개발에 간접적으로나마 스프링 시큐리티가 많은 공헌을 하지 않았을까 생각하는데 그 이유는 스프링 시큐리티가 이미 ACEGI 때부터 Filter를 이용해 클라이언트의 요청을 가로채는 하는 방식을 굉장히 오래 전부터 개발해놨었기 때문입니다. (Spring Security는 스프링 커뮤니티에 합류하기 전까지 Acegi라는 이름의 프레임워크였습니다. 이름이 Acegi인 이유는 13579순서대로 알파벳을 정렬했다고 하는군요.) 그렇기 때문에 스프링 시큐리티는 @MVC의 DispatcherServlet이나 AOP를 이용해 프록시를 생성하지 않고 아주 오래 전부터 사용해온 고유의 DelegatingFilterProxy 클래스를 사용합니다. 물론 DelegatingFilterProxy를 이용하면서도 AOP 포인트컷의 활용 또한 가능합니다.

먼저 기존의 제작된 프로젝트에 다음과 같이 web.xml에 DelegatingFilterProxy를 등록해 봅시다.
<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>

여기서 주의할 점은 <filter-name>의 값이 반드시 springSecurityFilterChain이어야 한다는 점입니다. 왜 꼭 이름을 springSecurityFilterChain으로 지어야 하냐면 DelegatingFilterProxy 클래스는 setTargetBeanName(String)이라는 메서드를 갖고 있는데 이 메서드는 실제 요청을 처리할 필터를 주입받습니다. 만약 이 메서드를 통해 구현할 필터빈을 주입받지 못한다면 DelegatingFilterProxy 클래스는 기본값으로 <filter-name>의 값과 동일한 빈이 스프링 컨텍스트에 존재하는지를 검색하게 됩니다.

그러나 곧 알게 되겠지만 DelegatingFilterProxy 클래스가 springSecurityFilterChain이란 빈이 필요하다고 직접 빈을 만들어줄 필요는 없습니다. 왜냐하면 springSecurityFilterChain은 스프링 시큐리티의 inner bean이기 때문에 자동으로 생성되기 때문이죠.


이런 관점에서 본다면 DelegatingFilterProxy는 굳이 스프링 시큐리티 에서만 사용할 게 아니라 다른 목적의 필터 체인으로도 충분히 사용될 수 있는 확장성이 존재합니다. 아마 스프링 제작진들도 이 클래스가 스프링 시큐리티에서 탄생했지만 역할의 중요성을 인식해 시큐리티 내부 패키지에 위치시키지 않고 org.springframework.web.filter에 등록시킨 것 같습니다.

아마 이쯤되면 어떤 예리한 독자 분이 "DispatcherServlet이 이미 모든 요청을 가로채는데 또 DelegatingFilterChain이 가로채면 우선순위는 어떻게 결정되나요?" 라는 질문을 던지실 수도 있겠군요. 정답만 이야기하자면 필터가 우선순위가 됩니다. web.xml을 보면 알다시피 DispatcherServlet은 서블릿으로 등록되있고 DelegatingFilterChain은 필터로 등록되어 있습니다. 만약 이런 상황에 서로 겹치는 URL이 요청으로 들어온다면 필터는 프록시 패턴처럼 서블릿을 샌드위치 해버립니다. 이런 점에서 본다면 우리는 왜 DelegatingFilterChain이 필터로 만들어졌으며 DispatcherServlet이 왜 서블릿으로 만들어졌는지 이해할 수도 있겠죠.

이제 필터를 이용해 모든 URL이 DelegatingFilterProxy를 통과하도록 설정했다면 컨텍스트를 설정할 차례입니다. 보안에 대해서 따로 관리해줄 컨텍스트 파일을 만드는 것이 좋으므로 security-context.xml이란 파일을 새로 만들도록 합시다. 그리고 완성된 빈은 web.xml에 등록하는 것도 잊지 말구요. 여기까지 완료하시면 스프링 시큐리티를 사용하기 위한 기본 세팅은 마무리 됩니다.

어떠셨나요? 글을 읽으시면서 어느 정도 보안에 대해 감이 오시나요? 만약 아무리 읽어도 감이 오지 않는다면 스스로 의문을 만들고 그 답을 찾아보도록 노력해보세요. 개인적으로 스프링 시큐리티 API문서를 찾아보시길 권장합니다 ^^; 그럼 글이 길어지는 관계로 이번 장에선 보안의 기본원리와 스프링 시큐리티의 기본 세팅까지 다루고 다음 장부터 본격적으로 security-context.xml을 이용한 보안설정을 다루도록 하겠습니다. 여기까지 읽어주셔서 감사하구요. 좋은 하루 되세요 :D


반응형
반응형

출처: http://lng1982.tistory.com/138


개인 프로젝트인 스프링 3.1 기반의 웹 애플리케이션에 시큐리티를 추가해 보기로 했다.

최종 구현 목표는 DB로 인증 및 URL 관리를 하는 것이며 일단 단계적으로 시큐리티가 어떤 기능을 지원하는지를 알아가기 위해서 오늘은 프로퍼티를 이용한 인증 및 권한 부여 방법에 대해서 설명하려고 한다.


먼저 스프링 시큐리티를 적용하기 위해서는 pom.xml에 라이브러리를 추가한다.

        <dependency>

            <groupId>org.springframework.security</groupId>

            <artifactId>spring-security-core</artifactId>

            <version>${spring.security.version}</version>

        </dependency>

        <dependency>

            <groupId>org.springframework.security</groupId>

            <artifactId>spring-security-config</artifactId>

            <version>${spring.security.version}</version>

        </dependency>

        <dependency>

            <groupId>org.springframework.security</groupId>

            <artifactId>spring-security-web</artifactId>

            <version>${spring.security.version}</version>

        </dependency>

        <dependency>

            <groupId>org.springframework.security</groupId>

            <artifactId>spring-security-taglibs</artifactId>

            <version>${spring.security.version}</version>

        </dependency>


그런 후 web.xml에 DelegatingFilterProxy 필터를 등록한다.

필터를 등록 한 후 url-pattern을 /* 로 설정하게 되면 모든 요청에 대한 처리를 시큐리티 필터가 받아 처리하겠다는 의미이다. 즉, 인증과 권한 부여를 통해 리소스 접근을 제어하겠다느 것이다.

그리고 한 가지 더 알아야 할 것은 스프링 시큐리티 필터를 등록하게 되면 DispatcherServlet보다 우선으로 적용되게 된다.

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



다음은 시큐리티 메타 설정 파일 등록 방법이다.

web.xml에 아래 /WEB-INF/security-context.xml 설정 추가

<listener>

<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>

/WEB-INF/application-context.xml

/WEB-INF/security-context.xml

</param-value>

</context-param>


시큐리티 메타 설정 파일

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

         xmlns:security="http://www.springframework.org/schema/security"

         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

         xsi:schemaLocation="http://www.springframework.org/schema/beans

           http://www.springframework.org/schema/beans/spring-beans-3.1.xsd

           http://www.springframework.org/schema/security

           http://www.springframework.org/schema/security/spring-security-3.1.xsd">

 

        

         <security:http pattern="/common/css/**" security="none" />

         <security:http pattern="/common/img/**" security="none" />

         <security:http pattern="/common/js/**" security="none" />

         

         <security:http auto-config="true">

                  <security:intercept-url pattern="/**"access="IS_AUTHENTICATED_ANONYMOUSLY" />

<security:intercept-url pattern="/admin/**" access="ROLE_ADMIN" />


                   <security:form-login login-page="/login.do"

                           login-processing-url="/j_spring_security_check.do"

                           default-target-url="/loginSuccess.do"

                           authentication-failure-url="/loginFailed.do" />

 

                  <security:logout logout-url="/j_spring_security_logout.do"

                           logout-success-url="/logout.do"

                           invalidate-session="true" />

         </security:http>

        

         <authentication-manager>

               <authentication-provider>

                       <user-service properties="classpath:conf/users-config.xml" />

               </authentication-provider>

        </authentication-manager>

 

</beans>

위의 설정 값 중 intercept-url 엘리먼트 정의를 보면 /admin/** 의 패턴이 access할 수 있는 권한은 ROLE_ADMIN으로 설정되어 있다.

/admin/** 패턴의 request URI로 들어오는 모든 요청에 대해서는 인증된 사용자가 ROLE_ADMIN 권한을 획득하고 있어야 리소스에 접근할 수 있다는 것이다.


users-config.xml

key에는 유저 아이디를 추가하고 엘리먼트 value 값에는 첫 번째 패스워드 그 이후로는 권한을 추가하면 된다. 권한은 여러개 추가 가능하며 콤마(,)로 구분하다.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">

<properties>

        <comment>users</comment>

        <entry key="admin">1111,ROLE_ADMIN,ROLE_USER</entry>

        <entry key="user">2222,ROLE_USER</entry>

</properties>


마지막으로 로그인 페이지

j_spring_security_check.do URL로 로그인 요청을 보낼 때 j_username, j_password에 아이디 패스워드 값을 담은 채로 서버에 요청을 보내야 시큐리티 인증 및 권한 획득 작업을 진행한다.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>

<%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt"%>

 

<html>

<head>

<title>Login Page</title>

<style>

.errorblock {

        color#ff0000;

        background-color#ffEEEE;

        border3px solid #ff0000;

        padding8px;

        margin16px;

}

</style>

</head>

<body onload="document.f.j_username.focus();">

        <h3>Login with Username and Password (Custom Page)</h3>

 

        <c:if test="${not empty error}">

               <div class="errorblock">

                       Your login attempt was not successful, try again.<br /> Caused : ${sessionScope["SPRING_SECURITY_LAST_EXCEPTION"].message}

               </div>

        </c:if>

 

        <form name="f" action="<c:url value="j_spring_security_check.do" />" method="POST">

 

               <table>

                       <tr>

                              <td>User:</td>

                              <td><input type="text" name="j_username" value=""></td>

                       </tr>

                       <tr>

                              <td>Password:</td>

                              <td><input type="password" name="j_password" /></td>

                       </tr>

                       <tr>

                              <td colspan="2"><input name="submit" type="submit" value="submit" /></td>

                       </tr>

                       <tr>

                              <td colspan="2"><input name="reset" type="reset" /></td>

                       </tr>

               </table>

 

        </form>

</body>

</html>


반응형

+ Recent posts