I want to set my User object only once through my #RequestScoped LoginBean. Then I want to reuse its setters, getters and an isLoggedIn() method in other #ManagedBean through CDI.
Request Scoped Class that Sets User Object
#ManagedBean
#RequestScoped
public class LoginBean {
#ManagedProperty(value = "#{bean}")
protected Bean bean;
private String username;
private String password;
public String login() {
bean.setLoggedInUser(userDao.getUser(username));
return "index";
}
// Getters and Setters, including for the #ManagedProperty baseBean.
}
SessionScoped Class that Stores User Object
#ManagedBean
#SessionScoped
public class Bean {
private User loggedInUser = null;
public boolean isLoggedIn() {
return loggedInUser != null;
}
// Getters and setters for loggedInUser
}
Class Where I Want To Refer to loggedInuser
#ManagedBean
#RequestScoped
public class ShowUserDetails extends Bean {
private Details details = new Details();
public void showDetails() {
if(isLoggedIn()) { // THIS ALWAYS RETURNS FALSE
// Do stuff
}
}
}
Solutions So Far
I can list a Bean #ManagedProperty in every single Backing Bean that needs the loggedInUser. This seems wrong as I am copy-pasting two lines in every class.
I can get the instance of Bean class from FacesContext using context.getApplication().evaluateExpressionGet(). This allows me to have one method to retrieve Bean instance in a superclass, however this also seems wrong. That said, this is the method I will go with if I am unable to find a pure CDI solution.
You are worried about adding two lines of code once in each bean, yet you do want to write
if(isLoggedIn()) { // THIS ALWAYS RETURNS FALSE
// Do stuff
}
And most likely many times in a bean (and most likely also things in an else statement).
Then I'd certainly go fo using annotations and interceptors
See also
https://code.google.com/p/jee6-cdi/source/browse/tutorial/cdi-aop-example/src/main/java/org/cdi/advocacy/security/
http://balusc.blogspot.nl/2013/01/apache-shiro-is-it-ready-for-java-ee-6.html
My solution was to use the HttpSession to store the User object instead of having a class member variable. This allowed me to have one class that handled the getting/setting and all the other classes could simply call 'getLoggedinUser' to retrieve the entire object without hitting the DB.
private HttpSession session = (HttpSession) FacesContext.getCurrentInstance().getExternalContext().getSession(true);
private static final String LOGGED_IN_USER = "loggedInUser";
public User getLoggedInUser() {
return (User) session.getAttribute(LOGGED_IN_USER);
}
public void setLoggedInUser(User user) {
session.setAttribute(LOGGED_IN_USER, user);
}`
Related
In the application I would like to create a request-scoped bean (annotated with #RequestScope) that would represent a user of the application (for authentication OAuth2 is used with the company's own authentication provider based off keycloak; the problem is that the Principal doesn't contain extra information about the permissions this particular user has and they have to be retrieved from another service, which is why I want to have this request-scoped bean that would retrieve fresh permissions on each request).
public class MyApplicationUser {
private final String name;
private final List<Permission> permissions;
/* all-arg constructor, getters */
}
#Configuration
public class UserConfiguration {
private final PermissionService permissionService;
/* constructor, etc */
#Bean
#RequestScope
public MyApplicationUser currentUser(#CurrentSecurityContext(expression = "authentication") OAuth2AuthenticationToken authenticationToken) {
/* call permissionService, map the result and the token details data to MyApplicationUser instance and return it*/
}
}
Although on each request a new instance of MyApplicationUser is created, it is not created by calling the currentUser method (it never gets called), but rather it seems that spring uses the provided constructor and supplies nulls as parameters, which is not what I want. How do I fix that?
P.S. the main class extends SpringBootServletInitializer
I have a bean which I've declared in my bean config as thus:
#Configuration
public class BeanConfig {
#Bean
public MemberDTO getMemberDTO() {
return new MemberDTO();
}
}
When a user calls my service, I use the username and password they've provided to call the endpoint of a different service to get the user's information:
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
private static final Logger LOGGER = LogManager.getLogger(CustomAuthenticationProvider.class);
private #Autowired MemberDTO memberDTO;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String loginGeniuneFailMessage = "";
boolean loginGeniuneFail = false;
try {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
String endPoint = credentialsBaseUrl + "/api/login";
HttpResponse<MemberDTO> response_auth = Unirest.get(endPoint)
.basicAuth(username, password)
.header("Accept", "*/*")
.asObject(MemberDTO.class);
int status_auth = response_auth.getStatus();
if (status_auth == 200) {
if (response_auth.getBody() == null) {
LOGGER.info("account validation - could not parse response body to object");
UnirestParsingException ex = response_auth.getParsingError().get();
LOGGER.error("parsing error: ", ex);
} else {
memberDTO = response_auth.getBody();
}
}
...
} catch (Exception ex) {
...
}
}
I want to store the user's information in the memberDTO and use the memberDTO elsewhere in a different component, rather than calling the login API every time:
#Component
public class MemberLogic {
private #Autowired MemberDTO memberDTO;
public ResponseEntity<?> processMemberInformation(WrapperDTO wrapperDTO, BindingResult result) {
if (result.hasFieldErrors()) {
String errors = result.getFieldErrors().stream()
.map(p -> p.getDefaultMessage()).collect(Collectors.joining("\n"));
return ResponseEntity.badRequest().body("An error occured while trying to persist information: " + errors);
}
String name = memberDTO.getName();
...
}
}
The problem now is the "memberDTO.getName()" is returning null, even though this value is being set from the initial API call in CustomAuthenticationProvider.
My questions are: why isn't this working? And is this the best approach to take for something like this?
Thanks.
My questions are: why isn't this working? And is this the best approach to take for something like this?
This doesn't work because Java uses pass-by-value semantics instead of pass-by-reference semantics. What this means is that the statement memberDTO = response_auth.getBody(); does not really make the Spring container start pointing to the MemberDTO returned by response_auth.getBody(). It only makes the memberDTO reference in CustomAuthenticationProvider point to the object in the response. The Spring container still continues to refer to the original MemberDTO object.
One way to fix this would be to define a DAO class that can be used for interacting with DTO instances rather than directly creating a DTO bean :
#Configuration
public class BeanConfig {
#Bean
public MemberDAO getMemberDAO() {
return new MemberDAO();
}
}
CustomAuthenticationProvider can then set the MemberDTO in the MemberDAO by using : memberDAO.setMemberDTO(response_auth.getBody());
Finally, MemberLogic can access the MemberDTO as String name = memberDAO.getMemberDTO().getName();
Note : Instead of returning the MemberDTO from the MemberDAO, the MemberDAO can define a method called getName which extracts the name from the MemberDTO and returns it. (Tell Don't Ask principle). That said and as suggested in the comments, the best practice would be to use a SecurityContext to store the user information.
The problem is, that you can not override a spring bean "content" like this memberDTO = response_auth.getBody(); because it changes only the instance variable for the given bean. (And its also not good because its out of the spring boot context and it overrides only the field dependency for this singleton bean)
You should not use a normal spring bean for holding data (a state). All the spring beans are singleton by default and you could have some concurrency problems.
For this you should use a database, where you write your data or something like a session bean.
I have this beautiful scenery in front of me including JSF, jUnit(4.11) and Mockito(1.10.19):
#ManagedBean
#ViewScoped
public class UserAuth implements Serializable {
private List<UserRole> roleList;
private LocalChangeBean localChangeBean;
public UserAuth() {
roleList = new ArrayList<UserRole>();
localChangeBean = (LocalChangeBean) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("localChangeBean");
setLocalChangeBean(localChangeBean);
setRoleList(getLocalChangeBean().getRoleList());
//many other property setting and some JSF stuff
}
public boolean checkAuth() {
for (UserRole role : getRoleList()) {
if(role.getName().equals("SUPER_USER"))
return true;
}
return false;
}
}
//A hell of a lot more code, proper getters/setters etc.
Here is the test class:
public class UserAuthTest {
#Test
public void testCheckAuth() {
UserAuth bean = mock(UserAuth.class);
List<UserRole> mockRoleList = new ArrayList<UserRole>();
UserRole ur = mock(UserRole.class);
when(ur.getName()).thenReturn("SUPER_USER");
mockRoleList.add(ur);
when(bean.getRoleList()).thenReturn(mockRoleList);
assertEquals(true, bean.checkAuth());
}
The thing is; UserRole class is not reachable by me, it's another part of the project. It doesn't have a no-argument constructor and the existing constructor requires other unreachable classes etc. Thus I can't instantiate it. In these circumstances, all I want to do is to make that mock UserRole object behave such as returning the needed String when it's getName() method gets called.
But obviously; when I try to add that UserRole mock object into the List of UserRoles, the behavior that I tried to define is not stored with the object. And yes, the code looks pretty funny in its current stance. Though I left it there to learn what should I do to achieve this simple, little goal of mine.
Post-Edit:
I couldn't manage the problem without changing the original bean, though I followed Jeff's suggestion below and it worked well as a strategy of isolation. I did not mark it as the best answer since the question was "How to mock an unreachable third party class?" (in the current example its the UserRole class) Eventually the noob me understood that "Mocking an unreachable third party class is no different than mocking any other class".
Here is how I managed it:
#ManagedBean
#ViewScoped
public class UserAuth implements Serializable {
private List<UserRole> roleList;
private LocalChangeBean localChangeBean;
public UserAuth() {
//the actual constructor including all JSF logic, highly dependent
}
UserAuth(List<UserRole> roleList) {
setRoleList(roleList);
//package private test-helper constructor which has no dependency on FacesContext etc.
}
public boolean checkAuth() {
for (UserRole role : getRoleList()) {
if(role.getName().equals("SUPER_USER"))
return true;
}
return false;
}
}
And here is the test class (attention to the iterator mock, it has the whole trick):
public class UserAuthTest {
private UserRole mockRole;
private Iterator<UserRole> roleIterator;
private List<UserRole> mockRoleList;
private UserAuth tester;
#SuppressWarnings("unchecked")
#Before
public void setup() {
mockRoleList = mock(List.class);
mockRole = mock(UserRole.class);
roleIterator = mock(Iterator.class);
when(mockRoleList.iterator()).thenReturn(roleIterator);
when(roleIterator.hasNext()).thenReturn(true, false);
when(roleIterator.next()).thenReturn(mockRole);
tester = new UserAuth(mockRoleList);
}
#Test
public void testCheckAuth(){
when(mockRole.getName()).thenReturn("SUPER_USER");
assertEquals("SUPER_USER expected: ", true, tester.checkAuth());
}
You don't need Mockito. A quick refactor will do this for you.
Your problem: Your code relies on a static call to FacesContext.getCurrentInstance() in your constructor, that is difficult to prepare or substitute out in tests.
Your proposed solution: Use Mockito to substitute out the FacesContext instance, the external context, or the session map. This is partly tricky because Mockito works by proxying out the instances, so without PowerMock you won't be able to replace the static call, and without a way to insert the mock into FacesContext or its tree, you have no alternative.
My proposed solution: Break out the bad call FacesContext.getCurrentInstance().getExternalContext.getSessionMap() into the default constructor. Don't call that constructor from tests; assume it works in the unit testing case. Instead, write a constructor that takes in the session map as a Map<String, Object>, and call that constructor from your tests. That gives you the best ability to test your own logic.
#ManagedBean
#ViewScoped
public class UserAuth implements Serializable {
// [snip]
public UserAuth() {
// For the public default constructor, use Faces and delegate to the
// package-private constructor.
this(FacesContext.getCurrentInstance().getExternalContext().getSessionMap());
}
/** Visible for testing. Allows passing in an arbitrary map. */
UserAuth(Map<String, Object> sessionMap) {
roleList = new ArrayList<UserRole>();
localChangeBean = (LocalChangeBean) sessionMap.get("localChangeBean");
setLocalChangeBean(localChangeBean);
setRoleList(getLocalChangeBean().getRoleList());
// [snip]
}
}
p.s. Another solution is to actually get the session map within the test and insert the value you need, but you have to be careful there not to pollute your other tests by installing something into a static instance that may persist between tests.
I have a SessionScoped bean say UserSession which holds a String property token which acts as a authenticated token for the logged in user. This token is injected into other SessionScoped and ViewScoped beans so that they can consume this token and perform stuff. I have no problems injecting the property.
However, there's a use case wherein the token in the UserSession itself is replaced by a different String. And once I do this, the other beans still refer to the old value of the token which results in invalid access.
How can I notify the other beans of this value change? or do I retrieve bean instance through
FacesContext context = FacesContext.getCurrentInstance();
Application app = context.getApplication();
UserSession session = (UserSession) app.evaluateExpressionGet(context, "#{userSession}", UserSession.class);
And then retrieve token as session.getToken()?
I'm using Mojarra 2.0.4
I don't see why that is a problem. This more sounds like as if you're copying the property like as:
#ManagedBean
#SessionScoped
private OtherBean {
#ManagedProperty(value="#{userSession}")
private UserSession userSession;
private String token;
#PostConstruct
public void init() {
this.token = userSession.getToken();
}
public void someAction() {
doSomethingWith(token);
}
public void otherAction() {
doSomethingElseWith(token);
}
// ...
}
while you should rather be accessing it directly:
// ...
public void someAction() {
doSomethingWith(userSession.getToken());
}
public void otherAction() {
doSomethingElseWith(userSession.getToken());
}
// ...
Fix your beans accordingly to get rid of the private token property which contains the copy and just let all methods get it from the injected bean directly.
I'm using boolean switchers to resolve a choosed behaviour of application, for example SAVEACCEPTED enables SAVE button of form.
<h:commandButton action="#{bean.save}" disabled="#{!bean.saveaccepted}">
JSF need private boolean and its getters and setters, but if I want to resolve some internal logic in application server, it must be defined static. For example
IF (USERFOUND) SAVEACCEPTED = true;
So, I'm using settings class and there are public static booleans defined. And in the beans there are getters and setters pointing to the Settings.VARIABLE
Settings.java
public static boolean SAVEACCEPTED = false;
Bean.java
public static boolean isSaveAccepted() {
return Settings.SAVEACCEPTED;
}
Problem is, that the public boolean is only one and if more then one users using an application, when the first switch the variable, it affects second user form.
How can I solve this issue, is there some standard solution?
Don't use a static variable. Use a #SessionScoped or #ViewScoped bean to store the settings separately for each user.
#Named
#SessionScoped
public class Settings
{
private boolean saveAccepted = false;
public boolean isSaveAccepted()
{
return saveAccepted;
}
public void setSaveAccepted(boolean saveAccepted)
{
this.saveAccepted = saveAccepted;
}
}
and
<h:commandButton action="#{bean.save}" disabled="#{!settings.saveaccepted}">
What if i'd need to set the saveAccepted = true in another bean (not in JSF)? It doesn't work, because in that case, saveAccepted have to be static.
Do not use a static variable.
If you need to set the value in another bean, you can #Inject an instance:
#Named
#RequestScoped
public class SomeOtherBean
{
#Inject
private Settings settings;
public boolean getSaveAccepted()
{
return settings.getSaveAccepted();
}
public void setSaveAccepted(boolean saveAccepted)
{
settings.setSaveAccepted(saveAccepted);
}
}
and CDI will give you the right instance of Settings.
BalusC comments:
Based on the question history, OP is using Tomcat which is just a simple servletcontainer.
Since it looks like you're not using a full Java EE 6 container, you can use #ManagedBean instead of #Named and #ManagedProperty instead of #Inject.
#ManagedBean
#RequestScoped
public class SomeOtherBean
{
#ManagedProperty
private Settings settings;
public boolean getSaveAccepted()
{
return settings.getSaveAccepted();
}
public void setSaveAccepted(boolean saveAccepted)
{
settings.setSaveAccepted(saveAccepted);
}
}
My apologies for sending you down a more complicated path!
Using static variable in this scenario is not ideal. Static fields by definition are shared across all instances of the class. So what is happening is the value you are storing are getting shared for all instances of your managed bean.
I recommend you save it at the request scope with #ViewScoped or define it in the faces-config.xml with <managed-bean-scope>.