如何让每个用户根据他们在 JAAS 中的权限/角色访问特定位置的资源?
Posted
技术标签:
【中文标题】如何让每个用户根据他们在 JAAS 中的权限/角色访问特定位置的资源?【英文标题】:How to make each user access resources at a specific location according to their authority/role in JAAS? 【发布时间】:2013-11-13 17:45:24 【问题描述】:我使用的是 GlassFish server 4.0,我在其中为不同的用户分配了不同的权限/角色。
一个用户可能有多个权限/角色。例如,管理员用户可能与ROLE_ADMIN
(执行管理任务)和ROLE_USER
(作为注册用户执行任务)关联)。
在我的web.xml
中,这是这样配置的。
<security-constraint>
<display-name>AdminConstraint</display-name>
<web-resource-collection>
<web-resource-name>ROLE_ADMIN</web-resource-name>
<description/>
<url-pattern>/admin_side/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<description/>
<role-name>ROLE_ADMIN</role-name>
</auth-constraint>
<user-data-constraint>
<description/>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<security-constraint>
<display-name>UserConstraint</display-name>
<web-resource-collection>
<web-resource-name>ROLE_USER</web-resource-name>
<description/>
<url-pattern>/user_side/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<description/>
<role-name>ROLE_USER</role-name>
</auth-constraint>
<user-data-constraint>
<description/>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<login-config>
<!--<auth-method>DIGEST</auth-method>-->
<auth-method>FORM</auth-method>
<realm-name>projectRealm</realm-name>
<form-login-config>
<form-login-page>/utility/Login.jsf</form-login-page>
<form-error-page>/utility/ErrorPage.jsf</form-error-page>
</form-login-config>
</login-config>
<security-role>
<description/>
<role-name>ROLE_ADMIN</role-name>
</security-role>
<security-role>
<description/>
<role-name>ROLE_USER</role-name>
</security-role>
这很好用。
有两种 URL 模式 /admin_side/*
和 /user_side/*
。管理员有两个角色ROLE_ADMIN
和ROLE_USER
。
当管理员使用权限ROLE_USER
登录时,应该访问位于/user_side/*
中的only资源。应该禁止访问位于/admin_side/*
中的资源,因为管理员是以注册用户而不是管理员身份登录的。
到目前为止,在我的情况下发生的情况是,当管理员使用任何权限登录时,可以访问这两个位置的资源,这是完全非法的。这是因为系统能够找到该特定用户的两个权限。
如何让每个用户根据他们的权限/角色访问特定位置的资源?
身份验证过滤器:
@WebFilter(filterName = "SecurityCheck", urlPatterns = "/jass/*")
public final class SecurityCheck implements Filter
private FilterConfig filterConfig = null;
@Resource(mappedName="jms/destinationFactory")
private ConnectionFactory connectionFactory;
@Resource(mappedName="jms/destination")
private Queue queue;
@EJB
private final UserBeanLocal userService=null;
public SecurityCheck()
private void sendJMSMessageToDestination(String message) throws JMSException
Connection connection = null;
Session session = null;
try
connection = connectionFactory.createConnection();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer messageProducer = session.createProducer(queue);
TextMessage textMessage = session.createTextMessage();
textMessage.setText(message);
messageProducer.send(textMessage);
finally
if(session!=null)session.close();
if(connection!=null)connection.close();
private void doBeforeProcessing(ServletRequest request, ServletResponse response) throws IOException, ServletException
HttpServletRequest httpServletRequest=(HttpServletRequest)request;
httpServletRequest.login(httpServletRequest.getParameter("userName"), httpServletRequest.getParameter("password"));
private void doAfterProcessing(ServletRequest request, ServletResponse response) throws IOException, ServletException, JMSException
HttpServletRequest httpServletRequest=(HttpServletRequest)request;
HttpServletResponse httpServletResponse=(HttpServletResponse)response;
ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
Map<String, Object> sessionMap = externalContext.getSessionMap();
if(httpServletRequest.isUserInRole("ROLE_USER"))
sendJMSMessageToDestination(httpServletRequest.getLocalName());
UserTable userTable = userService.setLastLogin(httpServletRequest.getParameter("userName"));
userTable.setPassword(null);
sessionMap.put("userName", userTable!=null?userTable.getFirstName():"Unknown");
sessionMap.put("user", userTable);
httpServletResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
httpServletResponse.setHeader("Pragma", "no-cache");
httpServletResponse.setDateHeader("Expires", 0);
httpServletResponse.sendRedirect("../user_side/Home.jsf");
else if(httpServletRequest.isUserInRole("ROLE_ADMIN"))
sendJMSMessageToDestination(httpServletRequest.getLocalName());
UserTable userTable = userService.setLastLogin(httpServletRequest.getParameter("userName"));
userTable.setPassword(null);
sessionMap.put("adminName", userTable!=null?userTable.getFirstName():"Unknown");
sessionMap.put("user", userTable);
httpServletResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
httpServletResponse.setHeader("Pragma", "no-cache");
httpServletResponse.setDateHeader("Expires", 0);
httpServletResponse.sendRedirect("../admin_side/Home.jsf");
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
try
doBeforeProcessing(request, response);
catch (Exception e)
HttpServletResponse httpServletResponse=(HttpServletResponse)response;
//FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Error", "Incorrect user name and/or password. Access denied."));
httpServletResponse.sendRedirect("../utility/Login.jsf");
return;
chain.doFilter(request, response);
try
doAfterProcessing(request, response);
catch (JMSException ex)
Logger.getLogger(SecurityCheck.class.getName()).log(Level.SEVERE, null, ex);
//The rest of the filter.
如果您需要在我的应用程序中查看其他内容,请告诉我。
【问题讨论】:
您究竟是如何以注册用户身份登录管理员的?如果一个特定的用户有多个角色,那么实际上没有办法只使用其中一个来登录。所以整个问题都很混乱。 这是一个对用户进行身份验证的过滤器。过滤器映射到 URL 模式 -/jass/*
(输入错误,应该是 /jaas/*
)。它指示一个目录,其中只有一个页面 - temp.jsp
,当按下 JSF 页面上的登录按钮时(通过其相应的 JSF 托管 bean),请求被调度的地方。有没有办法以管理员身份登录一次,以使用相同 ID/密码的用户身份登录一次?我也有这种感觉,但我认为容器可能支持一些机制来做到这一点。我查看了一些教程,但一无所获。
【参考方案1】:
您似乎认为当一个用户具有多个角色时,该用户只能同时使用其中一个角色登录。这不是真的。用户不是按角色登录的。用户基于每个用户登录。如果用户有多个角色,那么它们将全部在整个登录会话中使用和应用。
实际上,不可能让用户在整个会话期间只选择和使用其中一个分配的角色。到目前为止,这听起来太像您的管理员不应该首先拥有ROLE_USER
。但这在现实世界中毫无意义。角色不应该“扩展”现有角色。 IE。 ROLE_ADMIN
不应复制与 ROLE_USER
相同的限制,然后在此基础上添加更多限制。不,它应该仅仅代表“更多”。然后只为管理员用户分配这两个角色(您正确地完成了该部分)。否则,您最终会在用户和管理员都可以访问/使用的地方对整个代码进行重复检查。然后我不是在谈论第三个角色,它可能需要在代码中进行三次检查。您需要在所有地方编辑现有代码。
如果您想在运行时以编程方式切换角色,可能是因为您希望能够以普通用户的身份“预览”网站(例如,在隐藏管理员专用部分/按钮时检查网站的外观),那么基本上有两种选择:
将一些标志设置为会话属性或请求参数,并对其进行代码检查。例如
<h:form>
<h:selectBooleanCheckbox value="#sessionScope.preview">
<f:ajax render="@all" />
</h:selectBooleanCheckbox>
</h:form>
(注意:代码原样,#sessionScope
是一个隐式 EL 变量,引用 ExternalContext#getSessionMap()
;不需要额外的支持 bean)
然后在主模板中:
<c:set var="userIsAdmin" value="#request.isUserInRole('ROLE_ADMIN') and not preview" scope="request" />
在包含一些管理员特定内容的目标视图中:
<h:commandButton value="Some awesome admin button" rendered="#userIsAdmin" />
以普通用户身份执行编程登录。您可以使用HttpServletRequest#login()
以编程方式触发容器管理的身份验证。这样,管理员可以“冒充”不同的用户并浏览网站,就好像他以特定用户身份登录一样。例如。在会话范围的 bean 中:
public void runAs(User user)
// ...
try
request.login(user.getUsername(), user.getPassword());
originalUser = currentUser;
currentUser = user;
// ...
catch (ServletException e)
// ...
public void releaseRunAs()
// ...
try
request.login(originalUser.getUsername(), originalUser.getPassword());
currentUser = originalUser;
// ...
catch (ServletException e)
// ...
您甚至可以通过将所有以前的用户保存在会话范围内的 LILO(后进后出)队列中来扩展它。大多数像 Apache Shiro 这样的安全框架都有builtin APIs。
【讨论】:
我目前坚持“per-user based”方法,而不是不必要地陷入复杂性。主要是,我想知道容器是否支持它,开箱即用,我完全厌倦了来回翻转 Oracle 文档:) 我现在消除了我的大疑问。谢谢。【参考方案2】:我认为获得您要求的行为非常小心的方法,也许添加另一个管理员帐户并从该帐户中删除 ROLE_USER。
【讨论】:
在问这个问题之前,我还以为这只是这个问题的答案:)。这样做,但是我们不能说一个用户可以拥有多个权限/角色,因为一个用户需要拥有多个对应于多个权限/角色的帐户。尽管单个用户可能拥有多个具有不同权限/角色的帐户,但它们都被系统视为不同的用户 :)。以上是关于如何让每个用户根据他们在 JAAS 中的权限/角色访问特定位置的资源?的主要内容,如果未能解决你的问题,请参考以下文章
机器人询问用户他们希望加入啥角色,机器人根据回复将他们添加到角色
如何在事件订阅者中访问Symfony 3.3中的登录用户,而不会丢失Web分析器