Glassfish @RolesAllowed с пользовательским SecurityContext
Вопрос, который я собираюсь задать, немного сложен, и я пока не нашел ответа. Возможно, потому что я ищу не ту вещь. Но я надеюсь, что вы поможете мне в этом.
Я использовал следующий учебник для реализации пользовательского SecurityContext, который использует токены вместо базовой аутентификации пользователя / пароля.
По сути, он инициализирует и внедряет ResourceFilterFactory, который сам внедряет ResourceFilter при каждом HTTP-запросе, отправляемом приложению.
Этот ResourceFilter ищет в запросе заголовок "Аутентификация", получает его содержимое и затем аутентифицирует пользователя. Если пользователь аутентифицирован, он вводится в запрос с использованием 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 // Этот EJB вызывается при каждом HTTP-запросе, который получает сервер. Он проверяет заголовок "Аутентификация" и получает пользователя, связанного с токеном.
@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("lol@lol.lol");
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 // Этот класс внедряется в запрос и имеет метод isUserInRole(), который должен использоваться аннотацией @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 // Сущность, созданная и заполненная пользовательской информацией базы данных.
@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...
}
Наконец веб-сервис Джерси:
@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 работает хорошо, я получаю экземпляр ExternalUser, используя введенный SecurityContext. Но если я использую @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()
метод вручную, но я бы просто избежал проблемы вместо того, чтобы столкнуться с ней. Извините за длинный пост, но теперь я думаю, что у вас есть вся информация, необходимая, чтобы помочь мне в этом. Спасибо заранее за вашу помощь.
Эмерик
2 ответа
Хорошо, я только что закончил эту часть (на самом деле все части, за исключением EmailGateway).
Во-первых, я говорю спасибо Иану Портеру за его работу - ее высокое качество. И я прошу прощения за то, что по ошибке назвал его просто "Портер".
Что-то происходит с форматированием кода Stackru, так что имейте в виду, что некоторый код обрабатывает поля кода.
Для вашей проблемы я сделал это:
- web.xml - я не использую кроме Faces
Вместо 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 чтобы узнать, почему вам нужно зарегистрировать классы, даже если они помечены @Provider
- ResourceFilterFactory.java - я не использовал
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; } }
SecurityContextImpl такой же, как определил Iain:
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; } }
UserResource, как определил Iain, похож на ваш UserRestService:
@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(); } ...
Как насчет вашего 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>