2017-11-22 49 views
1

스프링 부트 애플리케이션이 있는데 아파치 시로를 통합하려고했습니다. 첫 번째 반복으로서 JWT 방법을 인증하고 승인합니다. 세션은 전혀 없습니다.JWT가있는 Spring Boot 및 Apache Shiro - 올바르게 사용하고 있습니까?

내가 작성한 방식대로 모든 REST 요청에는 유효성을 검사해야하는 JWT 헤더가 있어야합니다. 나는 시로 (shiro) 필터에서 그것을하고있다. 유효성 검사 후 필터는 컨텍스트를 설정하여 모든 REST 컨트롤러 메서드가 가져 와서 실행할 수 있습니다.

커뮤니티의 의견을 듣고 제 설정이 올바른지 확인하겠습니다. 또한, 특정 문제가 있습니다 (적어도 IMO), 나는 그것에 직면하고 있습니다. 그래서 누군가가 올바른 방법으로 빛을 던지면 큰 도움이 될 것입니다.

다음은 구성 및 영역 디자인을 보여주는 코드 조각입니다.

니핏 1 ShiroConfiguration

private AuthenticationService authenticationService; 
/** 
* FilterRegistrationBean 
* @return 
*/ 
@Bean 
public FilterRegistrationBean filterRegistrationBean() { 
    FilterRegistrationBean filterRegistration = new FilterRegistrationBean(); 
    filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter")); 
    filterRegistration.setEnabled(true); 
    filterRegistration.setDispatcherTypes(DispatcherType.REQUEST); 
    filterRegistration.setOrder(1); 
    return filterRegistration; 
} 
@Bean(name = "securityManager") 
public DefaultWebSecurityManager securityManager() { 
    DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager(); 
    dwsm.setRealm(authenticationService()); 
    final DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); 
    // disable session cookie 
    sessionManager.setSessionIdCookieEnabled(false); 
    dwsm.setSessionManager(sessionManager); 
    return dwsm; 
} 

/** 
* @see org.apache.shiro.spring.web.ShiroFilterFactoryBean 
* @return 
*/ 
@Bean(name="shiroFilter") 
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager, JWTTimeoutProperties jwtTimeoutProperties, TokenUtil tokenUtil) { 
    ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); 
    bean.setSecurityManager(securityManager); 

    //TODO: Create a controller to replicate unauthenticated request handler 
    bean.setUnauthorizedUrl("/unauthor"); 

    Map<String, Filter> filters = new HashMap<>(); 
    filters.put("perms", new AuthenticationTokenFilter(jwtTimeoutProperties, tokenUtil)); 
    filters.put("anon", new AnonymousFilter()); 
    bean.setFilters(filters); 

    LinkedHashMap<String, String> chains = new LinkedHashMap<>(); 
    chains.put("/", "anon"); 
    chains.put("/favicon.ico", "anon"); 
    chains.put("/index.html", "anon"); 
    chains.put("/**/swagger-resources", "anon"); 
    chains.put("/api/**", "perms"); 

    bean.setFilterChainDefinitionMap(chains); 
    return bean; 
} 
@Bean 
@DependsOn(value="lifecycleBeanPostProcessor") 
public AuthenticationService authenticationService() { 
    if (authenticationService==null){ 
     authenticationService = new AuthenticationService(); 
    } 

    return authenticationService; 
} 


@Bean 
@DependsOn(value="lifecycleBeanPostProcessor") 
public Authorizer authorizer() { 
    return authenticationService(); 
} 


@Bean 
@DependsOn("lifecycleBeanPostProcessor") 
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { 
    DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator(); 
    proxyCreator.setProxyTargetClass(true); 
    return proxyCreator; 
} 

니핏 2 AuthenticationFilter

public class AuthenticationTokenFilter extends PermissionsAuthorizationFilter { 
@Override 
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException { 
    HttpServletRequest httpRequest = (HttpServletRequest) request; 
    String authorizationHeader = httpRequest.getHeader(TOKEN_HEADER); 
    String authToken; 

    String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName(); 
    httpRequest.setAttribute(alreadyFilteredAttributeName, true); 

    AuthenticationService.ensureUserIsLoggedOut(); // To not end up getting following error. 

    if (authorizationHeader != null && !authorizationHeader.isEmpty()) { 

     if (authorizationHeader.startsWith(BEARER_TOKEN_START_WITH)) { 
      authToken = authorizationHeader.substring(BEARER_TOKEN_START_INDEX); 
     } else if (authorizationHeader.startsWith(BASIC_TOKEN_START_WITH)) { 
      String caseId = UUID.randomUUID().toString(); 
      log.warn("{} Basic authentication is not supported but a Basic authorization header was passed in", caseId); 
      return false; 
     } else { 
      // if its neither bearer nor basic, default it to bearer. 
      authToken = authorizationHeader; 
     } 
     try { 
      if(tokenUtil.validateTokenAgainstSignature(authToken, jwtTimeoutProperties.getSecret())) { 
       Map<String, Object> outerClaimsFromToken = tokenUtil.getOuterClaimsFromToken(authToken, jwtTimeoutProperties.getSecret()); 

       JWTAuthenticationToken jwtAuthenticationToken = new JWTAuthenticationToken(outerClaimsFromToken.get(TokenUtil.CLAIM_KEY_USERID), 
               (String) outerClaimsFromToken.get(TokenUtil.CLAIM_KEY_INNER_TOKEN)); 
       SecurityUtils.getSubject().login(jwtAuthenticationToken); 

     } catch (JwtException | AuthenticationException ex) { 
      log.info("JWT validation failed.", ex); 
     } 
    } 
    return false; 
} 

니핏 3 TokenRestController

public Response getToken() { 

    AuthenticationService.ensureUserIsLoggedOut(); // To not end up getting following error. 
                 // org.apache.shiro.session.UnknownSessionException: There is no session with id 

     // TODO: In case of logging in with the organization, create own token class implementing HostAuthenticationToken class. 
     IAMLoginToken loginToken = new IAMLoginToken(authenticationRequestDTO.getUsername(), authenticationRequestDTO.getPassword()); 
     Subject subject = SecurityUtils.getSubject(); 
     try { 
      subject.login(loginToken); 
     } catch (AuthenticationException e) { 
      log.debug("Unable to login", e); 
      return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(null); 
     } 

     AuthenticatingUser user = (AuthenticatingUser) subject.getPrincipal(); 

      String authToken = authenticationService.generateToken(user); 
      return ResponseEntity.status(HttpStatus.OK).body(new AuthenticationResponseDTO(authToken)); 
    }); 
,

발췌문 4 : AuthorizingRealm

@Override 
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { 
    if (token instanceof IAMLoginToken) { 
     IAMLoginToken usernamePasswordToken = (IAMLoginToken) token; 

     UserBO user = identityManagerRepository.getUserByUsername(usernamePasswordToken.getUsername(), true); 

     if (user != null && user.getSecret() != null && !user.getSecret().isEmpty()) { 
      if(passwordEncoder.matches(String.valueOf(usernamePasswordToken.getPassword()), user.getPassword())) { 
       if (!isActive(user)) { 
        throw new AuthenticationException("User account inactive."); 
       } 
       return new SimpleAuthenticationInfo(toAuthenticatingUser(user).withSecret(user.getSecret()), usernamePasswordToken.getPassword(), getName()); 
      } 
     } 
    } else if (token instanceof JWTAuthenticationToken) { 
     JWTAuthenticationToken jwtToken = (JWTAuthenticationToken) token; 
     String userId = (String) jwtToken.getUserId(); 
     String secret = cache.getUserSecretById(userId, false); 

     if (secret != null && !secret.isEmpty()) { 
      Map<String, Object> tokenClaims = tokenUtil.getClaims(jwtToken.getToken(), secret); 
      String orgId = (String) tokenClaims.get(TokenUtil.CLAIM_KEY_ORG); 
      String email = (String) tokenClaims.get(TokenUtil.CLAIM_KEY_EMAIL); 
      String firstName = (String) tokenClaims.get(TokenUtil.CLAIM_KEY_FIRSTNAME); 
      String lastName = (String) tokenClaims.get(TokenUtil.CLAIM_KEY_LASTNAME); 
      Set<String> permissions = (Set<String>) tokenClaims.get(TokenUtil.CLAIM_KEY_PERMISSIONS); 

      return new SimpleAccount(new AuthenticatingUser(userId, orgId, email, firstName, lastName, permissions), jwtToken.getToken(), getName()); 
     } 
    } 

    throw new AuthenticationException("Invalid username/password combination!"); 
} 

@Override 
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 

    SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); 
    authorizationInfo.setStringPermissions(((AuthenticatingUser)principals.getPrimaryPrincipal()).getPermissions()); 
    return authorizationInfo; 
} 

문제 및 문제

  • 여기에서 언급 한 바와 같이 동일한 오류가 발생했습니다. Shiro complaining "There is no session with id xxx" with DefaultSecurityManager 기본적으로 Shiro는 세션 사용을 중지하거나 유효성을 검사하기를 원합니다. 그것을 성취 할 수있는 방법이 있습니까? 나는 대답에서 언급 한 것과 같은 수정을 구현하여 문제를 해결했다. 즉 ensureUserIsLoggedOut()은 무엇을 하는가?

  • 구성의 ShiroFilterFactoryBean 정의에서 알 수 있듯이 일부 필터 체인 정의를 설정하고 있습니다. 그리고 거기에 /api으로 시작하는 모든 API 호출 앞에 인증 필터가있을 것입니다. 하지만 문제는 예외를 추가하고 싶습니다. 예 : /api/v0/login은 그 중 하나입니다. 이것을 성취 할 수있는 방법이 있습니까?

  • 매우 제한된 문서와 비슷한 오픈 소스 프로젝트 샘플을 발견 했으므로 전반적인 구성이 적절한지 모르겠습니다.

의견을 환영합니다.

답변

0

토큰 필터를 'perms'필터와 분리해야합니다. BasicAuth 필터 또는 'authc'필터를 살펴보십시오. 그것은 당신이보고있는 문제를 해결하는 데 도움이 될 것입니다.당신은 기본적으로 'authz'필터를 사용하고 있습니다. (나는 여러분이 그 해결 방법을 필요로하는 이유라고 생각합니다)

+0

불행히도 나는 동일한 효과를 얻었습니다. 두 유형의 필터가 모두 작동하며 여전히 동일한 문제가 발생합니다. 하지만 'authc'필터를 사용하는 측면에서 옳습니다. 그것의 오른쪽 필터. –

1

Shiro가 Subject의 세션을 사용하여 Subject의 세션을 저장하는 것을 막음으로써 원하지 않는 세션 유효성 검사 및 관리의 첫 번째 문제를 해결했습니다. 모든 주제에 대한 요청/호출/메시지 전반에 걸친 상태

shiro 구성에서 내 세션 관리자에게 다음 구성을 적용하기 만하면됩니다. https://shiro.apache.org/session-management.html#disabling-subject-state-session-storage