如何使用 RESTEasy 显示用于基本身份验证的浏览器登录表单
How to show browser login form for basic authentication using RESTEasy
我目前正在使用 JAX-RS 特别是 Resteasy,因为它 "just works" 与 Wildfly 一起使用,我不需要配置任何东西。这真的是我使用它的唯一原因。
我已经实现了基本身份验证,期待以后用 OAuth2 替换它,只是为了简单起见现在才这样做。
ContainerRequestFilter 看起来像这样
@Provider
public class SecurityFilter implements ContainerRequestFilter {
private static final String AUTHORIZATION_HEADER_KEY = "Authorization";
private static final String AUTHORIZATION_HEADER_PREFIX = "Basic ";
@Override
public void filter(ContainerRequestContext containerRequestContext) throws IOException {
if(isAuthenticated(containerRequestContext) == false)
containerRequestContext.abortWith(createUnauthorizedResponse("Access denied."));
}
private boolean isAuthenticated(ContainerRequestContext containerRequestContext) {
List<String> authHeader = containerRequestContext.getHeaders().get(AUTHORIZATION_HEADER_KEY);
ResourceMethodInvoker methodInvoker = (ResourceMethodInvoker) containerRequestContext.getProperty("org.jboss.resteasy.core.ResourceMethodInvoker");
Method method = methodInvoker.getMethod();
RolesAllowed rolesAnnotation = method.getAnnotation(RolesAllowed.class);
if (authHeader != null && authHeader.size() > 0) {
String authToken = authHeader.get(0).replaceFirst(AUTHORIZATION_HEADER_PREFIX, "");
byte[] decoded = null;
try {
decoded = Base64.getDecoder().decode(authToken);
} catch (IllegalArgumentException ex) {
return false;
}
String decodedString = new String(decoded);
StringTokenizer tokenizer = new StringTokenizer(decodedString, ":");
String username = null, password = null;
if(tokenizer.countTokens() < 2)
return false;
username = tokenizer.nextToken();
password = tokenizer.nextToken();
if (DbController.isValid(username, password, rolesAnnotation.value()))
return true;
}
return false;
}
private Response createUnauthorizedResponse(String msg) {
return Response.status(Response.Status.UNAUTHORIZED)
.entity("{ \"Unauthorized\" : \"" + msg + "\" }")
.type(MediaType.APPLICATION_JSON)
.build();
}
}
邮递员工作正常。而且我确实意识到此类 api 的主要用途是在其他程序中。
但如果在浏览器中打开它会要求您输入您的凭据,而不是仅仅告诉您您未被授权,无法真正输入您的凭据,那就太好了。除非你耍花招手动把它放在 header 中,否则你也可以只使用邮递员。
如果我对 auth-constraint 角色管理员设置安全约束,它会提供一个登录对话框,但授权不起作用,它只是不断请求授权。
除了containerRequestContext.abortWith,我还能做些什么吗?或者我是否需要使用完全不同的方法,但它不适用于 ContainerRequestFilter?
您需要将 WWW-Authenticate
header 添加到您中止的响应中。 header 告诉浏览器它应该显示默认的浏览器登录表单。
private static final String CHALLENGE_FORMAT = "%s realm=\"%s\"";
private Response createUnauthorizedResponse() {
return Response.status(Response.Status.UNAUTHORIZED)
.header(HttpHeaders.WWW_AUTHENTICATE, String.format(CHALLENGE_FORMAT, "Basic", "Access"))
.type(MediaType.TEXT_PLAIN_TYPE)
.entity("Credentials are required to access this resource.")
.build();
下面是 Chrome
上的登录信息
我目前正在使用 JAX-RS 特别是 Resteasy,因为它 "just works" 与 Wildfly 一起使用,我不需要配置任何东西。这真的是我使用它的唯一原因。
我已经实现了基本身份验证,期待以后用 OAuth2 替换它,只是为了简单起见现在才这样做。
ContainerRequestFilter 看起来像这样
@Provider
public class SecurityFilter implements ContainerRequestFilter {
private static final String AUTHORIZATION_HEADER_KEY = "Authorization";
private static final String AUTHORIZATION_HEADER_PREFIX = "Basic ";
@Override
public void filter(ContainerRequestContext containerRequestContext) throws IOException {
if(isAuthenticated(containerRequestContext) == false)
containerRequestContext.abortWith(createUnauthorizedResponse("Access denied."));
}
private boolean isAuthenticated(ContainerRequestContext containerRequestContext) {
List<String> authHeader = containerRequestContext.getHeaders().get(AUTHORIZATION_HEADER_KEY);
ResourceMethodInvoker methodInvoker = (ResourceMethodInvoker) containerRequestContext.getProperty("org.jboss.resteasy.core.ResourceMethodInvoker");
Method method = methodInvoker.getMethod();
RolesAllowed rolesAnnotation = method.getAnnotation(RolesAllowed.class);
if (authHeader != null && authHeader.size() > 0) {
String authToken = authHeader.get(0).replaceFirst(AUTHORIZATION_HEADER_PREFIX, "");
byte[] decoded = null;
try {
decoded = Base64.getDecoder().decode(authToken);
} catch (IllegalArgumentException ex) {
return false;
}
String decodedString = new String(decoded);
StringTokenizer tokenizer = new StringTokenizer(decodedString, ":");
String username = null, password = null;
if(tokenizer.countTokens() < 2)
return false;
username = tokenizer.nextToken();
password = tokenizer.nextToken();
if (DbController.isValid(username, password, rolesAnnotation.value()))
return true;
}
return false;
}
private Response createUnauthorizedResponse(String msg) {
return Response.status(Response.Status.UNAUTHORIZED)
.entity("{ \"Unauthorized\" : \"" + msg + "\" }")
.type(MediaType.APPLICATION_JSON)
.build();
}
}
邮递员工作正常。而且我确实意识到此类 api 的主要用途是在其他程序中。
但如果在浏览器中打开它会要求您输入您的凭据,而不是仅仅告诉您您未被授权,无法真正输入您的凭据,那就太好了。除非你耍花招手动把它放在 header 中,否则你也可以只使用邮递员。
如果我对 auth-constraint 角色管理员设置安全约束,它会提供一个登录对话框,但授权不起作用,它只是不断请求授权。
除了containerRequestContext.abortWith,我还能做些什么吗?或者我是否需要使用完全不同的方法,但它不适用于 ContainerRequestFilter?
您需要将 WWW-Authenticate
header 添加到您中止的响应中。 header 告诉浏览器它应该显示默认的浏览器登录表单。
private static final String CHALLENGE_FORMAT = "%s realm=\"%s\"";
private Response createUnauthorizedResponse() {
return Response.status(Response.Status.UNAUTHORIZED)
.header(HttpHeaders.WWW_AUTHENTICATE, String.format(CHALLENGE_FORMAT, "Basic", "Access"))
.type(MediaType.TEXT_PLAIN_TYPE)
.entity("Credentials are required to access this resource.")
.build();
下面是 Chrome
上的登录信息