[英]Using Keycloak for Wildfly Applications - Authentication Error
[英]Bearer only authentication in Wildfly without using Keycloak
我想在 Wildfly 中自己实现 Bearer-only 身份验证。 本质上,我将执行以下步骤:
当我收到一个请求时,我会检查它是否有一个 Authorization 标头。
我获取令牌并检查数据库(在这种情况下,我将使用 Redis)检查它的有效性。
我从数据库中获取该用户的角色。
我希望能够在我的休息服务上使用@RolesAllowed
注释。
我该怎么做? 如何修改 Wildfly 配置文件? 我需要实现哪些接口? 如何将用户的角色传递给安全上下文,以便@RolesAllowed
为我检查 @RolesAllowed?
如果回答,请考虑我是一位经验丰富的 Java 程序员,但对 Wildfly 不熟悉,因此您可以跳过有关编程逻辑的详细信息,但不能跳过有关 Wildfly 配置的详细信息。 同样在您的回答中,不要担心令牌首先是如何到达 Redis 的,或者客户端是如何获得它的。
编辑
这就是我所做的,但还没有运气。 我已经实现了一个实现ContainerRequestFilter
的AuthenticationFilter
。 (我在下面只包括我已经实现的主要过滤器功能。请注意,有一些从数据库中获取角色的辅助功能不包括在内)。 即使在函数结束时,我使用用户配置文件(包含角色)设置请求上下文的安全上下文,我也无法在我的 JAX-RS 休息服务上使用@RolesAllowed
注释。 关于我应该做什么的任何指示?
注意:我没有修改任何 Wildfly 配置文件或 web.xml 文件。 我知道每个请求都会调用过滤器,因为我能够在每个请求上记录来自它的消息。
/**
* (non-Javadoc)
* @see javax.ws.rs.container.ContainerRequestFilter#filter(javax.ws.rs.container.ContainerRequestContext)
*/
@Override
public void filter(ContainerRequestContext requestContext) {
//1. Read the JSON web token from the header
String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
return;
}
String token = authorizationHeader.substring("Bearer".length()).trim();
try{
//Note that if the token is not in the database,
//an exception will be thrown and we abort.
UserProfile userProfile = this.getUserProfile(token);
if (null == userProfile){
userProfile = this.decodeToken(token);
}
if (null == userProfile){
throw new Exception();
}
String role = userProfile.getUserRole();
if (null == role){
role = this.getRoleFromMod(userProfile);
if (null == role){
role = RoleType.READ_ONLY;
}
userProfile.setUserRole(role);
this.updateUserProfileForToken(token, userProfile);
}
userProfile.setUserRole(role);
//5. Create a security context class that implements the crazy interface
//and set it here.
requestContext.setSecurityContext(new ModSecurityContext(userProfile));
}
catch(Exception e){
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
}
}
是的,我不确定它在EE环境中如何工作,甚至使资源类成为无状态的Bean。 @RolesAllowed
批注旨在用于ejb。 在这种情况下,从Servlet请求中检索主体(我相信)。 我要做的只是实现您自己的授权过滤器,该过滤器将查找注释并对照安全上下文中的主体进行检查。
您可以看到Jersey如何实现它 。 除了AnnotatedMethod
类之外,没有什么东西是真正针对泽西岛的。 为此,您可以使用java.lang.reflect.Method
(resourceInfo.getResourceMethod())进行一些反射。 除此之外,您几乎可以照原样复制代码。 完成后,只需向应用程序注册RolesAllowedDynamicFeature
。 或者只是使用@Provider
对其进行注释以进行扫描。
您还需要确保使用@Priority(Priorities.AUTHENTICATION)
注释认证过滤器,以便在使用@Priority(Priorities.AUTHORIZATION)
注释的授权过滤器之前调用它。
这是我链接到的代码的重构,因此它不使用Jersey特定的类。 AnnotatedMethod
更改为Method
。
@Provider
public class RolesAllowedFeature implements DynamicFeature {
@Override
public void configure(ResourceInfo resourceInfo, FeatureContext configuration) {
Method resourceMethod = resourceInfo.getResourceMethod();
if (resourceMethod.isAnnotationPresent(DenyAll.class)) {
configuration.register(new RolesAllowedRequestFilter());
return;
}
RolesAllowed ra = resourceMethod.getAnnotation(RolesAllowed.class);
if (ra != null) {
configuration.register(new RolesAllowedRequestFilter(ra.value()));
return;
}
if (resourceMethod.isAnnotationPresent(PermitAll.class)) {
return;
}
ra = resourceInfo.getResourceClass().getAnnotation(RolesAllowed.class);
if (ra != null) {
configuration.register(new RolesAllowedRequestFilter(ra.value()));
}
}
@Priority(Priorities.AUTHORIZATION) // authorization filter - should go after any authentication filters
private static class RolesAllowedRequestFilter implements ContainerRequestFilter {
private final boolean denyAll;
private final String[] rolesAllowed;
RolesAllowedRequestFilter() {
this.denyAll = true;
this.rolesAllowed = null;
}
RolesAllowedRequestFilter(final String[] rolesAllowed) {
this.denyAll = false;
this.rolesAllowed = (rolesAllowed != null) ? rolesAllowed : new String[]{};
}
@Override
public void filter(final ContainerRequestContext requestContext) throws IOException {
if (!denyAll) {
if (rolesAllowed.length > 0 && !isAuthenticated(requestContext)) {
throw new ForbiddenException("Not Authorized");
}
for (final String role : rolesAllowed) {
if (requestContext.getSecurityContext().isUserInRole(role)) {
return;
}
}
}
throw new ForbiddenException("Not Authorized");
}
private static boolean isAuthenticated(final ContainerRequestContext requestContext) {
return requestContext.getSecurityContext().getUserPrincipal() != null;
}
}
}
首先,让我解释一下DynamicFeature
工作原理。 为此,我们首先将讨论的上下文更改为AuthenticationFilter
当前实现。
现在,它是为每个请求处理的过滤器。 但是,假设我们引入了自定义@Authenticated
批注
@Target({METHOD, TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Authenticated{}
我们可以使用此注释来注释不同的方法和类。 为了使过滤器仅过滤带注释的方法和类,我们可以引入DynamicFeature
来检查注释,然后仅在找到注释时注册过滤器。 例如
@Provider
public class AuthenticationDynamicFeature implements DynamicFeature {
@Override
public void configure(ResourceInfo resourceInfo, FeatureContext configuration) {
if (resourceInfo.getResourceMethod().isAnnotationPresent(Authenticated.class)) {
configuration.register(new AuthenticationFilter());
return;
}
if (resourceInfo.getResourceClass().isAnnotationPresent(Authenticated.class)) {
configuration.register(new AuthenticationFilter());
}
}
}
一旦我们注册了AuthenticationDynamicFeature
类,它将进行注册,以便仅使用@Authenticated
注释的方法和类将被过滤。
另外,这甚至可以在过滤器内完成。 我们可以从AuthenticationFilter
获取对ResourceInfo
的引用。 例如,检查注解(如果不存在),然后继续。
@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
@Context
private ResourceInfo resourceInfo;
@Override
public void filter(ContainerRequestContext context) throws IOException {
boolean hasAnnotation = false;
if (resourceInfo.getResourceMethod().isAnnotationPresent(Authenticated.class)
|| resourceInfo.getResourceClass().isAnnotationPresent(Authenticated.class)) {
hasAnnotation = true;
}
if (!hasAnnotation) return;
// process authentication is annotation is present
这样,我们就可以完全忘记DynamicFeature
。 最好只使用DynamicFeature
,我只是举一个例子进行演示。
话虽这么说,如果我们使用RolesAllowedDynamicFeature
代码,则可以更好地了解发生了什么。 它仅为使用@RolesAllowed
和@DenyAll
注释的方法和类注册过滤器。 您甚至可以对其进行重构,以将所有注释逻辑(而不是功能)包含在过滤器中。 您只有过滤器。 就像我对上面的AuthenticationFilter
示例所做的一样。 同样,这只是出于示例目的。
现在,就DynamicFeature
的注册而言,它的工作方式与注册任何其他资源类或提供程序类(例如,身份验证筛选器)相同。 因此,无论您如何注册,都可以以相同的方式注册RolesAllowedDynamicFeature
。 在扫描过程中,将扫描@Path
和@Provider
批注。 如果这是当前使用的功能,则只需使用@Provider
注释要素类@Provider
注册它。 例如,只有一个空的Application
子类将导致扫描发生
@ApplicationPath("/api")
public class RestApplication extends Application {}
然后,在您的Application
子类中进行了显式注册。 例如
@ApplicationPath("/api")
public class RestApplication extends Application {
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<>();
classes.add(AuthenticationFilter.class);
classes.add(RolesAllowedFeature.class);
classes.add(SomeResource.class);
return classes;
}
}
请注意,执行此操作时,将禁用所有继续进行的扫描。
因此,通过其他几件事可以确保上述所有内容都清楚了,但仍然无法正常工作。
确保使用@Priority(Priorities.AUTHENTICATION)
注释当前的AuthenticationFilter
。 这是为了确保在授权过滤器之前调用身份验证过滤器。 这需要发生,因为身份验证过滤器是设置安全上下文的工具,而授权过滤器会对其进行检查。
确保正确创建安全上下文。 授权过滤器将调用SecurityContext.isUserInRole(role)
,以从@RolesAllowed
批注中传入角色。 因此,您需要确保正确实现isUserInRole
。
如果我有积分,我会支持上面的帖子 xD
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.