-
JWT Token을 spring security에서 손쉽게 검증하기 위한 방법IT/spring 2021. 1. 26. 15:21
안녕하세요
JWT Token를 Controller에서 손쉽게 검증하기 위한 점진적 발자취입니다.
0. JWT Token 검증
- spring security 쪽에 Filter를 걸어 이미 Token 검증을 하고있음
1. 문제 인식 (상황)
- 사용자가 접근 가능한 URL @PathVariable 에만 접속 가능하도록 하고싶다.
2. PermissionEvaluator 처리.
- https://www.baeldung.com/spring-security-create-new-custom-security-expression
public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations { ... public boolean hasROLE_ADMIN() { return this.hasAnyRole("ROLE_ADMIN"); } public boolean isUserTokenAndDBEnable(Long no) { return this.isUserToken(no) && this.isUserDBEnable(no); } public boolean isUserToken(Long no) { UserAuthToken userAuthToken = tokenService.parserJwtToUserToken(); return userAuthToken.isUserEquals(no); } public boolean isUserDBEnable(Long no) { return coreUserService.findByNoAndEnabled(no, UseCd.USE001).isPresent(); } ... }
3. 사용
@Slf4j @RestController @RequestMapping(UsersController.URI_PREFIX) @Api(tags = "회원") @Validated public class UsersController { ... @ApiOperation(value = "정보") @GetMapping(value = "/{no}", produces = ProjectMediaType.V1_JSON_VALUE) @JsonView({JsonViewApi.class}) @PreAuthorize("hasROLE_ADMIN() or isUserTokenAndDBEnable(#no)") public CoreUser user(@PathVariable("no") Long no) { ... } ... }
@PreAuthorize("hasROLE_ADMIN() or isUserTokenAndDBEnable(#no)")
4. 테스트
환경: no: 2 를 취한 사용자가 요청할때
- 다른값 전송: /users/222
- 정상값 전송: /users/2
하지만.!!
매번 method에
@PreAuthorize("hasROLE_ADMIN() or isUserTokenAndDBEnable(#no)")
어노테이션을 달아주기 귀찮다.
그래서.
@Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .sessionManagement() .and() .formLogin() .loginPage(LOGIN_PAGE) //로그인 페이지 .loginProcessingUrl(LOGIN_PROCESSING_URL) //login-processing-url 로그인 페이지 form action에 입력할 주소 지정 .usernameParameter(USERNAME_PARAMETER) .passwordParameter(USERPWD_PARAMETER) .defaultSuccessUrl(DEFAULT_SUCCESS_URL) //성공시 이동될 페이지 .permitAll() .and() .logout() .logoutUrl(LOGOUT_URL) .invalidateHttpSession(true) .logoutSuccessUrl(DEFAULT_SUCCESS_URL) .and() .addFilter(new JwtUsernameAndPasswordAuthenticationFilter(authenticationManager(), jwtConfig, cacheManager, tokenService, coreUserService, passwordConfig)) .addFilterAfter(new JwtTokenVerifier(jwtConfig, cacheManager, tokenService),JwtUsernameAndPasswordAuthenticationFilter.class) .authorizeRequests() .expressionHandler(new CustomWebSecurityExpressionHandler(this.tokenService, this.coreUserService)) .antMatchers("/", "index","/favicon.ico", "*.css","/css/*", "/js/*").permitAll() .antMatchers("/apis/users/{no}" ,"/apis/users/{no}/**").access("isUserTokenAndDBEnable(#no)") .antMatchers("/apis" ,"/apis/**").hasAnyRole(UserRole.USER.name(), UserRole.ADMIN.name()) .antMatchers("/swagger", "/swagger/**").hasRole(UserRole.SWAGGER.name()) .antMatchers("/docs", "/docs/**").hasRole(UserRole.DOCS.name()) .anyRequest() .authenticated(); }
추가 및 패턴추가
expressionHandler(new CustomWebSecurityExpressionHandler(this.tokenService, this.coreUserService)).antMatchers("/apis/users/{no}" ,"/apis/users/{no}/**").access("isUserTokenAndDBEnable(#no)")
.antMatchers("/apis" ,"/apis/**").hasAnyRole(UserRole.USER.name(), UserRole.ADMIN.name())public class CustomWebSecurityExpressionHandler extends DefaultWebSecurityExpressionHandler { private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); private final TokenService tokenService; private final CoreUserService coreUserService; public CustomWebSecurityExpressionHandler(TokenService tokenService, CoreUserService coreUserService) { this.tokenService = tokenService; this.coreUserService = coreUserService; } @Override protected SecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, FilterInvocation fi) { final CustomSecurityExpressionRoot root = new CustomSecurityExpressionRoot(authentication, fi, this.tokenService, this.coreUserService); root.setPermissionEvaluator(getPermissionEvaluator()); root.setTrustResolver(this.trustResolver); root.setRoleHierarchy(getRoleHierarchy()); return root; } }
public class CustomSecurityExpressionRoot implements MethodSecurityExpressionOperations { ... private Object filterObject; private Object returnObject; public CustomSecurityExpressionRoot(Authentication authentication, FilterInvocation filterInvocation, TokenService tokenService, CoreUserService coreUserService) { if (authentication == null) { throw new IllegalArgumentException("Authentication object cannot be null"); } this.filterInvocation = filterInvocation; this.authentication = authentication; this.tokenService = tokenService; this.coreUserService = coreUserService; } public boolean hasROLE_ADMIN() { return this.hasAnyRole("ROLE_ADMIN"); } public boolean isUserTokenAndDBEnable(Long no) { return this.isUserToken(no) && this.isUserDBEnable(no); } public boolean isUserToken(Long no) { UserAuthToken userAuthToken = tokenService.parserJwtToUserToken(filterInvocation.getRequest().getHeader(HttpHeaders.AUTHORIZATION)); return userAuthToken.isUserEquals(no); } public boolean isUserDBEnable(Long no) { return coreUserService.findByNoAndEnabled(no, UseCd.USE001).isPresent(); } ... }
하여 DefaultWebSecurityExpressionHandler 상속받아 custom 하였다. 끝~
'IT > spring' 카테고리의 다른 글
spring boot 정리 (locale, messageSource, security, jpa, hibernate, Scheduler, config) (0) 2021.02.04 spring boot 에서 JOOQ 사용시, 구동 1분이상 느려짐 현상 (버그) (0) 2021.02.04 spring에서 FCM (firebase cloud messaging) push 보내기. (11) 2021.02.04 spring boot jwt (0) 2020.12.25 spring boot 다중 커넥션 multiple dataSource + hikari (0) 2020.09.30