I have a managed bean LoginBean:
#ManagedBean(name = "loginBean")
#SessionScoped
public class LoginBean implements Serializable {
private String email, password;
private BasicUser user;
/** Creates a new instance of LoginBean */
public LoginBean() {
}
public void setUser(BasicUser user) {
this.user = user;
}
public BasicUser getUser() {
return user;
}
...
}
And then a PhaseListener who gets the sessions loginBean.
public class FacebookSignInListener implements PhaseListener, UserService {
private LoginBean bean;
....
#Override
public PhaseId getPhaseId() {
return PhaseId.RENDER_RESPONSE;
}
#Override
public void afterPhase(PhaseEvent event) {
HttpSession session = (HttpSession) event.getFacesContext().getExternalContext().getSession(true);
bean = (LoginBean) session.getAttribute("loginBean");
bean.setUser(facebookUser);
}
#Override
public void beforePhase(PhaseEvent event) {
FacesContext fc = FacesContext.getCurrentInstance();
request = (HttpServletRequest) fc.getExternalContext().getRequest();
boolean isLoginPage =
(fc.getViewRoot().getViewId().indexOf("welcome") > -1);
if (isLoginPage) {
try {
FBOauth fbo = new FBOauth(this);
fbo.doLogin(request);
} catch (IOException ex) {
Logger.getLogger(FacebookSignInListener.class.getName()).log(Level.SEVERE, "Could not exchange code for access_token. Page where not found.", ex);
}
}
}
#Override
public boolean authFacebookLogin(String accessToken, FacesContext fc) throws FacebookException {
if (accessToken != null) {
FacebookClient facebookClient = new DefaultFacebookClient(accessToken);
User fbUser = facebookClient.fetchObject("me", User.class);
UserHelper uh = new UserHelper();
FacebookUser facebookUser = (FacebookUser) uh.getByFacebookId(fbUser.getId());
// Does the user already exist and is he already connected with facebook.
if (facebookUser != null) {
return true;
}
}
}
}
When I after deploy on the admin console press launch application, logs into my application via facebook there is no problem with the code below. I can logout and log in again and still no problem. If I then change browser and tries to login via facebook here I get a NullPointerException where I do
bean.setUser(facebookUser)
This also happens if I close the first browser, opens again and tries to login via facebook. Why is this happening?
I am using Glassfish v3.
If the session scoped bean is null then it simply means that it hasn't been created for the session yet. This can happen during the very first request of a fresh session. You've to create it yourself and put it in the session scope yourself. JSF will just reuse it in the remnant of the session.
Your way of grabbing the session scoped bean is a bit clumsy. You're getting the raw Servlet API from under the JSF's hoods. You could also just use ExternalContext#getSessionMap() to manage the session attributes.
Map<String, Object> sessionMap = externalContext.getSessionMap();
LoginBean loginBean = (LoginBean) sessionMap.get("loginBean");
if (loginBean == null) {
loginBean = new LoginBean();
sessionMap.put("loginBean", loginBean);
}
// ...
Note that you shouldn't declare the bean as an instance variable of the PhaseListener. There is namely a single PhaseListener instance throughout the application's lifetime, so all instance variables would be shared among all requests/sessions. In other words: it's not threadsafe.
Seems like you are trying to use the bean before it was created since your PhaseListener is firing in the first Phase. Have you tried to shift it to a later phase?
Edit:
You access the session with the crate flag set to true:
HttpSession session = (HttpSession) externalContext.getSession(true);
Thus, your newly created session wont include any session scoped beans.
The Framework will instantiate the session scoped Bean and put in the session object when an expression referencing the bean is evaluated, which is not the case here.
Related
Question
How do I set up a custom login with Spring Boot? I need to use the same connection method of a legacy app I have.
Things to know before I explain
I have a javafx App that connects to a data base using a php website
as proxy/Login Obviously, this javafx app has the User class already
defined.
The Database is separated by company(group of users), the login process basically retrieves the user object and to which database it should connect.
The javafx application logs in with the web service and retrieves
the database URL for that specific user.
The javafx application uses the database URL to access a database
directly.
Or, to simplify: The user put the login and password and click LOGIN -> The app, goes to the webhost, sends the data to a certain php file and requests the database url and data that comes ENCRYPTED through JASPYR and it also comes through https. Once the data is returned, we decrypt it and login to the database directly.
Im building a SpringBoot application to work with this legacy app.
Where Am I stuck?
I have built a Spring boot maven project and im reading a lot about Spring boot. My first step is to create a login page that behaves as the legacy app.
Currently, Im using a InMemorySecurityConfig as follows:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
#Configuration
public class InMemorySecurityConfig {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("{noop}123").roles("USER")
.and()
.withUser("admin").password("{noop}123").roles("ADMIN");
}
}
I dont know how to start. I think I have to find away to define the correct User model and then use a class to login that actually allows me to manually login.
Here is the legacy class that I use on my javafx App below.
public JSONObject login(String usuario, String senha) throws MalformedURLException, IOException {
int timeout = 10;
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(timeout * 1000)
.setConnectionRequestTimeout(timeout * 1000)
.setContentCompressionEnabled(true)
.setAuthenticationEnabled(true)
.setSocketTimeout(timeout * 1000)
.build();
CloseableHttpClient httpclient = HttpClientBuilder.create().setDefaultRequestConfig(config).build();
HttpPost httppost = new HttpPost(Settings.LOGIN_URL);
int CONNECTION_TIMEOUT_MS = timeout * 1000; // Timeout in millis.
RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(CONNECTION_TIMEOUT_MS)
.setConnectTimeout(CONNECTION_TIMEOUT_MS)
.setContentCompressionEnabled(true)
.setAuthenticationEnabled(true)
.setSocketTimeout(CONNECTION_TIMEOUT_MS)
.build();
httppost.setConfig(requestConfig);
// Request parameters and other properties.
ArrayList<NameValuePair> params = new ArrayList<NameValuePair>(2);
params.add(new BasicNameValuePair("usuario", usuario)); // user
params.add(new BasicNameValuePair("senha", senha)); // password
httppost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
CloseableHttpResponse execute = null;
try {
execute = httpclient.execute(httppost);
} catch (Exception ex) {
try {
httppost.abort();
if (execute != null) {
execute.close();
}
} catch (Exception x) {
ErrorLogger.log(x);
}
throw ex;
}
HttpEntity entity = execute.getEntity();
if (entity != null) {
JSONObject json = JsonTools.readJson(entity.getContent());
return json;
}
return null;
}
#Override
protected Task<JSONObject> createTask() {
return new Task<JSONObject>() {
#Override
protected JSONObject call() throws InterruptedException, IOException {
StringProperty usuario = getUserName();
StringProperty senha = getPassword();
LoginDAO loginDAO = new LoginDAO();
JSONObject login = loginDAO.login(MyTools.encodeToBase64(usuario.get()), MyTools.encodeToBase64(senha.get()));
String db_url = login.getString("db_url");
String db_username = login.getString("db_username");
String db_password = login.getString("db_password");
String nomeEmpresa = login.getString("nomeEmpresa");
int idUsuario = login.getInt("idUsuario");
String nomeDoUsuario = login.getString("nomeUsuario");
int idTunnel = login.getInt("idTunnel");
Settings.setDb_password(db_password);
Settings.setDb_username(db_username);
Settings.setDb_url(db_url);
Settings.setDb_empresa(nomeEmpresa);
//Connect to the data base
HibernateUtil.init();
return login;
}
};
}
How do I start? Any tips on how to start is greatly appreciated.
Im expecting that I have to somehow tell spring boot which User class to use, to tell spring to NOT instantly connect to the database and wait for the database info to be retrieved by the login page, and a way to login using spring.
Spring Boot base on http endpoints like this:
#RestController
#RequestMapping("/api/login")
public class LoginController {
private final LoginService loginService;
#GetMapping
public ObjectYouWantReturn returnDatabaseURL(#RequestBody DtoCredentials dto) {
return loginService.returnDatabaseURL(dto.getUsername, dto.getPassword);
}
}
Your client must send DtoCredentials to https://your.host/api/login. Then server will return secured data to your client. You need to implement mechanism to validation credentials from a user.
This class will load a UserDetailsImpl by username. You can use below class for load data from a database or simply create a List or something with users.
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Override
public UserDetailsImpl loadUserByUsername(String username) {
//here you have to create mechanism returning user credentials in UserDetails
}
}
UserDetails is basic DTO, look at the documentation
public class UserDetailsImpl implements UserDetails {
//implement here methods specified by UserDetails
}
And this is Service. In Spring Boot a good practice is separating logic from controllers. In method returnDatabaseURL() we are checking are the credentials correctly. If they are, the server returns data to client, or if not, we can throw ResponseStatusException(HttpStatus.NOT_FOUND) of anything else.
#Service
public class LoginService {
/*Spring Boot should automatically inject UserDetailsServiceImpl because we
annotated it by #Service annotation*/
private final UserDetailsServiceImpl userDetails;
public LoginService(UserDetailsServiceImpl userDetails) {
this.userDetails = userDetails;
}
public ObjectYouWantReturn returnDatabaseURL(String username, String password) {
if (userDetails.loadUserByUsername(username) != null &&
userDetails.loadUserByUsername(username).getPassword().equals(password)) {
return new ObjectYouWantReturn(/*URL to database or anything you want to
return to user*/);
}
}
}
Validating users in the service is not the best way, it is recommended that validate user in filter. But filters is a little bit more advanced issue.
Basic implementation of DtoCredentials:
public class DtoUsernamePassword implements Serializable {
#NotBlank
private final String username;
#NotBlank
private final String password;
public DtoUsernamePassword(#JsonProperty("username") #NotBlank String username,
#JsonProperty("password") #NotBlank String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}
Needed Jackson dependency:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.11.2</version>
</dependency>
I hope that after read it you will understand Spring Boot a bit more (because as I see you are new in Spring Boot). It's not a completely implementation. If my answer don't satisfying you, treat it as a loose attempt to explain how does Spring Boot work.
I want to set a session attribute with the name that is send by user. User will first login in. And when he logged in I want that his username will set as session attribute.
What should I do?
This is my controller:
#GetMapping("/login")
public String login() {
return "Login";
}
#PostMapping("/loginCheck")
public String checkLogin(#ModelAttribute("users") Users user) {
if (userService.checkUser(user)) {
return "redirect:/"+user.getUsername()+"/";
} else {
return "Login";
}
}
#PostMapping("/signup")
public ModelAndView createuser(#ModelAttribute("users") Users user) {
if (userService.checkUser(user)) {
return new ModelAndView("Login");
} else {
userService.adduser(user);
return new ModelAndView("Login");
}
}
Now how I set the username as session which I am getting in user.getUsername()?
In SpringMVC you can have the HttpSession injected automatically by adding it as a parameter to your method. So, you login could be something similar to:
#GetMapping("/login")
public String login(#ModelAttribute("users") Users user, HttpSession session)
{
if(userService.authUser(user)) { //Made this method up
session.setAttribute("username", user.getUsername());
view.setViewName("homepage"); //Made up view
}
else{
return new ModelAndView("Login");
}
}
#Autowired
ObjectFactory<HttpSession> httpSessionFactory;
.
.
.
HttpSession session = httpSessionFactory.getObject();
Works good. Thanks to this post.
If you use Spring Security, registered a bean listening for Spring Security's InteractiveAuthenticationSuccessEvent and SessionDestroyedEvent events. These events fire without any explicit configuration in a default Spring Boot environment.
See https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.security:
The basic features you get by default in a web application are:
. . .
A DefaultAuthenticationEventPublisher for publishing authentication events.
By handling these events you can add "username" as a session attribute immediately after a user logons and remove that attribute when the security session (security context) is destroyed:
#Component
public class SessionStoreUsernameAuthEventHandler {
#EventListener
public void audit(InteractiveAuthenticationSuccessEvent e) {
getSession().ifPresent(s -> s.setAttribute("username", e.getAuthentication().getName()));
}
#EventListener
public void audit(SessionDestroyedEvent e) {
getSession().ifPresent(s -> s.removeAttribute("username"));
}
private static Optional<HttpServletRequest> getCurrentRequest() {
return Optional.ofNullable(RequestContextHolder.getRequestAttributes())
.filter(ServletRequestAttributes.class::isInstance)
.map(ServletRequestAttributes.class::cast)
.map(ServletRequestAttributes::getRequest);
}
private static Optional<HttpSession> getSession() {
return getCurrentRequest().map(HttpServletRequest::getSession);
}
}
I have a problem. I can't implement user's Spring Security auto logout for particular user after timeout using thread.
I've already tried to implement the kill of the particular user's session. Also I've already tried to call Spring Security logout from my thread.
Here is my implementing of user's session kill.
public class Logout {
private static HttpServletRequest request;
private static HttpServletResponse response;
public static void threadrun() {
Runnable helloRunnable = new Runnable() {
public void run() {
try {
HttpSession newsession = request.getSession(false);
if (newsession != null) {
newsession.invalidate();
}
// I want to kill session for particular user, but don't know how
response.sendRedirect("../index.jsp");
} catch (IOException e) {
e.printStackTrace();
}
}
};
ScheduledExecutorService executor = Executors.newScheduledThreadPool(10);
executor.scheduleAtFixedRate(helloRunnable, 0, 3, TimeUnit.SECONDS);
}
}
I am using #With(Action.class) annotation to intercept the calls to specific controller/actions. I am trying to retrieve the session from database on in the interceptor function; however the JPA helper class is not available in the Action.class interceptor method "call".
Can someone please guide on how to retrieve database entities in the interceptor functions?
Thanks.
Interceptor class:
public class SecuredAction extends Simple {
public SecuredAction() {
// TODO Auto-generated constructor stub
}
#Override
public Promise<Result> call(Context ctx) throws Throwable {
// check isContactVerified/isEmailVerified
String sid = getSidFromCookie(ctx);
if (sid != null) {
Session appSession = (Session) JPA.em().createNamedQuery("Session.findBySessionId").getSingleResult();
User user = appSession.getUserId();
if (user != null) {
ctx.args.put("user", user);
return delegate.call(ctx);
}
}
Result unauthorized = Results.unauthorized("Invalid Session");
return F.Promise.pure(unauthorized);
}
private String getSidFromCookie(Http.Context ctx) {
return ctx.session().get(AppConstants.COOKIE_USER_SESSIONID);
}
}
Error:
[RuntimeException: No EntityManager bound to this thread. Try to annotate your action method with #play.db.jpa.Transactional]
Wrap body of you action with JPA.withTransaction:
return JPA.withTransaction(
"default",
false, () -> {
String sid = getSidFromCookie(ctx);
if (sid != null) {
Session appSession = (Session) JPA.em().createNamedQuery("Session.findBySessionId").getSingleResult();
User user = appSession.getUserId();
if (user != null) {
ctx.args.put("user", user);
return delegate.call(ctx);
}
}
Result unauthorized = Results.unauthorized("Invalid Session");
return F.Promise.pure(unauthorized);
}
);
And do not annotate method with #Transactional if you annotated it with #With(SecuredAction.class).
I have spring(4.0.6) web application. I've noticed that when user logon then logout and try to logon once again gets an error about user\pass.
I don't have web.xml file because I am using SpringBootServletInitializer class to config my application.
I've add to my configuration such bean
#Bean
public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher()
{
return new ServletListenerRegistrationBean<HttpSessionEventPublisher>(new HttpSessionEventPublisher());
}
and in security config I have this:
http.sessionManagement()
.maximumSessions(1).expiredUrl("/login?expired")
.maxSessionsPreventsLogin(true)
.and()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.invalidSessionUrl("/");
As I understand previous session is alive and because of Maximum session = 1 user can't login once again. What should I do to avoid it?
You should invalidate the user session when he logout. Below is the piece of code. Need to pass the userid.
UserInfo is the POJO
public boolean invalidateUserSession(Long userId) {
List<Object> principalsList = sessionRegistry.getAllPrincipals();
Object targetPrincipal = null;
for (Object principal : principalsList) {
if (principal instanceof UserInfo) {
if (((UserInfo) principal).getId().equals(userId)) {
targetPrincipal = principal;
break;
}
}
}
if (targetPrincipal == null) {
return false;
}
List<SessionInformation> userSessionsList = sessionRegistry.getAllSessions(targetPrincipal, false);
for (SessionInformation x : userSessionsList) {
x.expireNow();
}
return true;
}