2017-11-22 5 views
1

私はSpring Bootアプリケーションを持っており、Apache shiroをそのアプリケーションと統合しようとしました。最初の反復として、私はJWTの方法を認証し、許可しています。セッションはまったくありません。JWTのSpring BootとApache Shiro - 正しく使用していますか?

私がそれを構築したやり方は、すべてのREST要求には検証が必要なJWTヘッダーが含まれている必要があります。私は白ろ紙でそれをやっています。検証後、フィルターはコンテキストを設定し、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'フィルタを使用するという点で正しいです。そのフィルタ。 –

関連する問題