본문 바로가기

JAVA/Spring Framework

[Spring Framework] Security 설정 및 원리

스프링 시큐리티


스프링 시큐리티는 필터체인으로 인증과 권한을 확인한다.

따라서 webApplicationContext에 설정을 할 경우 모든 bean이 생성되지 않은 상태에서 로딩이 되므로 에러가 발생한다. rootApplicationContext에 설정하여 우선적으로 bean이 만들어질 수 있도록 해야 한다.

설정방법은 다음과 같다.

파일명 : pom.xml

<!-- Spring Security -->
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-core</artifactId>
        <version>4.1.3.RELEASE<<!-- Spring Security -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
            <version>4.2.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>4.2.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-taglibs</artifactId>
            <version>4.2.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-acl</artifactId>
            <version>4.2.0.RELEASE</version>
        </dependency>/version>
    </dependency>

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>4.1.3.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-taglibs</artifactId>
    <version>4.1.3.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-acl</artifactId>
    <version>4.1.3.RELEASE</version>
</dependency>

파일명 : web.xml

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

config/app/SecurityConfig.java 생성

package com.cafe24.config.app;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.web.filter.DelegatingFilterProxy;

/*
 Security Filter Chain 
 1. ChannelProcessingFilter
 2. SecurityContextPersistenceFilter        ( auto-config default ) 필수
 3. ConcurrentSessionFilter
 4. LogoutFilter                            ( auto-config default ) 필수
 5. UsernamePasswordAuthenticationFilter    ( auto-config default ) 필수
 6. DefaultLoginPageGeneratingFilter        ( auto-config default )
 7. CasAuthenticationFilter
 8. BasicAuthenticationFilter                ( auto-config default ) 필수 
 9. RequestCacheAwareFilter                    ( auto-config default )
10. SecurityContextHolderAwareRequestFilter    ( auto-config default )
11. JaasApiIntegrationFilter
12. RememberMeAuthenticationFilter
13. AnonymousAuthenticationFilter            ( auto-config default )
14. SessionManagementFilter                    ( auto-config default )
15. ExceptionTranslationFilter                ( auto-config default ) 필수
16. FilterSecurityInterceptor                ( auto-config default )     필수
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    // Spring security filter connection 
    // WebSecurity object create DelegatingFilterProxy Bean object named of springSecurityFilterChain
    // DelegatingFilterProxy Bean delegate to the many Spring Security filters  
    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
        // 예외 웹접근 url 설정한다. configure exception web approach
        // ACL(접근제외에서 예외 URL를 설정
    }
//    @Bean(name = "springSecurityFilterChain")
//    public void FilterChainProxy() {
//    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // TODO Auto-generated method stub
        super.configure(http);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // TODO Auto-generated method stub
        super.configure(auth);
    }
}

{domain}.config 에서 SecurityConfig.class import 작업

package com.cafe24.mysite.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import;

import com.cafe24.config.app.DBConfig;
import com.cafe24.config.app.MyBatisConfig;
import com.cafe24.config.app.SecurityConfig;

@Configuration
@EnableAspectJAutoProxy
@ComponentScan({"com.cafe24.mysite.service", "com.cafe24.mysite.repository", "com.cafe24.mysite.aspect"})
@Import({DBConfig.class, MyBatisConfig.class, SecurityConfig.class})
public class AppConfig {
}

시큐리티가 동작하는 원리는 다음과 같다.

  1. 유저가 로그인을 시도 (http request)
  2. AuthenticationFilter 에서부터 위와같이 user DB까지 타고 들어감
  3. DB에 있는 유저라면 UserDetails 로 꺼내서 유저의 session 생성
  4. spring security의 인메모리 세션저장소인 SecurityContextHolder 에 저장
  5. 유저에게 session ID와 함께 응답을 내려줌
  6. 이후 요청에서는 요청쿠키에서 JSESSIONID를 까봐서 검증 후 유효하면 Authentication를 쥐어준다

스프링 동작원리 링크 출처

접근 제어를 컨트롤하여 URL에 따라 Security 필터에 걸리지 않도록 예외 설정하기

파일명 : SecurityConfig.java

// Spring security filter connection 
    // WebSecurity object create DelegatingFilterProxy Bean object named of springSecurityFilterChain
    // DelegatingFilterProxy Bean delegate to the many Spring Security filters  
    @Override
    public void configure(WebSecurity web) throws Exception {
//        super.configure(web);
        // 예외 웹접근 url 설정한다. configure exception web approach
        // ACL(Access Control List)에서 예외 URL를 설정
        web.ignoring().antMatchers("/assets/**");
        web.ignoring().antMatchers("/favicon.ico");
//        web.ignoring().regexMatchers("\\A/assets/.*\\Z");
//        web.ignoring().regexMatchers("\\^/favicon.ico\\Z");
    }

URL에 따라 권한이 있는 지 확인(보안)

// Interceptor URL의 요청을 안전하게 보호(보안)하는 방법을 설정(ACL 작성)
    /*
     deny all
     /user/update    -> (ROLE_USER, ROLE_ADMIN) -> Authenticated
     /user/logout    -> (ROLE_USER, ROLE_ADMIN) -> Authenticated
     /board/write    -> (ROLE_USER, ROLE_ADMIN) -> Authenticated
     /board/delete   -> (ROLE_USER, ROLE_ADMIN) -> Authenticated
     /board/modify   -> (ROLE_USER, ROLE_ADMIN) -> Authenticated
     /admin/**       -> ROLE_ADMIN(Authorized)
     allow all
    */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //
        // 1. ACL 설정
        //
        http
        .authorizeRequests()

        // 인증이 되었있을 때(authenticated?)
        .antMatchers("/user/update", "/user/logout").authenticated()
        .antMatchers("/board/write", "/board/delete", "/board/modify").authenticated()

        // ADMIN Authority(ADMIN 권한, ROLE_ADMIN)
        //.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
        //.antMatchers("/admin/**").hasRole("ADMIN")
        .antMatchers("/admin/**").hasAuthority("ROLE_ADMIN")

        // 모두 허용
        //.antMatchers("/**").permitAll()
        .anyRequest().permitAll();

        // Temporary for Testing
        http.csrf().disable();

        //
        // 2. 로그인 설정
        //
        http
        .formLogin()
        .loginPage("/user/login")
        .loginProcessingUrl("/user/auth")
        .failureUrl("/user/login?result=fail")
        .defaultSuccessUrl("/", true)
        .usernameParameter("email")
        .passwordParameter("password");


        //
        // 3. 로그아웃  설정
        //
        http
        .logout()
        .logoutRequestMatcher(new AntPathRequestMatcher("/user/logout"))
        .logoutSuccessUrl("/")
        .invalidateHttpSession(true);
    }

LOGIN 페이지에서 정보를 보내기 (UserDetailService 구현)

파일명 : {domain}/security/SecurityUser.java

package com.cafe24.mysite.security;

import java.util.Collection;
import java.util.List;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

public class SecurityUser implements UserDetails {
    private static final long serialVersionUID = 1L;

    private Collection<? extends GrantedAuthority> authorities;
    private String username; // principal(biz name: email)
    private String password; // credential

    // etc
    private String name; // biz data

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
        this.authorities = authorities;
    }

    public void setUsername(String username) {
        this.username = username;
    }

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

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

}

파일명 : {domain}/security/UserDetailsServiceImpl.java

package com.cafe24.mysite.security;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import com.cafe24.mysite.repository.UserDao;
import com.cafe24.mysite.vo.UserVo;

@Component
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private UserDao userDao;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserVo userVo = userDao.get(username);
        SecurityUser securityUser = new SecurityUser();

        if( userVo != null ) {
            // mock data
            //String role = userVo.getRole();
            String role = "ROLE_USER";

            securityUser.setName(userVo.getName());         // biz data  
            securityUser.setUsername(userVo.getEmail());    // principal
            securityUser.setPassword(userVo.getPassword()); // credential

            List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
            authorities.add(new SimpleGrantedAuthority(role));
            securityUser.setAuthorities(authorities);    
        }

        return securityUser;
    }
}

파일명 : {domain}/config.app/SecurityConfig.java

// UserDetailsService를 설정
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

UserDetail(Interface)로 구현된 loadUserByUsername(String name)을 통해서 UserDetail에 String name을 주입할 수 있게 되었다. UserDetailsSerivce를 위한 Interface 구현체를 만들고 최종적으로 UserDetail에 String name및 UserDetail의 내용이 오버라이드 되어 되돌려질 수 있다면 우리는 Security에서 UserDetail에 대한 정보를 컨트롤 할 수 있으며 동일한 인터페이스로 구현된 Spring security의 타 기능들을 사용할 수 있게 되는 것이다.

그 정보를 설정해주기 위하여 AuthenticationManagerBuilder에 해당하는 auth에 Bean정보를 주입하여 전달한다.