2013-07-05 162 views
4

我要問的問題有點棘手,我還沒有找到任何答案。也許是因爲我在找錯誤的東西。但我希望你能幫助我。Glassfish @RolesAllowed with custom SecurityContext

我使用following tutorial來實現自定義的SecurityContext,它使用令牌而不是基本的用戶/密碼認證。

基本上它初始化並注入一個ResourceFilterFactory,它本身會在發送給應用程序的每個HTTP請求中注入一個ResourceFilter。

此ResourceFilter在請求中搜索「Authentication」標頭,獲取其內容,然後對用戶進行身份驗證。如果用戶通過身份驗證,則使用SecurityContext將其注入到請求中。

我轉換了代碼以使其作爲EJB工作。

下面的代碼:

的web.xml

<init-param> 
<param-name>com.sun.jersey.spi.container.ResourceFilters</param-name> 
<param-value>com.myapp.rest.filter.ResourceFilterFactory</param-value> 
</init-param> 

ResourceFilterFactory.java //這個類的注入EJB SecurityContextFilter

@Named 
@Stateless 
@LocalBean 
public class ResourceFilterFactory extends RolesAllowedResourceFilterFactory{ 

    @EJB(name="securityContextFilter") 
    private SecurityContextFilter securityContextFilter; 

    @PostConstruct 
    private void init(){ 
     System.out.println("ResourceFilterFactory initialized"); 
    } 

    @Override 
     public List<ResourceFilter> create(AbstractMethod am) { 
     //System.out.println("Creating resource filters list"); 
      List<ResourceFilter> filters = super.create(am); 
      if (filters == null) { 
       filters = new ArrayList<ResourceFilter>(); 
      } 

      List<ResourceFilter> securityFilters = new ArrayList<ResourceFilter>(filters); 
      securityFilters.clear(); 
      securityFilters.add(0, securityContextFilter); 

      return securityFilters; 
    } 
} 

SecurityContextFilter.java// 這個 在服務器接收到的每個HTTP請求時調用EJB。 它檢查「身份驗證」標題並獲取與令牌關聯的用戶。

@Named("securityContextFilter") 
@Stateless 
@LocalBean 
@Provider 
public class SecurityContextFilter implements ResourceFilter, ContainerRequestFilter { 

    protected static final String HEADER_AUTHORIZATION = "Authorization"; 

    @EJB 
    private AuthorizationService authorizationService; 

    @Override 
    public ContainerRequest filter(ContainerRequest req) { 
     System.out.println("Auth header: " + req.getHeaderValue(HEADER_AUTHORIZATION)); 
     String sessionToken = req.getHeaderValue(HEADER_AUTHORIZATION); 
     UserEntity entity = null; 
     try { 
      //entity = authorizationService.getParaUsingSessionToken(sessionToken); 
      // removing the part that retrieves the user from the database 
      ExternalUser user = new ExternalUser(); 
      user.setEmailAddress("[email protected]"); 
      user.setFirstName("lol"); 
      user.setLastName("LOL"); 
      user.setRole("arole"); 
      req.setSecurityContext(new org.company.server.rest.filter.SecurityContextImpl(user)); 
     } catch (AuthenticationException e) { 
      System.out.println("authentication exception"); 
      ExternalUser user = new ExternalUser(); 
      req.setSecurityContext(new org.company.server.rest.filter.SecurityContextImpl(user)); 
     } 

     return req; 
    } 

    @Override 
    public ContainerRequestFilter getRequestFilter() { 
     return this; 
    } 

    @Override 
    public ContainerResponseFilter getResponseFilter() { 
     return null; 
    } 

} 

SecurityContextImpl.java //這類被注入到該請求並具有應由@RolesAllowed註解可以使用

public class SecurityContextImpl implements SecurityContext { 


    private final ExternalUser user; 

    public SecurityContextImpl(ExternalUser user) { 
     //System.out.println("SecurityContext created : " + user.getFirstName()); 
     this.user = user; 
    } 

    public Principal getUserPrincipal() { 
     return user; 
    } 

    public boolean isUserInRole(String role) { 
     System.out.println("Checking access rights : " + role + "/" + this.user.getRole()); 
     return user.getRole().equalsIgnoreCase(role); 
    } 

    public boolean isSecure() { 
     return false; 
    } 

    public String getAuthenticationScheme() { 
     return SecurityContext.BASIC_AUTH; 
    } 
} 

ExternalUser.java所述的isUserInRole()方法//該實體創建並填充數據庫用戶信息。

@XmlRootElement 
public class ExternalUser implements Principal { 

    private String id; 
    private String firstName; 
    private String lastName; 
    private String emailAddress; 
    private boolean isVerified; 
    private String phoneNumber; 
    private String professionalId; 
    private String role; 

    public ExternalUser() {} 

    public ExternalUser(UserEntity user) { 
     this.setEmailAddress(user.getEmailAddress()); 
     this.setFirstName(user.getFirstName()); 
     this.setLastName(user.getLastName()); 
     this.setRole(user.getRole().toString()); 
     this.setPhoneNumber(user.getPhoneNumber()); 
     this.setProfessionalId(user.getProfessionnalID()); 
    } 
    // Getters and setters boilerplate code... 
} 

最後澤西WebService的:

@Path("/account") 
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 
@Stateless 
@LocalBean 
public class UserRestService { 

@Context 
private SecurityContext security; 

     @GET 
     @Path("info") 
     @Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_XML }) 
     public Response getInfo() { 
     ExternalUser user = (ExternalUser)security.getUserPrincipal(); 
     System.out.println("Email Address Of User : " + user.getEmailAddress()); 
      if (!security.isUserInRole("arole")){ 
       return Response.status(403).build(); 
      } 
     return Response.ok(user).build(); 
     } 
} 

WebService的效果很好,我得到使用注入的SecurityContext的ExternalUser實例。但是,如果我用的是@RolesAllowed({"arole"})註釋,Glassfish的給了我這個錯誤:

INFO: JACC Policy Provider:Failed Permission Check: context (" org.company.app.server/org_company_app_server_internal ") , permission (" ("javax.security.jacc.EJBMethodPermission" "UserRestService" "getInfo,Local,org.company.server.rest.models.authentication.RestSession") ") 
WARNING: EJB5184:A system exception occurred during an invocation on EJB UserRestService, method: public javax.ws.rs.core.Response org.company.server.rest.services.UserRestService.getInfo() 
WARNING: javax.ejb.AccessLocalException: Client not authorized for this invocation 
    at com.sun.ejb.containers.BaseContainer.preInvoke(BaseContainer.java:1888) 
    at com.sun.ejb.containers.EJBLocalObjectInvocationHandler.invoke(EJBLocalObjectInvocationHandler.java:212) 
    at com.sun.ejb.containers.EJBLocalObjectInvocationHandlerDelegate.invoke(EJBLocalObjectInvocationHandlerDelegate.java:88) 
    at com.sun.proxy.$Proxy140.getInfoPara(Unknown Source) 
    at org.company.server.rest.services.__EJB31_Generated__UserRestService__Intf____Bean__.getInfoPara(Unknown Source) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
    at java.lang.reflect.Method.invoke(Method.java:601) 
    at com.sun.jersey.spi.container.JavaMethodInvokerFactory$1.invoke(JavaMethodInvokerFactory.java:60) 
    at com.sun.jersey.server.impl.model.method.dispatch.AbstractResourceMethodDispatchProvider$ResponseOutInvoker._dispatch(AbstractResourceMethodDispatchProvider.java:205) 
    at com.sun.jersey.server.impl.model.method.dispatch.ResourceJavaMethodDispatcher.dispatch(ResourceJavaMethodDispatcher.java:75) 
    at com.sun.jersey.server.impl.uri.rules.HttpMethodRule.accept(HttpMethodRule.java:288) 
    at com.sun.jersey.server.impl.uri.rules.RightHandPathRule.accept(RightHandPathRule.java:147) 
    at com.sun.jersey.server.impl.uri.rules.ResourceClassRule.accept(ResourceClassRule.java:108) 
    at com.sun.jersey.server.impl.uri.rules.RightHandPathRule.accept(RightHandPathRule.java:147) 
    at com.sun.jersey.server.impl.uri.rules.RootResourceClassesRule.accept(RootResourceClassesRule.java:84) 
    at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:1469) 
    at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:1400) 
    at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:1349) 
    at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:1339) 
    at com.sun.jersey.spi.container.servlet.WebComponent.service(WebComponent.java:416) 
    at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:537) 
    at com.sun.jersey.spi.container.servlet.ServletContainer.doFilter(ServletContainer.java:895) 
    at com.sun.jersey.spi.container.servlet.ServletContainer.doFilter(ServletContainer.java:843) 
    at com.sun.jersey.spi.container.servlet.ServletContainer.doFilter(ServletContainer.java:804) 
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:256) 
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:217) 
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:279) 
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175) 
    at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:655) 
    at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:595) 
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:161) 
    at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:331) 
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:231) 
    at com.sun.enterprise.v3.services.impl.ContainerMapper$AdapterCallable.call(ContainerMapper.java:317) 
    at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:195) 
    at com.sun.grizzly.http.ProcessorTask.invokeAdapter(ProcessorTask.java:849) 
    at com.sun.grizzly.http.ProcessorTask.doProcess(ProcessorTask.java:746) 
    at com.sun.grizzly.http.ProcessorTask.process(ProcessorTask.java:1045) 
    at com.sun.grizzly.http.DefaultProtocolFilter.execute(DefaultProtocolFilter.java:228) 
    at com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:137) 
    at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:104) 
    at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:90) 
    at com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:79) 
    at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:54) 
    at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:59) 
    at com.sun.grizzly.ContextTask.run(ContextTask.java:71) 
    at com.sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:532) 
    at com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:513) 
    at java.lang.Thread.run(Thread.java:722) 

WARNING: StandardWrapperValve[default]: PWC1406: Servlet.service() for servlet default threw exception 
javax.ejb.AccessLocalException: Client not authorized for this invocation 
    at com.sun.ejb.containers.BaseContainer.preInvoke(BaseContainer.java:1888) 
    at com.sun.ejb.containers.EJBLocalObjectInvocationHandler.invoke(EJBLocalObjectInvocationHandler.java:212) 
    at com.sun.ejb.containers.EJBLocalObjectInvocationHandlerDelegate.invoke(EJBLocalObjectInvocationHandlerDelegate.java:88) 
    at com.sun.proxy.$Proxy140.getInfoPara(Unknown Source) 
    at org.company.server.rest.services.__EJB31_Generated__UserRestService__Intf____Bean__.getInfoPara(Unknown Source) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
    at java.lang.reflect.Method.invoke(Method.java:601) 
    ... 

我想我需要配置GlassFish,使其使用我創建的SecurityContextImpl。我不知道爲什麼它不能正常工作,我可以在我的代碼中調用它的方法。

我可以手動使用isUserInRole()方法,但我會避免這個問題,而不是面對它。 對於這篇較長的文章,我很抱歉,但現在我認爲你已經掌握了所有必要的信息來幫助我。預先感謝您的幫助。

Emeric

+0

爲了記錄和搜索,有問題的教程編寫REST服務在Java中 - http://porterhead.blogspot.co.uk/2013/05/writing-rest-services -in-java-part-1.html – HankCa

回答

3

好吧我剛剛完成了那部分(實際上除了EmailGateway以外的所有部分)。

首先我要感謝伊恩波特他的工作 - 它的高品質。我很抱歉,以前只是將他誤稱爲'波特'。

有一些與Stackoverflow代碼格式有關的事情,所以請注意一些代碼繼續代碼框。

對於您的問題,我這樣做:

  1. web.xml中 - 我不只是面孔
  2. 相反的web.xml和登記我創建了一個類的各種供應商使用AuthApplicationConfig.java

    @ApplicationPath("rest") 
    public class AuthApplicationConfig extends Application { 
    
        @Override 
        public Set<Class<?>> getClasses() { 
         Set<Class<?>> resources = new java.util.HashSet<>(); 
    
         // REST resources 
         resources.add(HealthCheckResource.class); 
         resources.add(PasswordResource.class); 
         resources.add(UserResource.class); 
         resources.add(VerificationResource.class); 
    
         // Filters (Auth) 
         resources.add(RolesAllowedDynamicFeature.class); 
         resources.add(SecurityContextFilter.class); 
    
         // Misc 
         resources.add(GenericExceptionMapper.class); 
         return resources; 
        } 
    } 
    

https://java.net/jira/browse/JERSEY-1634爲什麼您需要註冊的類別,即使他們被註釋w ^第i個@Provider

  1. ResourceFilterFactory.java - 我沒有使用
  2. SecurityContextFilter.java略有不同:

    @Provider 
    @Priority(Priorities.AUTHENTICATION) // So it comes in before org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature 
    
    public class SecurityContextFilter implements ContainerRequestFilter { 
        @Inject 
        Logger logger; 
    
        protected static final String HEADER_AUTHORIZATION = "Authorization"; 
    
        protected static final String HEADER_DATE = "x-java-rest-date"; 
    
        protected static final String HEADER_NONCE = "nonce"; 
    
        private AuthorizationService authorizationService; 
    
        ApplicationConfig config; 
    
        @Inject 
        public SecurityContextFilter(UserRepository userRepository, 
          UserService userService, ApplicationConfig config) { 
         delegateAuthorizationService(userRepository, userService, config); 
         this.config = config; 
    
        } 
    
        /** 
        * If there is an Authorisation header in the request extract the session 
        * token and retrieve the user 
        * 
        * Delegate to the AuthorizationService to validate the request 
        * 
        * If the request has a valid session token and the user is validated then a 
        * user object will be added to the security context 
        * 
        * Any Resource Controllers can assume the user has been validated and can 
        * merely authorize based on the role 
        * 
        * Resources with @PermitAll annotation do not require an Authorization 
        * header but will still be filtered 
        * 
        * @param request 
        *   the ContainerRequest to filter 
        * 
        */ 
        @Override 
        public void filter(ContainerRequestContext requestContext) 
          throws IOException { 
         System.out.println("SecurityContextFilter/filter("+printContainerRequestContext(requestContext)+")"); 
         String authToken = requestContext.getHeaderString(HEADER_AUTHORIZATION); 
         String requestDateString = requestContext.getHeaderString(HEADER_DATE); 
         String nonce = requestContext.getHeaderString(HEADER_NONCE); 
         AuthorizationRequestContext context = new AuthorizationRequestContext(
           requestContext.getUriInfo().getPath(), 
           requestContext.getMethod(), requestDateString, nonce, authToken); 
         ExternalUser externalUser = authorizationService.authorize(context); 
         requestContext 
           .setSecurityContext(new SecurityContextImpl(externalUser)); 
         System.out.println(String.format(" END OF SecurityContextFilter/filter - AuthorizationRequestContext: %s, externalUser:%s", context,externalUser)); 
        } 
    
        private String printContainerRequestContext(ContainerRequestContext requestContext) { 
         return String.format("[ContainerRequestContext:%s]", requestContext); 
        } 
    
        /** 
        * Specify the AuthorizationService that the application should use 
        * 
        * @param userRepository 
        * @param userService 
        * @param config 
        */ 
        private void delegateAuthorizationService(UserRepository userRepository, 
          UserService userService, ApplicationConfig config) { 
         System.out.println("SecurityContextFilter - requireSignedRequests?"+config.requireSignedRequests()); 
         if (config.requireSignedRequests()) { 
          this.authorizationService = new RequestSigningAuthorizationService(
            userRepository, userService, config); 
         } else { 
          this.authorizationService = new SessionTokenAuthorizationService(
            userRepository); 
         } 
        } 
    
        @Inject 
        public void setConfig(ApplicationConfig config) { 
         this.config = config; 
        } 
    } 
    
  3. SecurityContextImpl是伊恩定義它一樣的:

    public class SecurityContextImpl implements SecurityContext { 
    
        private final ExternalUser user; 
    
        public SecurityContextImpl(ExternalUser user) { 
         this.user = user; 
        } 
    
        @Override 
        public Principal getUserPrincipal() { 
         return user; 
        } 
    
        @Override 
        public boolean isUserInRole(String role) { 
         if(role.equalsIgnoreCase(Role.anonymous.name())) { 
          return true; 
         } 
         if(user == null) { 
          throw new InvalidAuthorizationHeaderException(); 
         } 
         System.out.println(String.format("SecurityContextImpl/isUserInRole - role:%s, user:%s", role, user)); 
         return user.getRole().equalsIgnoreCase(role); 
        } 
    
        @Override 
        public boolean isSecure() { 
         return false; 
        } 
    
        @Override 
        public String getAuthenticationScheme() { 
         return SecurityContext.BASIC_AUTH; 
        } 
    } 
    
  4. UserResource是Iain定義的,類似於您的UserRest服務:

    @Path("/user") 
    // @Component 
    @Produces({ MediaType.APPLICATION_JSON }) 
    @Consumes({ MediaType.APPLICATION_JSON }) 
    @RequestScoped 
    public class UserResource { 
        // A Social thing that is not needed 
        // private ConnectionFactoryLocator connectionFactoryLocator; 
        @Inject 
        Logger logger; 
    
        @Inject 
        protected UserService userService; 
    
        @Inject 
        protected VerificationTokenService verificationTokenService; 
    
        @Inject 
        protected EmailServicesGateway emailServicesGateway; 
    
        @Context 
        protected UriInfo uriInfo; 
    
        // @Inject 
        // protected ApplicationConfig config; 
    
        // @Autowired 
        // public UserResource(ConnectionFactoryLocator connectionFactoryLocator) { 
        // this.connectionFactoryLocator = connectionFactoryLocator; 
        // } 
    
        @PermitAll 
        @POST 
        public Response signupUser(CreateUserRequest request) { 
         AuthenticatedUserToken token = userService.createUser(request, Role.authenticated); 
         verificationTokenService.sendEmailRegistrationToken(token.getUserId()); 
         URI location = uriInfo.getAbsolutePathBuilder().path(token.getUserId()).build(); 
         return Response.created(location).entity(token).build(); 
        } 
    
        @RolesAllowed("admin") 
        @Path("{userId}") 
        @DELETE 
        public Response deleteUser(@Context SecurityContext sc, @PathParam("userId") String userId) { 
         ExternalUser userMakingRequest = (ExternalUser) sc.getUserPrincipal(); 
         userService.deleteUser(userMakingRequest, userId); 
         return Response.ok().build(); 
        } 
        ... 
    
+0

對不起,我回答之前花了一段時間,但您的解決方案似乎在Glassfish上運行良好!謝謝! – mrik974

+0

令牌在Jersey Response對象中發送後如何存儲在客戶端? – oberger

+0

從內存中它是在一個cookie。 – HankCa

1

你的web.xml呢?

你已經把這個過濾器放到web.xml中了嗎?

<init-param> 
     <param-name>com.sun.jersey.spi.container.ContainerRequestFilters</param-name> 
     <param-value>com.yourcompany.filter.SecurityFilter</param-value> 
    </init-param> 
    <init-param> 
     <param-name>com.sun.jersey.spi.container.ResourceFilters</param-name> 
     <param-value>com.sun.jersey.api.container.filter.RolesAllowedResourceFilterFactory</param-value> 
    </init-param> 
+0

'​​com.sun.jersey.spi.container.ResourceFilters'被設置爲' com.myapp.rest.filter.ResourceFilterFactory',但'ContainerRequestFilters'參數未設置。由於我不得不前進,我決定在每次web服務調用時都進行手動安全檢查。我會盡快解決您的問題。謝謝你回答,我會讓你保持更新。 – mrik974

+0

我現在正在做這個完全相同的端口,就像你在這裏討論的一樣 - 從Java EE 7下的Spring Framework到EJB。我沒有意識到在這種轉換中有多少細節。無論如何,我是最主要的 - 除了我剛剛開始研究的Filters之外,所有測試都通過了。您可能正在使用基礎軟件包名稱爲「org.glassfish.jersey」的Jersey2(Porter使用了Jersey 1.1)。請注意有一些差異。 https://jersey.java.net/documentation/latest/migration.html是遷移指南。 – HankCa

+0

我的意思是'可能使用澤西島2'而不是'可能'。你會使用Java 7.我相信你也可以用'@ Provider'來註釋任何這樣的資源並添加到你的'javax.ws.rs.core中。應用程序子類''getClasses()'。而不是添加一個web.xml來提供該init-param。 – HankCa

相關問題