Glassfish7多WAR应用SSO配置异常:登录后重定向回Login.war
核心问题分析
1. 容器认证与自定义登录逻辑冲突
Login.war的web.xml配置了FORM类型的容器认证,但LoginController里又自行实现了用户名密码校验、手动往session存入用户信息的逻辑。这导致容器安全上下文未完成认证流程,Start.war通过getUserPrincipal()检查时,容器认为用户未认证,直接重定向回登录页。
2. Login.war未触发容器认证流程
Login.war的web.xml仅配置了<login-config>,但没有添加<security-constraint>。容器只有在访问受保护资源时才会触发认证,Login.war没有受保护资源,容器不会初始化认证会话,自定义登录的session属性无法被容器的安全机制识别。
3. Start.war登录页路径配置错误
Start.war中<form-login-page>配置为/login/login.xhtml,但Login.war的上下文根是Login,正确路径应为/Login/login.xhtml,否则容器无法找到登录页(不过这不是导致重定向回Login的核心原因,但会引发其他异常)。
4. 自定义重定向未利用容器认证跳转机制
FORM认证模式下,容器会自动处理登录成功后的跳转逻辑(跳转到用户最初请求的受保护页面),而手动调用redirect()会跳过容器的认证会话同步,导致Start.war无法识别已认证状态。
解决方案
方案一:完全使用容器FORM认证(推荐)
1. 调整Login.war的web.xml
移除自定义登录逻辑依赖,确保容器能正确处理认证:
<?xml version="1.0" encoding="UTF-8"?> <web-app ...> <display-name>Login</display-name> <welcome-file-list> <welcome-file>login.xhtml</welcome-file> </welcome-file-list> <login-config> <auth-method>FORM</auth-method> <realm-name>jdbcRealm</realm-name> <form-login-config> <form-login-page>/login.xhtml</form-login-page> <form-error-page>/login-error.xhtml</form-error-page> </form-login-config> </login-config> <!-- 保留其他配置,如Faces Servlet、session-config等 --> </web-app>
2. 修改Login.xhtml的表单
使用容器认证的标准表单参数,action必须为j_security_check:
<h:form id="loginForm" action="j_security_check"> <h:inputText id="username" name="j_username" value="#{loginController.username}" /> <h:inputSecret id="password" name="j_password" value="#{loginController.password}" /> <h:commandButton value="Login" type="submit" /> </h:form>
3. 简化LoginController
移除自定义校验逻辑,仅保留属性 getter/setter:
package login.web; import java.io.Serializable; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Named; @RequestScoped @Named public class LoginController implements Serializable { private static final long serialVersionUID = -5346634778960677989L; private String username; private String password; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
4. 修正Start.war的登录页路径
<form-login-config> <form-login-page>/Login/login.xhtml</form-login-page> <form-error-page>/Login/login-error.xhtml</form-error-page> </form-login-config>
5. 开启GlassFish跨应用SSO
在domain.xml中确保开启单签:
<glassfish-web-app> <single-sign-on enabled="true" /> </glassfish-web-app>
或通过GlassFish管理控制台,在配置的HTTP服务中开启SSO。
方案二:保留自定义登录,同步容器安全上下文
如果必须保留自定义校验逻辑,需在登录成功后手动触发容器认证:
public void login() { FacesContext context = FacesContext.getCurrentInstance(); ExternalContext externalContext = context.getExternalContext(); User user = loginService.findUserByUsername(username); if (user != null && user.getPassword().equals(SHA256Util.hashPassword(password))) { try { // 触发容器认证,同步安全上下文 externalContext.login(username, password); context.getExternalContext().getSessionMap().put("user", user); externalContext.redirect(URL + "/Start"); } catch (IOException e) { e.printStackTrace(); } } else { context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Invalid username or password", null)); } }
同时确保Login.war和Start.war的session cookie路径均为/(已在配置中设置),且GlassFish开启跨应用SSO。
内容的提问来源于stack exchange,提问作者herohannes

