-
-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FormAuth: Programatically login using Form Authentication #409
Comments
Because Quarkus obsviously works differently than a normal EE container i am not surprised by this. @tmulle asked this very question on this ticket how to programmtic "login" and I don't think it ever got answered as that topic morphed more into progammatic logout and the login question i am not sure was ever answered was it @tmulle? Read this thread: quarkusio/quarkus#27389 |
@Ryaryu it might be worth opening another Quarkus ticket and reference that original ticket that was never answered about programmatic login? I also asked the Devs on Zulip Chat as well: https://quarkusio.zulipchat.com/#narrow/stream/187038-dev/topic/j_security_check.20Programmatic.20Login.3F |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Oh... sure. @Named
@RequestScoped
public class LoginController {
@Getter
@Setter
String username;
@Getter
@Setter
String password;
@Inject
FacesContext facesContext;
@ConfigProperty(name = "quarkus.http.auth.form.cookie-name")
String cookieName;
/**
* Clear cookieName and redirects to my login page (/login.xhtml)
*/
public String logout() {
var fcResponse = (HttpServletResponse) facesContext.getExternalContext().getResponse();
var cookie = new Cookie(cookieName, "");
cookie.setMaxAge(0);
fcResponse.addCookie(cookie);
return "/login.xhtml?faces-redirect=true";
}
public void login() {
try {
var request = (HttpServletRequest) facesContext.getExternalContext().getRequest();
generateCookie(request);
// redirect to your main page.
facesContext.getExternalContext().redirect("/principal.xhtml");
} catch (Exception ex) {
// do something?
}
}
/**
* Here we just replace the login form partial URL (in my case /login.xhtml) with /j_security_check
* and make a request there so Quarkus can create the session cookie
*/
private void generateCookie(HttpServletRequest request) throws IOException, InterruptedException {
var securityCheckUrl = request.getRequestURL().toString()
.replace("/login.xhtml", "/j_security_check");
var response = jSecurityCheckRequest(securityCheckUrl);
var fcResponse = (HttpServletResponse) facesContext.getExternalContext().getResponse();
setCookie(response, fcResponse);
}
/**
* Magic lies here.
* We set the cookie generated by the /j_security_check request into the FacesContext response.
*/
private void setCookie(HttpResponse<String> response, HttpServletResponse fcResponse) {
var responseMap = response.headers().map();
if (responseMap.containsKey("set-cookie")) {
var cookieString = responseMap.get("set-cookie").get(0);
io.undertow.server.handlers.Cookie cookie = io.undertow.util.Cookies.parseSetCookieHeader(cookieString);
var quarkusCookie = new Cookie(cookieName, cookie.getValue());
quarkusCookie.setMaxAge(8 * 60 * 60);
quarkusCookie.setHttpOnly(true);
fcResponse.addCookie(quarkusCookie);
}
}
private HttpResponse<String> jSecurityCheckRequest(String securityCheckUrl)
throws IOException, InterruptedException {
var response = HttpClient.newHttpClient().send(HttpRequest.newBuilder()
.uri(URI.create(securityCheckUrl))
.POST(HttpRequest.BodyPublishers.ofString(
"j_username=" + username + "&j_password=" + password))
.header("Content-Type", "application/x-www-form-urlencoded")
.build(), HttpResponse.BodyHandlers.ofString());
return response;
}
} You can then call this bean from your xhtml freely, |
Issue created at Quarkus HTTP: quarkusio/quarkus-http#169 |
@Ryaryu this caused a bug found in the other ticket the proper way to parse the cookie. var cookieString = responseMap.get("set-cookie").get(0);
io.undertow.server.handlers.Cookie cookie = io.undertow.util.Cookies.parseSetCookieHeader(cookieString);
var quarkusCookie = new Cookie(cookieName, cookie.getValue()); I updated your code above. |
Here is my solution, AbstractIdendityService is my own service: quarkus.http.auth.basic=true
quarkus.http.auth.form.enabled=true
quarkus.http.auth.form.login-page=/login
quarkus.http.auth.form.landing-page=/dashboard
quarkus.http.auth.form.error-page=/login?failed
quarkus.http.auth.permission.public.paths=/jakarta.faces.resource/*,login,error,notfound,denied,expired,help,favicon.ico
quarkus.http.auth.permission.public.policy=permit
quarkus.http.auth.permission.public.methods=GET
quarkus.http.auth.permission.private.paths=/*
quarkus.http.auth.permission.private.policy=authenticated <form id="j_security_check" action="/j_security_check" method="post">
<div class="login-form">
<h2>Login</h2>
<p:staticMessage severity="error"
summary="#{msgs['login.error.title']}"
detail="#{msgs['login.error.description']}"
rendered="#{request.parameterMap.containsKey('failed')}" />
<p:inputText id="j_username" name="j_username" placeholder="#{msgs['login.username']}" required="true" />
<p:password id="j_password" name="j_password" placeholder="#{msgs['login.password']}" />
<p:commandButton value="#{msgs['login.submit']}" type="submit" form="@(#j_security_check)" ajax="false" />
</div>
</form> @ApplicationScoped
public class InitialIdentityProvider implements IdentityProvider<UsernamePasswordAuthenticationRequest> {
@Inject
AbstractIdendityService<?> idendityService;
@Override
public Class<UsernamePasswordAuthenticationRequest> getRequestType() {
return UsernamePasswordAuthenticationRequest.class;
}
@Override
public Uni<SecurityIdentity> authenticate(UsernamePasswordAuthenticationRequest request,
AuthenticationRequestContext authenticationRequestContext) {
String username = request.getUsername();
String password = new String(request.getPassword().getPassword());
AbstractIdendityService.Idendity idendity = idendityService.authenticate(username, password);
if (idendity == null) {
throw new AuthenticationFailedException();
}
return Uni.createFrom().item(
QuarkusSecurityIdentity.builder()
.setPrincipal(new QuarkusPrincipal(idendity.getId()))
.addCredential(request.getPassword())
.setAnonymous(false)
.addAttributes(idendity.getAttributes())
.addRoles(idendity.getRoles())
.build());
}
} @ApplicationScoped
public class IntrospectionIdentityProvider implements IdentityProvider<TrustedAuthenticationRequest> {
@Inject
AbstractIdendityService<?> idendityService;
@Inject
HttpConfiguration httpConfiguration;
@Override
public Class<TrustedAuthenticationRequest> getRequestType() {
return TrustedAuthenticationRequest.class;
}
@Override
public Uni<SecurityIdentity> authenticate(TrustedAuthenticationRequest request,
AuthenticationRequestContext authenticationRequestContext) {
AbstractIdendityService.Idendity idendity = idendityService.isAuthenticated(request.getPrincipal());
if (idendity == null) {
// we received a cookie with old login
// we need to remove the cookie, otherwise we get a infinite loop
RoutingContext routingContext = (RoutingContext) request.getAttributes().get(HttpSecurityUtils.ROUTING_CONTEXT_ATTRIBUTE);
routingContext.response().removeCookie(httpConfiguration.auth.form.cookieName);
throw new AuthenticationFailedException();
}
return Uni.createFrom().item(
QuarkusSecurityIdentity.builder()
.setPrincipal(new QuarkusPrincipal(idendity.getId()))
.setAnonymous(false)
.addAttributes(idendity.getAttributes())
.addRoles(idendity.getRoles())
.build());
}
} and some logout controller: @Named
@RequestScoped
public class IdentityFacesController {
@Inject
AbstractIdendityService<?> idendityService;
@Inject
HttpConfiguration httpConfiguration;
public void logout() throws IOException {
AbstractIdendityService.Idendity idendity = idendityService.getCurrentIdentity();
FacesContext facesContext = FacesContext.getCurrentInstance();
HttpServletRequest request = (HttpServletRequest) facesContext.getExternalContext().getRequest();
HttpServletResponse response = (HttpServletResponse) facesContext.getExternalContext().getResponse();
HttpSession session = (HttpSession) facesContext.getExternalContext().getSession(false);
try {
request.logout();
}
catch (ServletException e) {
Log.error("Error while calling logout", e);
}
session.invalidate();
Cookie authCookie = (Cookie) facesContext.getExternalContext().getRequestCookieMap().get(httpConfiguration.auth.form.cookieName);
if (authCookie != null) {
authCookie.setPath("/");
authCookie.setMaxAge(0);
response.addCookie(authCookie);
}
idendityService.remove(idendity);
facesContext.getExternalContext().redirect(httpConfiguration.auth.form.landingPage.get() + "?faces-redirect=true");
} @ApplicationScoped
public abstract class AbstractIdendityService<T extends AbstractIdendityService.Idendity>
implements IdentityService {
@Getter
protected final Map<String, T> identities = new ConcurrentHashMap<>();
@Inject
SecurityIdentity securityIdentity;
public T getCurrentIdentity() {
String id = securityIdentity.getPrincipal().getName();
return getIdentities().get(id);
}
@Override
public String getCurrentIdentityId() {
return securityIdentity.getPrincipal().getName();
}
public abstract T authenticate(String username, String password);
public Idendity isAuthenticated(String id) {
return identities.get(id);
}
public void remove(Idendity idendity) {
identities.remove(idendity.getId());
try {
release((T) idendity);
}
catch (Exception e) {
Log.error("Could not release identity " + idendity.id, e);
}
}
protected abstract void release(T identity);
public void removeAll() {
for (T identity : new ArrayList<>(identities.values())) {
remove(identity);
}
}
public void onShutdown(@Observes ShutdownEvent shutdownEvent) {
removeAll();
}
@Getter
@Setter
@EqualsAndHashCode
@AllArgsConstructor
public static class Idendity {
private String id;
private String username;
private Set<String> roles;
private Map<String, Object> attributes;
}
} |
Awesome @tandraschko ! |
Hello, I'm working on an app that has to use Form Authentication.
Normally we would just create a form posting to
/j_security_check
and that would work with Quarkus.But I need to do some work before calling the endpoint, therefore I'm calling a method from a
@Named
bean.The problem is that after doing this before-hand work, when I do call
((HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest()).login(username, password);
, the method works perfectly but Quarkus doesn't generate a Cookie.Have you found a solution for this?
Currently I'm picking the external host/url and manually calling
/j_security_check
with an absolute URL, but that is very ugly and error prone.The text was updated successfully, but these errors were encountered: