使用 JAAS 登录模块注销
Logout with JAAS login module
问题比预期的要长一些。下面是 link 到类似的(第 3 个 post),我没有找到令人满意的答案。
我正在尝试使用 JAAS 登录模块注销。这是项目的简要结构:
LoginService
负责实例化 LoginContext
当用户想要登录时:
@Service
public class LoginService {
public UserDTO getUserDTOFrom(Credentials credentials) {
try {
LoginContext loginContext = new LoginContext("Login", new JAASCallbackHandler(credentials));
loginContext.login();
// construct UserDTO object.
} catch (LoginException e) {
LOGGER.error("Login Exception: {}", e.getMessage());
// construct UserDTO object.
}
// return UserDTO object.
}
LoginController
调用方法:
@RestController
@RequestMapping("/login")
public class LoginController {
private final LoginService loginService;
@Autowired
public LoginController(LoginService loginService) {
this.loginService = loginService;
}
@PostMapping
public ResponseEntity<UserDTO> getUserDTOFrom(@Valid @RequestBody Credentials credentials) {
UserDTO userDTO = loginService.getUserDTOFrom(userForm);
// return response that depends on outcome in the login service
}
}
当我想注销之前登录的用户时出现问题。 LoginContext
负责调用JAAS Login Module中的logout方法。例如:
loginContext.logout();
JAAS 登录模块中的方法:
public class JAASLoginModule implements LoginModule {
@Override
public boolean logout() {
subject.getPrincipals().remove(usernamePrincipal);
subject.getPrincipals().remove(passwordPrincipal);
return true;
}
}
我在LogoutService
中没有LoginContext
,无法完全清除之前验证过的主题。
我试图创建一个单例 bean 来获取 LoginContext
:
的相同实例
@Configuration
public class LoginContextBean {
@Lazy
@Bean
public LoginContext getLoginContext(Credentials credentials) throws LoginException {
System.setProperty("java.security.auth.login.config", "resources/configuration/jaas.config");
return new LoginContext("Login", new JAASCallbackHandler(credentials));
}
}
@Service
public class LoginService {
private final ObjectProvider<LoginContext> loginContextProvider;
@Autowired
public LoginService(ObjectProvider<LoginContext> loginContextProvider) {
this.loginContextProvider = loginContextProvider;
}
public UserDTO getUserDTOFrom(Credentials credentials) {
try {
LoginContext loginContext = loginContextProvider.getObject(credentials);
loginContext.login();
// construct UserDTO object.
} catch (LoginException e) {
LOGGER.error("Login Exception: {}", e.getMessage());
// construct UserDTO object.
}
// return UserDTO object.
}
}
@Service
public class LogoutService {
private final ObjectProvider<LoginContext> loginContextProvider;
@Autowired
public LogoutService(ObjectProvider<LoginContext> loginContextProvider) {
this.loginContextProvider = loginContextProvider;
}
public void performLogout() {
LoginContext loginContext = loginContextProvider.getObject();
try {
loginContext.logout();
} catch (LoginException e) {
LOGGER.error("Failed to logout: {}.", e.getMessage());
}
}
}
该解决方案不是特别有用,因为下一个/同一用户登录将在 LoginContext
上获得 NPE。
我读到 HttpServletRequest
的 getSession().invalidate();
应该调用 JAAS 的 logout()
或者 HttpServletRequest
的 logout()
可以完成这项工作。但是这两种方法都没有效果。例如:
@RestController
@RequestMapping("/logout")
public class LogoutController {
private final LogoutService logoutService;
@Autowired
public LogoutController(LogoutService logoutService) {
this.logoutService = logoutService;
}
@DeleteMapping
public ResponseEntity<Void> deleteJwt(@CookieValue("jwt_cookie") String jwtToken, HttpServletRequest request) throws ServletException {
request.getSession().invalidate(); // logout() is not called.
request.logout(); // logout() is not called.
return getResponse();
}
}
我想在用户想要注销时获取之前创建的 LoginContext
,但在另一个用户尝试登录时创建一个新的。
请注意,我没有使用 Spring 安全性。
编辑:
其中一个想法是使用一个单例来保存与特定用户关联的 Set
登录上下文。然后在用户注销时调用并销毁它们。这种 Set
的键可以是 JWT 令牌或用户 ID。进一步思考后,我觉得一个用户可能有多个会话,在这种情况下,用户id作为键就达不到目的了。第二个选项是 JWT 令牌,但有一种情况是未来的中间件会在到期时发出新的 JWT 令牌,那么我的 Set
将无法 return 有效的登录上下文。
经过一些研究,我的团队认为 JAAS 不适合我们的需要。我们没有使用它必须提供的完整功能,它束缚了我们的双手,而不是使开发过程变得顺畅。
如果您遇到类似的问题,这里有一个解释:
我们正在使用支持 JAAS 的 WebSphere 8.5.5。可以注销,但价格将与应用程序服务器绑定。考虑到我们的计划是从 WebSphere 迁移,此实现不是一个选项。
其中一个指南的 link 位于 here.
未来有两种选择:
- 将其包装在 Spring 安全性中,因为它提供 support for JAAS;
- 替换完全依赖Spring安全的自定义模块
功能。
问题比预期的要长一些。下面是 link 到类似的(第 3 个 post),我没有找到令人满意的答案。
我正在尝试使用 JAAS 登录模块注销。这是项目的简要结构:
LoginService
负责实例化 LoginContext
当用户想要登录时:
@Service
public class LoginService {
public UserDTO getUserDTOFrom(Credentials credentials) {
try {
LoginContext loginContext = new LoginContext("Login", new JAASCallbackHandler(credentials));
loginContext.login();
// construct UserDTO object.
} catch (LoginException e) {
LOGGER.error("Login Exception: {}", e.getMessage());
// construct UserDTO object.
}
// return UserDTO object.
}
LoginController
调用方法:
@RestController
@RequestMapping("/login")
public class LoginController {
private final LoginService loginService;
@Autowired
public LoginController(LoginService loginService) {
this.loginService = loginService;
}
@PostMapping
public ResponseEntity<UserDTO> getUserDTOFrom(@Valid @RequestBody Credentials credentials) {
UserDTO userDTO = loginService.getUserDTOFrom(userForm);
// return response that depends on outcome in the login service
}
}
当我想注销之前登录的用户时出现问题。 LoginContext
负责调用JAAS Login Module中的logout方法。例如:
loginContext.logout();
JAAS 登录模块中的方法:
public class JAASLoginModule implements LoginModule {
@Override
public boolean logout() {
subject.getPrincipals().remove(usernamePrincipal);
subject.getPrincipals().remove(passwordPrincipal);
return true;
}
}
我在LogoutService
中没有LoginContext
,无法完全清除之前验证过的主题。
我试图创建一个单例 bean 来获取 LoginContext
:
@Configuration
public class LoginContextBean {
@Lazy
@Bean
public LoginContext getLoginContext(Credentials credentials) throws LoginException {
System.setProperty("java.security.auth.login.config", "resources/configuration/jaas.config");
return new LoginContext("Login", new JAASCallbackHandler(credentials));
}
}
@Service
public class LoginService {
private final ObjectProvider<LoginContext> loginContextProvider;
@Autowired
public LoginService(ObjectProvider<LoginContext> loginContextProvider) {
this.loginContextProvider = loginContextProvider;
}
public UserDTO getUserDTOFrom(Credentials credentials) {
try {
LoginContext loginContext = loginContextProvider.getObject(credentials);
loginContext.login();
// construct UserDTO object.
} catch (LoginException e) {
LOGGER.error("Login Exception: {}", e.getMessage());
// construct UserDTO object.
}
// return UserDTO object.
}
}
@Service
public class LogoutService {
private final ObjectProvider<LoginContext> loginContextProvider;
@Autowired
public LogoutService(ObjectProvider<LoginContext> loginContextProvider) {
this.loginContextProvider = loginContextProvider;
}
public void performLogout() {
LoginContext loginContext = loginContextProvider.getObject();
try {
loginContext.logout();
} catch (LoginException e) {
LOGGER.error("Failed to logout: {}.", e.getMessage());
}
}
}
该解决方案不是特别有用,因为下一个/同一用户登录将在 LoginContext
上获得 NPE。
我读到 HttpServletRequest
的 getSession().invalidate();
应该调用 JAAS 的 logout()
或者 HttpServletRequest
的 logout()
可以完成这项工作。但是这两种方法都没有效果。例如:
@RestController
@RequestMapping("/logout")
public class LogoutController {
private final LogoutService logoutService;
@Autowired
public LogoutController(LogoutService logoutService) {
this.logoutService = logoutService;
}
@DeleteMapping
public ResponseEntity<Void> deleteJwt(@CookieValue("jwt_cookie") String jwtToken, HttpServletRequest request) throws ServletException {
request.getSession().invalidate(); // logout() is not called.
request.logout(); // logout() is not called.
return getResponse();
}
}
我想在用户想要注销时获取之前创建的 LoginContext
,但在另一个用户尝试登录时创建一个新的。
请注意,我没有使用 Spring 安全性。
编辑:
其中一个想法是使用一个单例来保存与特定用户关联的 Set
登录上下文。然后在用户注销时调用并销毁它们。这种 Set
的键可以是 JWT 令牌或用户 ID。进一步思考后,我觉得一个用户可能有多个会话,在这种情况下,用户id作为键就达不到目的了。第二个选项是 JWT 令牌,但有一种情况是未来的中间件会在到期时发出新的 JWT 令牌,那么我的 Set
将无法 return 有效的登录上下文。
经过一些研究,我的团队认为 JAAS 不适合我们的需要。我们没有使用它必须提供的完整功能,它束缚了我们的双手,而不是使开发过程变得顺畅。
如果您遇到类似的问题,这里有一个解释:
我们正在使用支持 JAAS 的 WebSphere 8.5.5。可以注销,但价格将与应用程序服务器绑定。考虑到我们的计划是从 WebSphere 迁移,此实现不是一个选项。
其中一个指南的 link 位于 here.
未来有两种选择:
- 将其包装在 Spring 安全性中,因为它提供 support for JAAS;
- 替换完全依赖Spring安全的自定义模块 功能。