Binding a User entity and a GlassFish Principal
I have an entity class User
that contains information such as username, first name, last name and a password and I have my GlassFish 3.1 server setup to perform authentication. So far, so good. After the container has authenticated a user, I need some way to bind the principal to the actual User entity. After all, GlassFish is telling me is that user "laurens" has authenticated, it is not giving me the corresponding User
entity.
To that end I wrote a JSF managed bean UserController
. What I would like to know is if this is the correct way to look the actual entity up and if there are any obvious pitfalls I am not seeing.
UserController
features the following fields:
@EJB
private UserFacade userFacade;
private User user;
The userFacade
is a stateless session bean to persist and find User
instances. The user
field is used by the JSF page to get and set properties on the user.
I use the following method to perform the binding accompanied by two helper methods:
@PostConstruct
private void init() {
try {
user = userFacade.find(getUserPrincipal().getName());
} catch (NullPointerException ex) {
// Intentionally left empty -- User is not logged in.
}
}
private HttpServletRequest getHttpServletRequest() {
return (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
}
private Principal getUserPrincipal() {
return getHttpServletRequest().getUserPrincipal();
}
The following methods are used by the JSF page to determine what components to show (if the user is already authenticated then there is no need to show a login form), authenticate the user if the "Login" button is clicked, or register as a new user when the "Register" button is clicked.
public boolean isAuthenticated() {
return getUserPrincipal() != null;
}
public void authenticate() {
try {
getHttpServletRequest().login(user.getEmailAddress(), user.getPassword());
} catch (Exception ex) {
// TODO: Handle failed login attempt
}
}
public void register() {
userFacade.create(user);
}
Would this the correct way to go about?
Thanks!
Edit:
Thanks for the input both! I thought about it for a bit, and while I think moving the passwords to a different table is a little to much for me to handle at the moment, I do think I can address some of the issues by separating the UserController
in a @RequestScoped
AuthenticationController
and a stripped down @SessionScoped
UserController
.
The AuthenticationController
would have emailAddress
and password
fields, bound by the web page's emailAddress and password fields. It would additionally contain the public void authenticate()
to authenticate the user and discard the credentials afterwards. The @SessionScoped
UserController
can then bind to the appropriate User
entity without ever needing to know a password. In fact I believe I would be able to remove the password field from User
altogether.
Your proposed approach has a few rough edges, but for the most part it is quite fine.
If you intend to store a reference to the User
entity, then it is preferable to do so in a SessionScoped
managed bean. This has it's pros and cons. The obvious advantage is that
User
entity is available through out the application flow, across all pages. This would mean that you need to bind the Principal
to a User
entity only once for a session. And you can re-use the bound value through all pages, if the need arises. The not-so-obvious disadvantage is that
password
field would be stored in memory for quite a long duration. At best, you should attempt to nullify the password field of the entity after an authentication attempt (whether it is unsuccessful or not, irrespective of whether the field contains the password in clear or a hash). Also, it would make a lot of sense to define the password
field as lazily fetched ( FetchType
of LAZY
) as opposed to the default of eager fetch ( FetchType
of EAGER
). If you implement this (in particular, nullification of the password field), you'll need to watch out for problems involving merge operations conducted on the User
entity; in such an event, it might be better to have a separate entity to store passwords for users (quite unfortunate, but that is the extent to which you must bend your back to protect password and their hashes in certain applications). Having said that, it is also necessary to ensure the following:
User
entity instead of the Principal
object to enforce access control checks. Update
This is based on the edited question. If you implement the authenticate
method as proposed, you can implement your scheme of binding the User
entity to the Principal
only upon successful authentication.
The following is a reproduction of a similar implementation from my application:
public String authenticate()
{
String result = null;
ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
HttpServletRequest request = (HttpServletRequest) externalContext.getRequest();
try
{
request.login(userId, password);
result = "/private/MainPage.xhtml?faces-redirect=true";
}
catch (ServletException ex)
{
logger.error("Failed to authenticate user.", ex);
FacesMessage facesMessage = new FacesMessage(FacesMessage.SEVERITY_ERROR, Messages.getString("Login.InvalidIdOrPasswordMessage"), null);
FacesContext.getCurrentInstance().addMessage(null, facesMessage);
}
return result;
}
which is invoked from the facelet:
<h:form id="LoginForm" acceptcharset="UTF-8">
<p>
<h:outputLabel for="userid" value="#{msg['Login.userid.label']}" />
<h:inputText id="userid" value="#{loginBean.userId}" />
</p>
<p>
<h:outputLabel for="password" value="#{msg['Login.password.label']}" />
<h:inputSecret id="password" value="#{loginBean.password}" />
</p>
<h:commandButton id="submit" value="#{msg['Login.submit.label']}"
action="#{loginBean.authenticate}" />
</h:form>
Note the use of the POST-REDIRECT-GET pattern for the case where the authentication is successful. I've left a bit of code that relates to invalidation of the current session before redirection, to prevent session fixation attacks. The binding of the User
entity to the Principal
would be done in new session, as long as it is done in a session scoped bean.
上一篇: 条件类型约束参数