새소식

반응형
Java/Spring

Spring Security

  • -
반응형

Filter(Spring Security) 와 Interceptor의 차이

* Filter : Dispatcher Servlet 앞단에서 정보를 처리하고, Interceptor는 Dispatcher Servlet에서 Controller로 가기전 요청을 가로채서 정보를 처리한다.

- 기능적인 측면으론 완전히 다른 개념이다.

spring security (Dispatcher Servlet 구간으로 진입전 Filter 구간에서 처리)

: 인증,권한,보안 기능을 제공하는 Spring의 하위 프레임워크

: 스프링 기반의 어플리케이션 보안을 담당하는 프레임워크이다.

* 사용해야할 어노테이션

@Configuration

: Spring IOC 컨테이너(Bean을 관리하는 컨테이너)에게 해당 클래스는 Bean 관련 클래스임을 명시하여 알려준다.

@EnableWebSecurity

: SpringSecurity 활성화, Spring Security를 사용하기 위한 어노테이션

@EnableGlobalAuthentication

: 모든 빈에 보안을 설정하기 위한 어노테이션, 글로벌 설정

* Spring Security 아키텍쳐

Spring Security 인증 과정

1. 사용자가 아이디/비밀번호를 입력하여 로그인 요청한다.

2. AuthenticationFilter는 UsernamePasswordAuthenticationToken을 생성하여 AuthenticationManager에게 전달한다.

3. AuthenticationManager는 등록된 적절한 AuthenticationProvider 에게 인증을 요구한다.

4. AuthenticationProvider는 UserDetailsService의 loadUserByUsername 메소드를 통해 사용자가 요청한 로그인 아이디 

   (username)를 기반으로 DB에 저장된 사용자 정보를 가져와 UserDetails 객체로 변환한다.

5. AuthencationProvider의 authenticate 메소드를 통해 사용자가 입력한 패스워드와 DB에 저장된 패스워드를 비교하여

   같을 경우 인증이 성공된 UsernamePasswordAuthenticationToken(Authentication 객체)을 AuthenticationManager로

   반환한다.

6. AuthenticationManager는 성공한 Authentication 객체를 AuthenticationFilter로 전달한다.

7. AuthenticationFilter는 전달받은 Authentication 객체를 LoginSuccessHandler로 전송하고 SecurityContextHolder에

   저장한다.

 

* UsernamePasswordAuthenticationToken

사용자의 인증 요청을 Authentication 인터페이스로 추상화하고 AuthenticationManager를 호출한다.

 

Authentication 인터페이스에서 제공하는 핵심 메소드

Object getPrincipal(); - 인증 아이디
Object getCredentials(); - 인증 패스워드
Collection<? extends GrantedAuthority> getAuthorities(); - 인증된 사용자의 권한 목록
Object getDetails(); - 인증된 사용자의 상세 정보

 

* AuthenticationManager

사용자가 입력한 아이디/비밀번호를 인증하기 위해 적절한 AuthenticationProvider를 찾아 처리를 위임한다.

 

실제 Authentication을 만들고 인증을 처리하는 인터페이스는 AuthenticationManager이다.

AuthenticationManager는 아래 그림과 같이 authenticate 라는 메서드만을 가진다.

 

* AuthenticationProvider

AuthenticationManager에 의해 호출되며 실질적으로 사용자 인증을 처리하고 인증 결과를

Authentication 인터페이스로 반환한다.

 

* UserDetailsService

Spring Security에서 유저의 정보를 가져오는 인터페이스이다.

사용자가 입력한 username을 기반으로 저장된 유저정보를 가져와서 UserDetails 객체로 변환하여 돌려주는 메소드

유저의 정보를 불러오기 위해서 구현해야 하는 인터페이스로 기본 오버라이드 메소드는 아래와 같다.

 

* UserDetails

일반 서비스의 사용자 객체를 Spring Security 에서 사용하는 사용자 객체와 호환해주는 어뎁터

보통 Spring Security의 UserDetails는 사용자의 정보를 담는 인터페이스이다.

UserDetailsService 인터페이스에서 가져온 유저의 정보를 담는 인터페이스이다.

메소드 리턴 타입 설명 기본값
getAuthorities() Collection<? extends GrantedAuthority> 계정의 권한 목록을 리턴  
getPassword() String 계정의 비밀번호를 리턴  
getUsername() String 계정의 고유한 값을 리턴
( ex : DB PK값, 중복이 없는 이메일 값 )
 
isAccountNonExpired() boolean 계정의 만료 여부 리턴 true ( 만료 안됨 )
isAccountNonLocked() boolean 계정의 잠김 여부 리턴 true ( 잠기지 않음 )
isCredentialsNonExpired() boolean 비밀번호 만료 여부 리턴 true ( 만료 안됨 )
isEnabled() boolean 계정의 활성화 여부 리턴 true ( 활성화 됨 )

 

 

* SecurityContextHolder

Authentication 객체를 보관하는곳, 어플리케이션 어디에서든지 접근할 수 있다.

 

​* Authentication 객체

Spring Security에서 한 유저의 인증 정보를 가지고 있는 객체

사용자의 인증과정을 성공적으로 마치면 Spring Security는 사용자의 정보 및 인증 성공 여부를 가지고

Authentication 객체를 생성한 후 SecurityContext에 보관한다.

 

 

* Spring Security Filter들

1. SecurityContextPersistenceFilter : SecurityContextRepository에서 SecurityContext를 가져오거나 저장하는 역할을 한다. (SecurityContext는 밑에)

2. LogoutFilter : 설정된 로그아웃 URL로 오는 요청을 감시하며, 해당 유저를 로그아웃 처리

3. (UsernamePassword)AuthenticationFilter : (아이디와 비밀번호를 사용하는 form 기반 인증) 설정된 로그인 URL로 오는 요청을 감시하며, 유저 인증 처리

3-1. AuthenticationManager를 통한 인증 실행

3-2. 인증 성공 시, 얻은 Authentication 객체를 SecurityContext에 저장 후

AuthenticationSuccessHandler 실행

3-3. 인증 실패 시, AuthenticationFailureHandler 실행

4. DefaultLoginPageGeneratingFilter : 인증을 위한 로그인폼 URL을 감시한다.

5. BasicAuthenticationFilter : HTTP 기본 인증 헤더를 감시하여 처리한다.

6. RequestCacheAwareFilter : 로그인 성공 후, 원래 요청 정보를 재구성하기 위해 사용된다.

7-1. SecurityContextHolderAwareRequestFilter : HttpServletRequestWrapper를 상속한

7-2. SecurityContextHolderAwareRequestWapper 클래스로 HttpServletRequest 정보를 감싼다.

7-3. SecurityContextHolderAwareRequestWrapper 클래스는 필터 체인상의 다음 필터들에게 부가정보를 제공한다.

8. AnonymousAuthenticationFilter : 이 필터가 호출되는 시점까지 사용자 정보가 인증되지 않았다면 인증토큰에 사용자가 익명 사용자로 나타난다.

9. SessionManagementFilter : 이 필터는 인증된 사용자와 관련된 모든 세션을 추적한다.

10. ExceptionTranslationFilter : 이 필터는 보호된 요청을 처리하는 중에 발생할 수 있는 예외를 위임하거나 전달하는 역할을 한다.

11. FilterSecurityInterceptor : 이 필터는 AccessDecisionManager 로 권한부여 처리를 위임함으로써 접근 제어 결정을 쉽게해준다.

 

Spring Security를 사용하기 위해 WebSecurityConfigurerAdapter를 상속받아 configure 메소드를

override 한다.

1. @EnableWebSecurity, @EnableGlobalAuthentication 어노테이션을

사용하기 위한 maven에 라이브러리 추가

- Maven을 이용한 web, security 추가

<dependency>

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

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

<version>4.2.2.RELEASE</version>

</dependency>

<dependency>

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

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

<version>4.2.2.RELEASE</version>

</dependency>

2. WebSecurityConfigurerAdapter 상속받기

package com.pig.api.config;

import org.springframework.context.annotation.Configuration;

import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;

import org.springframework.security.config.annotation.web.builders.HttpSecurity;

import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration

@EnableWebSecurity

@EnableGlobalAuthentication

public class SpringSecurity extends WebSecurityConfigurerAdapter {

@Autowired

private UserDetailsService userDetailsService;

@Override

public void configure(WebSecurity web) throws Exception {

//static 하위 폴더에 있는 js, css, img는 모든사람이 접근해야하기때문에 무시한다.

web.ignoring().antMatchers("/css/**", "/js/**", "/img/**");

}

@Override

protected void configure(HttpSecurity http) throws Exception {

http

.csrf().disable()

.authorizeRequests()

.antMatchers("/login").permitAll()

.anyRequest().authenticated()

.and()

.formLogin()

.loginPage("/auth") //로그인 페이지 url

.loginProcessingUrl("/loginAction") //Form 태그의 action과 같아야함

.usernameParameter("email") //로그인 요청시 파라미터 id

//.passwordParameter("password") //로그인 요청시 password 파라미터

.failureForwardUrl("/login/fail") //로그인 실패시 이동할 url

.defaultSuccessUrl("/");

}

 

@Override //사용자 세부 서비스를 설정하기 위한 오버라이딩

protected void configure(AuthenticationManagerBuilder auth) throws Exception {

// 사용자 세부 서비스를 설정하기 위한 오버라이딩이다.

// Spring Security에서 인증(Authentication)은 AuthenticationManager가 담당

// /loginPage 입력한 폼데이터(email)를 AuthentiCationManager에 넘겨주면

// AuthenticationManager를 구현한 ProviderManager가 처리한다.

// AuthenticationManager가 AuthenticationProvider를 통해 UserDetailsService를

// 거쳐 인증을 받아 UserDetails에 SecurityUser를 등록한다.

auth.userDetailsService(userDetailsService);

}

}

- csrf().disable() : csrf(Cross Site Request Forgery, 사이트간 요청 위조)

- authorizeRequests() : HttpServletRequest 요청 URL에 따라 접근 권한을 설정한다.

- antMatchers("url") : 해당 URL에 맞는

- permitAll() : 모든 유저에게 접근을 허용한다.

- anyRequest().authenticated() : 이외의 요청에는 인증을 해야 접근가능

- formLogin() : 로그인에 관한 설정을 한다.

- loginPage("url") : 로그인 페이지

- loginProcessingUrl("/loginAction") : html파일의 Form 태그의 action과 같아야함

 

 

3. 상속한 WebSecurityConfigurerAdapter 의 Configure 메소드를 override 하기

- authorizeRequests() : HttpServletRequest 요청 URL에 따라 접근 권한을 설정한다.

- antMatchers("pathPattern") : 요청 URL 패턴을 지정한다.

- permitAll() : 모든 유저에게 접근을 허용한다.

- anonymous() : 인증되지 않은 유저만 허용한다.

- denyAll() : 모든 유저에 대해 접근을 허용하지 않는다.

- formLogin() : 로그인에 관한 설정을 한다.

- loginPage("url") : 로그인 페이지

HttpSecurity 와 WebSecurity 가 존재한다.

* HttpSecurity : 특정 Http 요청들에 대해 보안구성

* WebSecurity : 특정 요청을 무시하기 위해 사용, FilterChainProxy를 생성하는 필터입니다.

ex)

.antMatchers("/admin/**").hasRole("ADMIN")

/admin 으로 시작하는 경로는 ADMIN 롤을 가진 사용자만 접근 가능합니다.

.antMatchers("/user/myinfo").hasRole("MEMBER")

/user/myinfo 경로는 MEMBER 롤을 가진 사용자만 접근 가능합니다.

.antMatchers("/**").permitAll()

모든 경로에 대해서는 권한없이 접근 가능합니다.

.anyRequest().authenticated()

모든 요청에 대해, 인증된 사용자만 접근하도록 설정할 수도 있습니다. ( 예제에는 적용 안함 )

.csrf().disable()

(Cross Site Request Forgery : 사이트 간 요청 위조)

4. 회원 인증정보를 담고있는 UserDetails 인터페이스를 구현한 UserCheck VO 구현

- UserDetails를 구현한 UserCheck VO

- UserDetails를 구현해야하는 이유는 유저정보 인증로직을 하기위해 UserDetailsService 인터페이스를 구현하고 return으로 UserDetails를 넘겨줘야하기떄문

public class UserCheck implements UserDetails{

private String username;

private String password;

private boolean isEnabled;

private boolean isAccountNonExpired;

private boolean isAccountNonLocked;

private boolean isCredentialsNonExpired;

private Collection<? extends GrantedAuthority>authorities;

@Override

public String getUsername() {

return username;

}

public void setUsername(String username) {

this.username = username;

}

@Override

public String getPassword() {

return password;

}

public void setPassword(String password) {

this.password = password;

}

@Override

public boolean isEnabled() {

return isEnabled;

}

public void setEnabled(boolean enabled) {

isEnabled = enabled;

}

@Override

public boolean isAccountNonExpired() {

return isAccountNonExpired;

}

public void setAccountNonExpired(boolean accountNonExpired) {

isAccountNonExpired = accountNonExpired;

}

@Override

public boolean isAccountNonLocked() {

return isAccountNonLocked;

}

public void setAccountNonLocked(boolean accountNonLocked) {

isAccountNonLocked = accountNonLocked;

}

@Override

public boolean isCredentialsNonExpired() {

return isCredentialsNonExpired;

}

public void setCredentialsNonExpired(boolean credentialsNonExpired) {

isCredentialsNonExpired = credentialsNonExpired;

}

@Override

public Collection<? extends GrantedAuthority> getAuthorities() {

return authorities;

}

public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {

this.authorities = authorities;

}

@Override

public String toString() {

return "CustomUserDetails [username=" + username + ", password=" + password + ", isEnabled=" + isEnabled

+ ", isAccountNonExpired=" + isAccountNonExpired + ", isAccountNonLocked=" + isAccountNonLocked

+ ", isCredentialsNonExpired=" + isCredentialsNonExpired + ", authorities=" + authorities + "]";

}

}

UserDetailsService 구현한 AccountService class

package com.secureone.draw.service;

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.Service;

import com.secureone.draw.vo.UserCheck;

@Service

public class AccountService implements UserDetailsService{

@Override

public UserCheck loadUserByUsername(String username) throws UsernameNotFoundException{

//입력한 아이디가 username에 담겨져 온다.

//입력한 username을 가지고 인증정보를 DB에서 불러와 user 데이터에 집어넣고

//username의 DB정보를 집어넣은 UserDetails VO와 입력한 데이터를 비교하여 인증여부 판별

int count = rm.userEmailCount(username);

SecurityUser su = new SecurityUser();

if(count > 0) {

List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();

authorities.add(new SimpleGrantedAuthority("ROLE_USER"));

su.setUsername(username);

su.setPassword("{noop}"+"1234");

su.setAuthorities(authorities);

}else {

su.setPassword("{noop}"+"1");

}

return su;

}

}

* loadUserByUsername(String username)

1. 해당 메서드의 파라미터인 username으로 사용자가 로그인페이지에서 입력한 ID가 넘어온다.

2. 입력한 username의 값을 가지고 DB에서 패스워드 및 권한을 가져와 UserDetails를 구현한

SecurityUser VO에다가 집어넣고 return 한다.

3. SecurityUser VO와 Authentication 을 비교하여 인증여부 판별

* {noop}

PasswordEncode 에러가 날경우 패스워드 앞부분에 "{noop}" String을 붙여준다.

반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.