SMALL

Spring Security에서 Annotation 형태로 Class, Method 레벨 단위로 권한별 접근제어 기능을 제공한다.

( https://joomn11.tistory.com/88 )

 

그중에서 @PreAuthorize, @PostAuthorize Annotation의 경우에는 SpringEL에서 제공하는 Expression 이외에도 사용자가 정의한 Custom Expression(Method)를 호출할 수 있는 기능을 제공한다.

해당 기능에 대해서 알아보도록 하자.

 

@PreAuthorize("isAuthenticated() and (( #user.name == principal.name ) or hasRole('ROLE_ADMIN'))")
@RequestMapping( value = "", method = RequestMethod.PUT)
public ResponseEntity<Project> updateProject( User user ){
    updateProject.update( user );
    return new ResponseEntity<Project>( new Project(), HttpStatus.OK );
}

우선 위의 예제와 같이 isAuthenticated, hasRole과 같은 Spring EL에서 기본제공하는 기능을 활용할 수도 있다. 

 

다만, 사용자가 원하는 기능이 기본으로 제공하는 기능에 존재하지 않는 경우에는 문제가 될 수 있다.

이러한 경우를 커버하기 위해서 Spring Security에서는 Custom Expression을 사용할 수 있는 기능을 열어 두었다.

 

 

Component 생성

먼저, Custom Expression(Method)를 정의할 Component를 생성한다. 

 

// @Service
@Component("loginService")
public class LoginService {
	public boolean pageReadRoleCheck() {
		return true;
	}
}

 

@PreAuthorize, @PostAuthorize Annotation에서 사용

@GetMapping("/api/v1/projects")
@PreAuthorize("@loginService.pageReadRoleCheck()")
public List<ProjectResponseDto> getProjectList() {
	return projectService.findAll();
}

 

위와 같이 간단하게 Custom Expression을 Spring Security Annotation에서 사용이 가능하다.

 

Param 정보 전달

추가적으로, Custom Expression에 필요한 정보를 Param으로 넘길 수도 있다.

#pj와 같이 사용하여 Annotation이 붙은 Method의 Param 정보도 전달 할 수 있다.

// @Service
@Component("loginService")
public class LoginService {
	public boolean pageReadRoleCheck(String id) {
		return true;
	}
}
/////
@GetMapping("/api/v1/projects")
@PreAuthorize("@loginService.pageReadRoleCheck(#pj.id)")
public List<ProjectResponseDto> getProjectList(Project pj) {
	return projectService.findAll();
}

 

returnObject

@PostAuthorize의 경우에는 해당 Method의 수행 결과 값도 전달 할 수 있다. 

returnObject라는 예약어를 사용하면 된다.

@PostAuthorize("@loginService.pageReadRoleCheck(returnObject.name)")
@RequestMapping( value = "/{id}", method = RequestMethod.GET )
public Project getProject( @PathVariable("id") long id ){
    return service.findOne(id);
}

 

Annotation 활성화 설정 추가

마지막으로, 이러한 Annotation을 활성화 시키기 위해서는 설정을 추가해 주어야 한다.

@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)

@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class ServerSecurityConfiguration extends WebSecurityConfigurerAdapter {
	@Override
	protected void configure(HttpSecurity http) throws Exception {
        //.....
   }
}

 

LIST
SMALL

Spring Security를 처음 접하는 경우 권한 별 접근 제어는 대부분 

WebSecurityConfigurerAdapter를 상속받은 클래스 하위에 configure(HttpSecurity http) 메서드에 설정하게 된다. 

 

public class ServerSecurityConfiguration extends WebSecurityConfigurerAdapter {

	private final LoginService loginService;
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
	    SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
	    successHandler.setTargetUrlParameter("redirectTo");
	    successHandler.setDefaultTargetUrl(this.adminServer.path("/"));

	    http.authorizeRequests(
	        (authorizeRequests) -> authorizeRequests.antMatchers("/assets/**").permitAll() 
	            .antMatchers("/login").permitAll()
	            .antMatchers("/h2-console/**").permitAll()
	            .anyRequest().authenticated() 
	    ).httpBasic(Customizer.withDefaults()) 
	        .csrf((csrf) -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) 
	            .ignoringRequestMatchers(
	                new AntPathRequestMatcher("/api/v1/**"),
	                new AntPathRequestMatcher("/h2-console/**")
	                
	            ))
	        .headers().frameOptions().disable().and()
	        .rememberMe((rememberMe) -> rememberMe.key(UUID.randomUUID().toString()).tokenValiditySeconds(1209600));
	}
}

 

 

이 설정 이외에 만약 특정 메서드에서 권한 별 접근 제어를 하고 싶은 경우에는 어떠한 방법이 있는지 알아보도록 하자. 

 

Spring Security에서는 특정 Class, Method에 적용할 수 있는 Annotation을 제공한다.

해당 Annotation을 원하는 위치에 작성하면 해당 클래스 또는 메서드는 권한 체크하는 로직을 타게 된다. 

 

Spring Security에서는 다양한 Annotation을 제공하는데, 오늘은 간단하게 @Secure, @PreAuthorize를 알아보도록 하자.

 

@Secured

@Secured는 특정 권한만 접근이 가능하다는 것을 나타내는 Annotation이다. 

간단하게 특정 기능은 Admin만 이용하게 하고 싶은 경우 위의 Annotation을 붙여주면 된다. 

public class ProjectController {

    private final ProjectService projectService;

    @PostMapping("/api/v1/project")
    @Secured("ROLE_ADMIN")
    public String save(@RequestBody ProjectSaveRequestDto requestDto) throws DuplicateException {

        return projectService.save(requestDto);
    }
}

 

만약에 단순하게 특정 권한을 가진 사람이 아닌 다양한 조건이 들어가야 되는 경우에는 @Secured로 표현하기는 조금 힘들게 된다.

이러한 경우에는 @PreAuthorize, @PostAuthorize를 이용하면 좀 더 복잡한 케이스도 처리할 수 있게 된다.

해당 Annotation에서는 SpringEL을 사용할 수 있어서 다양한 권한에 따른 처리가 가능하다. 

 

Spring Expression Language에서는 다양한 Built-in expression을 제공한다. 

아래의 예시에서는 " isAutheticated()" 를 사용하였다. 

Spring EL에서 제공하는 Expression을 숙지하고 있으면 사용자가 직접 기능을 만들지 않고도 활용할 수 있어 유용하다. 

 

https://docs.spring.io/spring-security/reference/servlet/authorization/expression-based.html#el-common-built-in

 

Expression-Based Access Control :: Spring Security

Any Spring-EL functionality is available within the expression, so you can also access properties on the arguments. For example, if you wanted a particular method to only allow access to a user whose username matched that of the contact, you could write

docs.spring.io

 

@PreAuthorize("isAuthenticated() and (( #user.name == principal.name ) or hasRole('ROLE_ADMIN'))")
@RequestMapping( value = "", method = RequestMethod.PUT)
public ResponseEntity<Project> updateProject( User user ){
    updateProject.update( user );
    return new ResponseEntity<Project>( new Project(), HttpStatus.OK );
}

@PreAuthorize

추가적으로 @PreAuthorize 내에 #user.name를 사용한 부분을 보면

Annotation이 붙은 Method의 Param값을 위와 같이 활용하여 사용할 수 있게 된다. 

 

@PostAuthorize

@PostAuthorize의 경우에는 Method가 실행된 이후의 return값을 활용할 수 있다. 

아래와 같이 returnObject.name과 같이 "returnObject"를 사용하면 된다. 

@PostAuthorize("isAuthenticated() and (( returnObject.name == principal.name ) or hasRole('ROLE_ADMIN'))")
@RequestMapping( value = "/{id}", method = RequestMethod.GET )
public Project getProject( @PathVariable("id") long id ){
    return service.findOne(id);
}

 

Annotation 활성화 설정 추가

추가적으로, 이러한 Annotation을 활성화 시키기 위해서는 설정을 추가해 주어야 한다. 

@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)

@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class ServerSecurityConfiguration extends WebSecurityConfigurerAdapter {

	private final LoginService loginService;
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
	    
        //.....
   }
}
LIST

+ Recent posts