[Spring Security] 인가 기능 구현 (1)

식빵·2022년 9월 16일
0

이 시리즈에 나오는 모든 내용은 인프런 인터넷 강의 - 스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security - 에서 기반된 것입니다. 그리고 여기서 인용되는 PPT 이미지 또한 모두 해당 강의에서 가져왔음을 알립니다. 추가적으로 여기에 작성된 코드들 또한 해당 강의의 github 에 올라와 있는 코드를 참고해서 만든 겁니다.


이 글의 시작 코드는 아래와 같이 준비한다.
1. git clone https://github.com/onjsdnjs/corespringsecurity.git
2. git checkout ch05-01
3. application.properties 파일 내용 일부 수정

spring.datasource.url=jdbc:postgresql://localhost:5432/springboot
spring.datasource.username=postgres
spring.datasource.password=root # 자기 것에 맞게 수정

spring.jpa.hibernate.ddl-auto=create # 일단 create 으로 수정
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true

spring.thymeleaf.cache=false

spring.devtools.livereload.enabled=true
spring.devtools.restart.enabled=true

spring.main.allow-bean-definition-overriding=true
  1. SetupDataLoader 코드 일부 수정
@Component
public class SetupDataLoader implements ApplicationListener<ContextRefreshedEvent> {

	// ... 일부 생략 ...
    private void setupSecurityResources() {
        Set<Role> roles = new HashSet<>();
        Role adminRole = createRoleIfNotFound("ROLE_ADMIN", "관리자");
        roles.add(adminRole);
        createResourceIfNotFound("/admin/**", "", roles, "url");
        Account account = createUserIfNotFound("admin", "pass", "admin@gmail.com", 10,  roles);
      
       Set<Role> roles1 = new HashSet<>();
       Role managerRole = createRoleIfNotFound("ROLE_MANAGER", "매니저");
       roles1.add(managerRole);
        createUserIfNotFound("manager", "pass", "manager@gmail.com", 20, roles1);
 
        Set<Role> roles3 = new HashSet<>();
        Role childRole1 = createRoleIfNotFound("ROLE_USER", "회원");
        roles3.add(childRole1);
        createResourceIfNotFound("/users/**", "", roles3, "url");
        createUserIfNotFound("user", "pass", "user@gmail.com", 30, roles3);

        createResourceIfNotFound("/mypage", "", roles3, "url");
        createResourceIfNotFound("/message", "", roles1, "url");
        createResourceIfNotFound("/config", "", roles, "url");
    }
}

  1. "두 번" 애플리케이션 실행, 처음은 에러 문구 좀 보이고, 두번째 다시 실행하면 OK

  2. application.properties 에서 spring.jpa.hibernate.ddl-auto=validate 로 수정

이전 글에서 작성한 코드에 이어서 작성하고 싶지만, 강의 코드 자체가 부분적으로 아주 많이 바뀌었는데, 그걸 다 추적해서 새로 작성하는 건 힘들어서 그냥 clone, checkout 했다.



1. 개요

목표

  • DB와 연동하여 자원 및 권한을 설정하고 제어 ==> 동적 권한 관리가 가능!
  • 기존의 하드코딩된 권한 관련 코드를 제거
    • antMathcers("/user").hasRole("USER")
  • 관리자 시스템 구축
    • 회원 관리 ==> 권한 부여
    • 권한 관리 ==> 권한 생성, 삭제
    • 자원 관리 ==> 자원 생성, 삭제, 수정, 권한 매핑
  • 권한 계층 구현
    • URL or Method(=aop 방식)





2. 도메인, 테이블, Entity Class


권한과 관련된 시스템을 구축하기 위해서는 테이블과 그에 따른 도메인,
그리고 그 도메인과 연관이 있는 JPA Entity Class를 작성해야 한다.

우리가 필요로하는 모델은 아래와 같다.

2-1. 도메인 관계도


2-2. 테이블 관계도


2-3. Entity Class 구성

package io.security.corespringsecurity.domain.entity;

import lombok.*;

import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

@Entity
@Data
@ToString(exclude = {"userRoles"})
@Builder
@EqualsAndHashCode(of = "id")
@NoArgsConstructor
@AllArgsConstructor
public class Account implements Serializable {

    @Id
    @GeneratedValue
    private Long id;

    @Column
    private String username;

    @Column
    private String email;

    @Column
    private int age;

    @Column
    private String password;

    @ManyToMany(fetch = FetchType.LAZY, cascade={CascadeType.ALL})
    @JoinTable(name = "account_roles", 
    		   joinColumns = { @JoinColumn(name = "account_id") },
               inverseJoinColumns = {@JoinColumn(name = "role_id") })
    private Set<Role> userRoles = new HashSet<>();
}

package io.security.corespringsecurity.domain.entity;

import lombok.*;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

@Entity
@Table(name = "RESOURCES")
@Data
@ToString(exclude = {"roleSet"})
@EntityListeners(value = { AuditingEntityListener.class })
@EqualsAndHashCode(of = "id")
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Resources implements Serializable {

    @Id
    @GeneratedValue
    @Column(name = "resource_id")
    private Long id;

    @Column(name = "resource_name")
    private String resourceName;

    @Column(name = "http_method")
    private String httpMethod;

    @Column(name = "order_num")
    private int orderNum;

    @Column(name = "resource_type")
    private String resourceType;

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "role_resources",
    		   joinColumns = { @JoinColumn(name = "resource_id") },
               inverseJoinColumns = { @JoinColumn(name = "role_id") })
    private Set<Role> roleSet = new HashSet<>();

}


package io.security.corespringsecurity.domain.entity;

import lombok.*;

import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;

@Entity
@Table(name = "ROLE")
@Getter
@Setter
@ToString(exclude = {"users","resourcesSet"})
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(of = "id")
public class Role implements Serializable {

    @Id
    @GeneratedValue
    @Column(name = "role_id")
    private Long id;

    @Column(name = "role_name")
    private String roleName;

    @Column(name = "role_desc")
    private String roleDesc;

    @ManyToMany(fetch = FetchType.LAZY, mappedBy = "roleSet")
    @OrderBy("ordernum desc")
    private Set<Resources> resourcesSet = new LinkedHashSet<>();

    @ManyToMany(fetch = FetchType.LAZY, mappedBy = "userRoles")
    private Set<Account> accounts = new HashSet<>();

}

2-4. Repository

public interface ResourcesRepository extends JpaRepository<Resources, Long> {

    Resources findByResourceNameAndHttpMethod(String resourceName, String httpMethod);

    @Query("select r from Resources r join fetch r.roleSet " +
    		"where r.resourceType = 'url' order by r.orderNum desc")
    List<Resources> findAllResources();

    @Query("select r from Resources r join fetch r.roleSet " +
    		"where r.resourceType = 'method' order by r.orderNum desc")
    List<Resources> findAllMethodResources();

    @Query("select r from Resources r join fetch r.roleSet " +
    		"where r.resourceType = 'pointcut' order by r.orderNum desc")
    List<Resources> findAllPointcutResources();
}

public interface RoleRepository extends JpaRepository<Role, Long> {

    Role findByRoleName(String name);

    @Override
    void delete(Role role);

}

public interface UserRepository extends JpaRepository<Account, Long> {
  Account findByUsername(String username);
  int countByUsername(String username);
}





3. 주요 아케텍처 이해


우리가 이전에 배운 시큐리티 인가 처리는 위와 같이 코드를 짰다.
그리고 여기서 알 수 있는 게, 시큐리티 필터가 인가 처리를 위해 사용되는 정보가
3가지(인증저보, 요청정보, 권한정보)라는 것이다.


그렇다면 스프링 시큐리티에서 저 정보들을 어떤 식으로 생성해두고 내부적으로 사용할까?
아래 그림을 보면서 설명해보겠다.

일단 처음에는 http.antMatchers("/user").access("hasRole('USER')");

  • key 는 자원정보(여기서는 URL 정보)가 담긴 RequestMatcher
  • value 는 해당 자원에 필요한 권한(ex: hasRole("USER")) 정보를 담는
    Map 을 생성한다.

이때 Map 을 만드는 주체는 ExpressionBasedFilterInvocationSecurityMetadataSource 클래스이다. 스프링 시큐리티가 초기화할 때 해당 클래스 내부에서 생성된다.


위 처럼 초기화된 후에 사용자 요청이 들어오면...

  1. FilterSecurityInterceptor 필터는 요청정보를 FilterInvocation 클래스로 Wrap

  2. FilterInvocation 정보를 자신의 부모클래스의 beforeInvocation 메소드의 인자로 주면서 호출

  3. 부모 클래스의 beforeInvocation 내에서 MetadataSource의 참조값을 사용해서 미리 만들었던 Map 정보를 조회

  4. Map 의 Key 값인 RequestMatcher 와 FilterInvocation(현재 요청정보)을 비교

  5. 비교해서 같은 게 있다면 Map value("권한 목록 정보")를 return . (없으면 null 반환)
    그 권한 목록이 위 그림에서는 List<ConfigAttribute> 이다.

  6. SecurityInterceptor 는 반환받은 ("권한 목록 정보")와 List<ConfigAttribute>, Authentication 을 모두 모아서 AccessDecisionManager 에게 보내면서 인가 처리를 위임하게 된다

  7. AccessDecisionManager은 전달 받은 3개의 인자들을 Voter 에게 주면서 인가처리를 위임한다.



여기서 가장 핵심은 자원의 권한 정보를 생성 및 내장하는 MetadataSource 계열의 클래스다.

만약 우리가 위처럼 http.antMatchers("/user").access("hasRole('USER')"); 를 사용하면 ExpressionBasedFilterInvocationSecurityMetadataSource 클래스를 통해서 Map을 만든다.

하지만 우리가 필요한 것은 저런 하드 코딩된 인가 처리가 아니라 DB 에 있는 자원-인가정보를 빼와서 해당 정보로 Map 을 생성해두는 것이다. 지금부터 Custom MetadataSource 클래스를 하나 작성해보자.

본격적으로 만들기에 앞서 ExpressionBasedFilterInvocationSecurityMetadataSource 클래스에 디버그 포인트를 잡고 조금 자세히 관찰해보자.
이후에 우리가 직접 만들 때 참고하기 위함이다.





4. ExpressionBasedFilterInvocationSecurityMetadataSource 코드 관찰


일단 우리의 SecurityConfig 에는 권한 관련 정보를 아래처럼 하드코딩했다.

http
.authorizeRequests()
.antMatchers("/mypage").hasRole("USER")
.antMatchers("/messages").hasRole("MANAGER")
.antMatchers("config").hasRole("ADMIN")
.antMatchers("/**").permitAll()
.anyRequest().authenticate()

이러면 ExpressionBasedFilterInvocationSecurityMetadataSource 에서 아래와 같이 위에 작성된 url 에 따른 FitlerInvocation 객체를 사용하는 것을 확인할 수 있다. (PermitAll 은 이후에 사용될 것이니 신경쓰지 지금은 신경쓰지 말자.)


이 Map 정보는 부모 클래스인 DefaultFilterInvocationSecurityMetadataSource 내부의 requestMap 필드에 저장된다.

public class DefaultFilterInvocationSecurityMetadataSource implements
		FilterInvocationSecurityMetadataSource {
	private final Map<RequestMatcher, Collection<ConfigAttribute>> requestMap;

	public DefaultFilterInvocationSecurityMetadataSource(
			LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap) {

		this.requestMap = requestMap;
	}
    
    // ... 일부 생략 ...

}

아무튼 이 정보는 아래와 같이 FilterSecurityInterceptor 필터의 코드에서 사용하게 된다.
실제 인가 처리를 위해서 말이다. 아래 코드를 보자.


- AbstractSecurityInterceptor.java 일부

  • 실질적 인가처리 작업을 수행하는 beforeInvocation 메소드 내부에서 해당 자원에 필요한 권한 정보를 추출하는 것을 확인
  • 만약 권한 추출 메소드 결과가 null 이면 바로 다음에 나오는 if 문에 의해서 메소드가 return 되는데, 이러면 AccessDecisionManager 에게 권한 심사를 받지 않고 바로 자원에 접근하게 되는 것이다.

- DefaultFilterInvocationSecurityMetadataSource.java 일부

  • 해당 자원의 권한 정보를 추출하는 메소드
  • getAttributes 메소드의 인자로 요청 정보를 담고 있는 FilterInvocation 객체를 받고,
  • 해당 객체의 요청 정보를 추출하여 Map 을 순회한다.
  • 있으면 해당 요청정보에 있던 권한 목록 정보를 return, 없으면 null 반환

이렇게 추출된 권한 정보는 AbstractSecurityInterceptor.beforeInvocation 후반부에 있는 this.accessDecisionManager.decide(authenticated, object, attributes); 메소드에서 사용된다.

첫번째 파라미터는 인증객체, 두번째 파라미터는 FilterInvocation(요청정보), 세번째 파라미터는 방금 설명한 자원접근에 필요한 권한 리스트 정보다.

참고로 FilterInvocation 은 FilterSecurityInterceptor 에서 생성하여, 자신의 부모 클래스인 AbstractSecurityInterceptor.beforeInvocation 메소드의 인자로 준 것이다.



최종적으로 accessDecisionManager.decide 메소드에서 Voter 들에 의한 접근 여부를 묻게 된다.

참고: 스프링 시큐리티의 MetadataSource 클래스 및 인터페이스






5. 커스텀 MetadataSource 클래스


작성에 앞서 우리가 만들 MetadataSource 클래스가 어디서 어떤 역할을 하는지 그림을 통해 가볍게 보고 바로 구현을 시작하자.



🔘 클래스 생성

package io.security.corespringsecurity.security.metadatasource;

import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.RequestMatcher;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

public class MyUrlFilterInvocationSecurityMetadatsSource implements FilterInvocationSecurityMetadataSource {
	
	private LinkedHashMap<RequestMatcher, List<ConfigAttribute>> requestMap
		= new LinkedHashMap<>();
	
	@Override
	public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
		
		HttpServletRequest request = ((FilterInvocation) object).getRequest();
		
		if (requestMap != null) {
			Set<Map.Entry<RequestMatcher, List<ConfigAttribute>>> entries = requestMap.entrySet();
			for (Map.Entry<RequestMatcher, List<ConfigAttribute>> entry : entries) {
				RequestMatcher matcher = entry.getKey();
				if (matcher.matches(request)) {
					return entry.getValue();
				}
			}
		}
		
		return null;
	}
	
	@Override
	public Collection<ConfigAttribute> getAllConfigAttributes() {
		Set<ConfigAttribute> allAttributes = new HashSet<>();
		
		for (Map.Entry<RequestMatcher, List<ConfigAttribute>> entry : requestMap
			.entrySet()) {
			allAttributes.addAll(entry.getValue());
		}
		
		return allAttributes;
	}
	
	@Override
	public boolean supports(Class<?> clazz) {
		return FilterInvocation.class.isAssignableFrom(clazz);
	}
}



🔘 스프링 시큐리티 적용


@Configuration
@EnableWebSecurity
@Slf4j
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
	// ... 생략 ....

	// AcessDecisionManager 생성
    private AccessDecisionManager affirmativeBased() {
        AffirmativeBased affirmativeBased = new AffirmativeBased(getAccessDecistionVoters());
        return affirmativeBased;
    }

	// AcessDecisionManager 내부에서 찬반 여부를 정하는 Voter 생성
    private List<AccessDecisionVoter<?>> getAccessDecistionVoters() {
        return Arrays.asList(new RoleVoter());
    }
    
    // 자원에 대한 권한 정보를 읽는 MetadataSource 생성
    @Bean
    public FilterInvocationSecurityMetadataSource myUrlFilterInvocationSecurityMetadataSource() {
        return new MyUrlFilterInvocationSecurityMetadatsSource();
    }
    
    
    // MetadataSource 를 사용하는 권한 체크 필터, 
    // 즉 FilterSecurityInterceptor 를 직접 생성한다.
    @Bean
    public FilterSecurityInterceptor customFilterSecurityInterceptor() throws Exception {
        
        FilterSecurityInterceptor filterSecurityInterceptor = new FilterSecurityInterceptor();
        filterSecurityInterceptor
        	.setSecurityMetadataSource(myUrlFilterInvocationSecurityMetadataSource());
        filterSecurityInterceptor.setAccessDecisionManager(affirmativeBased());
      
        // 권한체크 필터는 현재 사용자가 인증된 사용자인지 검사하기 때문에 인증 매니저가 필요하다.
        filterSecurityInterceptor.setAuthenticationManager(authenticationManagerBean());
        return filterSecurityInterceptor;
    }


    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            // .antMatchers("/mypage").hasRole("USER")
            // .antMatchers("/messages").hasRole("MANAGER")
            // .antMatchers("/config").hasRole("ADMIN")
            // .antMatchers("/**").permitAll()
            .anyRequest().authenticated()
            .and()
            // ... 일부 생략 ...
            .and()
            .addFilterBefore(customFilterSecurityInterceptor(),
            	FilterSecurityInterceptor.class)
        ;

        http.csrf().disable();

        customConfigurer(http);
    }
}

이렇게 하고 실행한 후에 FilterProxyChain 내부에 디버깅 포인트를 잡으면
우리가 생성한 FilterSecurityInterceptor 가 정상적으로 등록된 것을 확인할 수 있다.



참고: FilterSecurityInterceptor 가 2개 있는데, 권한 체크가 두 번 일어나는 거 아님?
대답은 No 이다. 아래 코드를 보면 이해가 될 것이다.

한 번 FilterSecurityInterceptor 를 거치게 되면 다음 FilterSecurityInterceptor는 요청을 바로 다음 Filter Chain 으로 위임한다.



🔘 코드흐름 관찰

한번 localhost:8080/mypage 로 바로 접근해보자.

  • 현재 requestMap 에 아무런 자원, 권한 정보가 없어서 인가 처리가 일어나지 않는다.

  • 문구 그대로, 더 이상 권한 체크 작업 프로세스는 일어나지 않는다, 곧 구현할 것이다.
  • null 이 반환되면 권한 심사는 일어나지 않는다. 바로 mypage 에 접근이 된다.

  • mypage 화면에 접근






6. DB 조회로 requestMap 세팅


MyUrlFilterInvocationSecurityMetadatsSource 에는 현재 Map<자원, 권한목록> 정보가 없어서 권한체크 프로세스가 일어나지 않는다.

이제 Map<자원, 권한목록> 를 DB 에서 정보를 얻어와서 생성하고,
실제 권한 체크가 일어나도록 해보자.


자원 - 권한 DB 정보 조회 Service

public class SecurityResourceService {

	private final ResourcesRepository resourcesRepository;
	
	public SecurityResourceService(ResourcesRepository resourcesRepository) {
		this.resourcesRepository = resourcesRepository;
	}

	public LinkedHashMap<RequestMatcher, List<ConfigAttribute>> getResourceList() {

		LinkedHashMap<RequestMatcher, List<ConfigAttribute>> result
				= new LinkedHashMap<>();

		List<Resources> resourcesList = resourcesRepository.findAllResources();
		resourcesList.forEach(re -> {
			List<ConfigAttribute> configAttributeList = new ArrayList<>();
			re.getRoleSet().forEach(role -> {
				configAttributeList.add(new SecurityConfig(role.getRoleName()));
				result.put(new AntPathRequestMatcher(re.getResourceName()), configAttributeList);
			});
		});
		
		return result;
	}
}



requestMap 생성 FactoryBean 작성

public class UrlResourcesMapFactoryBean implements FactoryBean<LinkedHashMap<RequestMatcher, List<ConfigAttribute>>> {

	private SecurityResourceService securityResourceService;

	private LinkedHashMap<RequestMatcher, List<ConfigAttribute>> resourceMap;

	public void setSecurityResourceService(SecurityResourceService securityResourceService) {
		this.securityResourceService = securityResourceService;
	}

	@Override
	public LinkedHashMap<RequestMatcher, List<ConfigAttribute>> getObject() throws Exception {
		if (resourceMap == null) {
			init();
		}
		return resourceMap;
	}

	private void init() {
		resourceMap = securityResourceService.getResourceList();
	}

	@Override
	public Class<?> getObjectType() {
		return LinkedHashMap.class;
	}

	@Override
	public boolean isSingleton() {
		return FactoryBean.super.isSingleton();
	}
}



MetadatsSource 생성자로 Map 정보 받기

public class MyUrlFilterInvocationSecurityMetadatsSource implements FilterInvocationSecurityMetadataSource {
	
	private LinkedHashMap<RequestMatcher, List<ConfigAttribute>> requestMap
		= new LinkedHashMap<>();
	
    // DI 받는다.
	public MyUrlFilterInvocationSecurityMetadatsSource(LinkedHashMap<RequestMatcher, List<ConfigAttribute>> resourcesMap) {
		this.requestMap = resourcesMap;
	}
	

	// ... 생략 ...
}



Security Config

@Configuration
@EnableWebSecurity
@Slf4j
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	// 생략...
    
    @Autowired
    private SecurityResourceService securityResourceService;
        
    private UrlResourcesMapFactoryBean urlResourcesMapFactoryBean() {
        UrlResourcesMapFactoryBean urlResourcesMapFactoryBean = new UrlResourcesMapFactoryBean();
        urlResourcesMapFactoryBean.setSecurityResourceService(securityResourceService);
        return urlResourcesMapFactoryBean;
    }
    
    @Bean
    public FilterInvocationSecurityMetadataSource myUrlFilterInvocationSecurityMetadataSource() throws Exception {
        return new MyUrlFilterInvocationSecurityMetadatsSource(urlResourcesMapFactoryBean().getObject());
    }

}






7. 인가처리 실시간 반영


MyUrlFilterInvocationSecurityMetadatsSource 수정

public class MyUrlFilterInvocationSecurityMetadatsSource implements FilterInvocationSecurityMetadataSource {
	
	private LinkedHashMap<RequestMatcher, List<ConfigAttribute>> requestMap;
	private SecurityResourceService securityResourceService;
	
	public MyUrlFilterInvocationSecurityMetadatsSource(LinkedHashMap<RequestMatcher, List<ConfigAttribute>> resourcesMap, SecurityResourceService securityResourceService) {
		this.requestMap = resourcesMap;
		this.securityResourceService = securityResourceService;
	}
	
	public void reload() {
		LinkedHashMap<RequestMatcher, List<ConfigAttribute>> reloadedMap = securityResourceService.getResourceList();
		Iterator<Map.Entry<RequestMatcher, List<ConfigAttribute>>> iterator = reloadedMap.entrySet().iterator();
		
		requestMap.clear();
		
		while (iterator.hasNext()) {
			Map.Entry<RequestMatcher, List<ConfigAttribute>> entry = iterator.next();
			requestMap.put(entry.getKey(), entry.getValue());
		}
	}
}



시큐리티 설정 수정

@Configuration
@EnableWebSecurity
@Slf4j
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    private SecurityResourceService securityResourceService;

    @Bean
    public MyUrlFilterInvocationSecurityMetadatsSource myUrlFilterInvocationSecurityMetadataSource() throws Exception {
        return new MyUrlFilterInvocationSecurityMetadatsSource(urlResourcesMapFactoryBean().getObject(), securityResourceService);
    }
    
    // ... 생략 ...
    
}



Controller 에 코드 추가

@Controller
public class ResourcesController {
	
    //.. 생략 ...
	@Autowired
	private MyUrlFilterInvocationSecurityMetadatsSource myUrlFilterInvocationSecurityMetadatsSource;
	
	@PostMapping(value="/admin/resources")
	public String createResources(ResourcesDto resourcesDto) throws Exception {

		ModelMapper modelMapper = new ModelMapper();
		Role role = roleRepository.findByRoleName(resourcesDto.getRoleName());
		Set<Role> roles = new HashSet<>();
		roles.add(role);
		Resources resources = modelMapper.map(resourcesDto, Resources.class);
		resources.setRoleSet(roles);

		resourcesService.createResources(resources);
		myUrlFilterInvocationSecurityMetadatsSource.reload();

		return "redirect:/admin/resources";
	}

	@GetMapping(value="/admin/resources/delete/{id}")
	public String removeResources(@PathVariable String id, Model model) throws Exception {

		Resources resources = resourcesService.getResources(Long.valueOf(id));
		resourcesService.deleteResources(Long.valueOf(id));
		myUrlFilterInvocationSecurityMetadatsSource.reload();

		return "redirect:/admin/resources";
	}
	    
}
  • 생성/삭제에 대해서만 refresh!

profile
백엔드를 계속 배우고 있는 개발자입니다 😊

0개의 댓글