将@RolesAllowed 注释应用于存储在数据库中的现有用户
Apply @RolesAllowed annotations to existing users stored in database
TLDR
在我开发的 Java Web 应用程序中,需要实现对 REST 服务的用户区分。我知道有一些注释(@RolesAllowed
、@PermitAll
、@DenyAll
)可以描述哪个角色可以使用该服务。我的问题是,如何将 @RolesAllowed
中的角色与用户角色枚举匹配,该用户是存储在数据库中的持久对象?
许多教程解释了 @RolesAllowed
,但我发现 none 与已经创建的角色匹配
说明
如何通过自动检查用户的角色(通过会话 ID 找到)来验证用户?我知道 Jersey 已经这样做了,通过注册 RolesAllowedDynamicFeature.class
。我已经设法通过将 @DenyAll
注释放入方法中来检查此 RolesAllowedDynamicFeature.class
是否有效,并返回了 403 错误。
首先,我们先从什么是用户说起。用户是数据库实体,使用 Ebean
将它们从 Java 对象中持久化。这是来自 User.class
:
的示例
/*
* A sample that describes the fields that I ask for and to understand the concept
*/
@Entity
@Table(name = "users")
public class User extends Model
{
@Id
@GeneratedValue
@Column(name = "id")
private long id;
@Enumerated(EnumType.STRING)
@Column(name = "role", nullable = false)
private UserRole role;
// contructors getters setters helper methods etc
/**
* Fetch a user from DB
*
* @param id the id to search for
* @return a Person.class object or may return null
*/
public static User getUserById(Long id)
{
return Ebean.find(User.class, id);
}
/* and here is the UserRole enum that define the roles every user can have */
public static enum UserRole
{
Administrator, User, Manager;
}
}
以上所有代码,工作正常,用户存储正确,我可以轻松获取它们。
每个用户在登录时都会通过类似的服务进行身份验证,并且会为每个用户创建一个具有唯一会话 ID(使用 UUID)的 ConnectedUser 对象。
在每次服务调用之后,都有一个身份验证运行,即如果用户可以通过搜索使用此服务,则验证是否有此会话的已连接用户条目(存储为cookie):
@Secured
@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter
{
@Context
private HttpServletRequest request;
/**
* Authenticates a user's access with every request that is made via a token.
*
* @param requestContext The request that is sent to the server.
* @throws IOException
*/
@Override
public void filter(ContainerRequestContext requestContext) throws IOException
{
boolean isValidated;
Cookie sessionCookie = null;
Cookie[] cookies = request.getCookies();
if (cookies.length != 0) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("CookieName")) {
sessionCookie = cookie;
}
}
}
if (sessionCookie != null) {
// UserValidationHandler checks if user is in connected_users table
isValidated = UserValidationHandler.validateUser(sessionCookie.getValue(), request.getRemoteAddr());
}
else {
MultivaluedMap pathParameters = requestContext.getUriInfo().getQueryParameters();
// UserValidationHandler checks if user is in connected_users table
isValidated = UserValidationHandler.validateUser((String)pathParameters.getFirst("token"), request.getRemoteAddr());
}
if (!isValidated) {
LOGGER.warn("[Authorization filter] Unauthorized user.");
URI indexURI = URI.create("http://login.jsp");
requestContext.setRequestUri(indexURI);
}
}
}
备注:
注意 1:大多数实施建议将角色应用到 web.xml 文件中。尽管我认为,就我而言,这是不可行的。
注2:另外,授权用户使用服务的正确位置在哪里?我发现我可以用 @Priority(Priorities.AUTHORIZATION)
创建 ContainerRequestFilter
class。哪个比较好?
我有点迷路了。我读了很多 Q/As 或那里的样本,但没有任何解释透彻。
您应该将关注点分开,并在一个过滤器中执行身份验证,在一个过滤器中执行授权。通常这是如何实现的,方法是从身份验证过滤器内部设置 SecurityContext
,然后从授权过滤器内部检索它。
SecurityContext
有一个您重写的 isUserInRole
方法。应该在授权过滤器中调用此方法。通常,您将拥有作为 SecurityContext
成员的角色,只需迭代角色
static class MySecurityContext implements SecurityContext {
private final String[] userRoles;
public MySecurityContext(String[] roles, String user) {
this.userRoles = roles;
}
@Override
public Principal getUser() {
return new Principal() {
@Override
public String getName() {
return name;
}
}
}
public boolean isUserInRole(String role) {
for (String userRole: userRoles) {
if (role.equals(userRole) {
return true;
}
}
return false;
}
// more methods to override
}
在身份验证过滤器中,您只需调用传入新 SecurityContext
的 requestContext.setSecurityContext
方法。
在授权过滤器中,您将通过使用 ResourceInfo
获得 @RolesAllowed
注释。例如
class AuthorizationFilter implement ContainerRequestContext {
@Context
private ResourceInfo info;
@Override
public void filter(ContainerRequestContext request) {
SecurityContext sc = request.getSecurityContext();
RolesAllowed anno = info.getResourceMethod().getAnnotation(RolesAllowed.class);
String rolesAllowed = anno.value();
for (role: rolesAllowed) {
if (sc.isUserInRole(role)) {
return;
}
}
request.abortWith(Response.status(403).build());
}
}
或者类似的东西。
不过,如果您使用的是 Jersey2,则无需自己实施授权。这已在 RolesAllowedDynamicFeature
中实现。您只需要在您的应用程序中注册该功能。它的工作原理与我之前提到的相同; SecurityContext
预计在到达授权过滤器之前填写,过滤器将检查角色并授权或拒绝。
我的实现与 peeskillet
的不同,但它对我有用。
实现安全性的代码可能如下所示
public class ServiceFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext req) throws IOException {
//user name and password are obtained from the header
String auth = req.getHeaderString(HttpHeaders.AUTHORIZATION);
if(auth == null) {
throw new WebApplicationException(Status.UNAUTHORIZED);
}
//user name and password
String[] credentials = auth.substring(1, auth.length()-1).split(":");
String user = credentials[0];//user name
String password = credentials[1];//password
if(user == null || password == null)
throw new WebApplicationException(Status.UNAUTHORIZED);
ServiceSecurity ss = null;
//user name and password are hardcoded here but you better put them in a DB or file
if(user.equals("servUser") && password.equals("service"))
ss = new ServiceSecurity(new ServiceUser("servUser"));
else if(user.equals("servAdmin") && password.equals("admin"))
ss = new ServiceSecurity(new ServiceUser("servAdmin"));
else
throw new WebApplicationException(Status.UNAUTHORIZED);
req.setSecurityContext(ss);
}
}
安全上下文
import java.security.Principal;
import javax.ws.rs.core.SecurityContext;
public class ServiceSecurity implements SecurityContext {
private ServiceUser sUser;
public ServiceSecurity(ServiceUser sUser){
this.sUser = sUser;
}
@Override
public String getAuthenticationScheme() {
// TODO Auto-generated method stub
return SecurityContext.DIGEST_AUTH;
}
@Override
public Principal getUserPrincipal() {
// TODO Auto-generated method stub
return sUser;
}
@Override
public boolean isSecure() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isUserInRole(String role) {
// TODO Auto-generated method stub
return sUser.getRole().equals(role) ? true : false;
}
}
安全上下文实现 class 使用 class 实现主体 (ServiceUser) 初始化,用于获取此特定用户的角色。
import java.security.Principal;
public class ServiceUser implements Principal {
private String role;
public ServiceUser(String role){
this.role = role;
}
public ServiceUser(){
}
public String getRole(){
return role;
}
public void setRole(String role){
this.role = role;
}
public String getName(){
return "some name";
}
}
在这种情况下,您需要将以下内容添加到您的服务器方法中
@RolesAllowed("servUser")
应在请求中提供凭据(用户名和密码)header(从上面的过滤方法可以看出)
TLDR
在我开发的 Java Web 应用程序中,需要实现对 REST 服务的用户区分。我知道有一些注释(@RolesAllowed
、@PermitAll
、@DenyAll
)可以描述哪个角色可以使用该服务。我的问题是,如何将 @RolesAllowed
中的角色与用户角色枚举匹配,该用户是存储在数据库中的持久对象?
许多教程解释了 @RolesAllowed
,但我发现 none 与已经创建的角色匹配
说明
如何通过自动检查用户的角色(通过会话 ID 找到)来验证用户?我知道 Jersey 已经这样做了,通过注册 RolesAllowedDynamicFeature.class
。我已经设法通过将 @DenyAll
注释放入方法中来检查此 RolesAllowedDynamicFeature.class
是否有效,并返回了 403 错误。
首先,我们先从什么是用户说起。用户是数据库实体,使用 Ebean
将它们从 Java 对象中持久化。这是来自 User.class
:
/*
* A sample that describes the fields that I ask for and to understand the concept
*/
@Entity
@Table(name = "users")
public class User extends Model
{
@Id
@GeneratedValue
@Column(name = "id")
private long id;
@Enumerated(EnumType.STRING)
@Column(name = "role", nullable = false)
private UserRole role;
// contructors getters setters helper methods etc
/**
* Fetch a user from DB
*
* @param id the id to search for
* @return a Person.class object or may return null
*/
public static User getUserById(Long id)
{
return Ebean.find(User.class, id);
}
/* and here is the UserRole enum that define the roles every user can have */
public static enum UserRole
{
Administrator, User, Manager;
}
}
以上所有代码,工作正常,用户存储正确,我可以轻松获取它们。
每个用户在登录时都会通过类似的服务进行身份验证,并且会为每个用户创建一个具有唯一会话 ID(使用 UUID)的 ConnectedUser 对象。 在每次服务调用之后,都有一个身份验证运行,即如果用户可以通过搜索使用此服务,则验证是否有此会话的已连接用户条目(存储为cookie):
@Secured
@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter
{
@Context
private HttpServletRequest request;
/**
* Authenticates a user's access with every request that is made via a token.
*
* @param requestContext The request that is sent to the server.
* @throws IOException
*/
@Override
public void filter(ContainerRequestContext requestContext) throws IOException
{
boolean isValidated;
Cookie sessionCookie = null;
Cookie[] cookies = request.getCookies();
if (cookies.length != 0) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("CookieName")) {
sessionCookie = cookie;
}
}
}
if (sessionCookie != null) {
// UserValidationHandler checks if user is in connected_users table
isValidated = UserValidationHandler.validateUser(sessionCookie.getValue(), request.getRemoteAddr());
}
else {
MultivaluedMap pathParameters = requestContext.getUriInfo().getQueryParameters();
// UserValidationHandler checks if user is in connected_users table
isValidated = UserValidationHandler.validateUser((String)pathParameters.getFirst("token"), request.getRemoteAddr());
}
if (!isValidated) {
LOGGER.warn("[Authorization filter] Unauthorized user.");
URI indexURI = URI.create("http://login.jsp");
requestContext.setRequestUri(indexURI);
}
}
}
备注:
注意 1:大多数实施建议将角色应用到 web.xml 文件中。尽管我认为,就我而言,这是不可行的。
注2:另外,授权用户使用服务的正确位置在哪里?我发现我可以用 @Priority(Priorities.AUTHORIZATION)
创建 ContainerRequestFilter
class。哪个比较好?
我有点迷路了。我读了很多 Q/As 或那里的样本,但没有任何解释透彻。
您应该将关注点分开,并在一个过滤器中执行身份验证,在一个过滤器中执行授权。通常这是如何实现的,方法是从身份验证过滤器内部设置 SecurityContext
,然后从授权过滤器内部检索它。
SecurityContext
有一个您重写的 isUserInRole
方法。应该在授权过滤器中调用此方法。通常,您将拥有作为 SecurityContext
成员的角色,只需迭代角色
static class MySecurityContext implements SecurityContext {
private final String[] userRoles;
public MySecurityContext(String[] roles, String user) {
this.userRoles = roles;
}
@Override
public Principal getUser() {
return new Principal() {
@Override
public String getName() {
return name;
}
}
}
public boolean isUserInRole(String role) {
for (String userRole: userRoles) {
if (role.equals(userRole) {
return true;
}
}
return false;
}
// more methods to override
}
在身份验证过滤器中,您只需调用传入新 SecurityContext
的 requestContext.setSecurityContext
方法。
在授权过滤器中,您将通过使用 ResourceInfo
获得 @RolesAllowed
注释。例如
class AuthorizationFilter implement ContainerRequestContext {
@Context
private ResourceInfo info;
@Override
public void filter(ContainerRequestContext request) {
SecurityContext sc = request.getSecurityContext();
RolesAllowed anno = info.getResourceMethod().getAnnotation(RolesAllowed.class);
String rolesAllowed = anno.value();
for (role: rolesAllowed) {
if (sc.isUserInRole(role)) {
return;
}
}
request.abortWith(Response.status(403).build());
}
}
或者类似的东西。
不过,如果您使用的是 Jersey2,则无需自己实施授权。这已在 RolesAllowedDynamicFeature
中实现。您只需要在您的应用程序中注册该功能。它的工作原理与我之前提到的相同; SecurityContext
预计在到达授权过滤器之前填写,过滤器将检查角色并授权或拒绝。
我的实现与 peeskillet
的不同,但它对我有用。
实现安全性的代码可能如下所示
public class ServiceFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext req) throws IOException {
//user name and password are obtained from the header
String auth = req.getHeaderString(HttpHeaders.AUTHORIZATION);
if(auth == null) {
throw new WebApplicationException(Status.UNAUTHORIZED);
}
//user name and password
String[] credentials = auth.substring(1, auth.length()-1).split(":");
String user = credentials[0];//user name
String password = credentials[1];//password
if(user == null || password == null)
throw new WebApplicationException(Status.UNAUTHORIZED);
ServiceSecurity ss = null;
//user name and password are hardcoded here but you better put them in a DB or file
if(user.equals("servUser") && password.equals("service"))
ss = new ServiceSecurity(new ServiceUser("servUser"));
else if(user.equals("servAdmin") && password.equals("admin"))
ss = new ServiceSecurity(new ServiceUser("servAdmin"));
else
throw new WebApplicationException(Status.UNAUTHORIZED);
req.setSecurityContext(ss);
}
}
安全上下文
import java.security.Principal;
import javax.ws.rs.core.SecurityContext;
public class ServiceSecurity implements SecurityContext {
private ServiceUser sUser;
public ServiceSecurity(ServiceUser sUser){
this.sUser = sUser;
}
@Override
public String getAuthenticationScheme() {
// TODO Auto-generated method stub
return SecurityContext.DIGEST_AUTH;
}
@Override
public Principal getUserPrincipal() {
// TODO Auto-generated method stub
return sUser;
}
@Override
public boolean isSecure() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isUserInRole(String role) {
// TODO Auto-generated method stub
return sUser.getRole().equals(role) ? true : false;
}
}
安全上下文实现 class 使用 class 实现主体 (ServiceUser) 初始化,用于获取此特定用户的角色。
import java.security.Principal;
public class ServiceUser implements Principal {
private String role;
public ServiceUser(String role){
this.role = role;
}
public ServiceUser(){
}
public String getRole(){
return role;
}
public void setRole(String role){
this.role = role;
}
public String getName(){
return "some name";
}
}
在这种情况下,您需要将以下内容添加到您的服务器方法中
@RolesAllowed("servUser")
应在请求中提供凭据(用户名和密码)header(从上面的过滤方法可以看出)