How to logout programmatically from magnolia cms - java

I'd like to create custom REST for logging out users. I created jax-rs based endpoint definition with one method /logout:
#Path("/test")
public class MyEndpoint<D extends EndpointDefinition> extends AbstractEndpoint<D> {
#Path("/logout")
#GET
#Produces(MediaType.APPLICATION_JSON)
public void logout() {
//how to logout user here?
}
}
What code should I put in place of //how to logout user here? to make it work?

You can inject the following component and trigger logout from it.
info.magnolia.context.UserContext

I created working solution based on info.magnolia.cms.security.LogoutFilter
#Path("/logout")
#GET
#Produces(MediaType.APPLICATION_JSON)
public void logout(#Context HttpServletRequest request) {
info.magnolia.context.Context ctx = MgnlContext.getInstance();
if (ctx instanceof UserContext) {
AuditLoggingUtil.log((UserContext) ctx);
((UserContext) ctx).logout();
}
if (request.getSession(false) != null) {
request.getSession().invalidate();
}
}

Related

Logout with JAAS login module

The question is a little bit longer than expected. Below is the link to a similar one (3rd post) where I didn't find the answer satisfying.
TL;DR
I am trying to logout using the JAAS Login Module. Here is the brief structure of the project:
LoginService is responsible for instantiating LoginContext when a user wants to log in:
#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.
}
The LoginController calls the method:
#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
}
}
The issue arises when I want to logout previously logged in user. LoginContext is responsible for calling the logout method in the JAAS Login Module. For instance:
loginContext.logout();
The method in the JAAS Login Module:
public class JAASLoginModule implements LoginModule {
#Override
public boolean logout() {
subject.getPrincipals().remove(usernamePrincipal);
subject.getPrincipals().remove(passwordPrincipal);
return true;
}
}
I don't have the LoginContext in LogoutService and unable to completely clear the previously authenticated subject.
I tried to create a singleton bean to get the same instance of the 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());
}
}
}
The solution is not particularly useful, since next / the same user to log in will get NPE on the LoginContext.
I read that HttpServletRequest's getSession().invalidate(); suppose to call the logout() of JAAS or that HttpServletRequest's logout() would do the job. But both methods have no effect. For instance:
#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();
}
}
I want to get the hand on the previously created LoginContext when a user wants to log out but create a new one when another user tries to log in.
Please note that I am not using Spring Security.
EDIT:
One of the ideas was to use a singleton that will hold a Set of login contexts associated with the particular user. And then call and destroy them when the user logs out. A key for such a Set could be a JWT token or user id. After further thinking, it appeared to me that a user might have multiple sessions, and in this case, user id as a key will fail to serve its purpose. The second option is a JWT token, but there is a scenario when the future middleware will issue a new JWT token upon expiration, then my Set will have no way to return a valid login context.
After some research, my team decided that JAAS doesn't suit our needs. We are not using the complete functionality it has to offer, and it ties our hands rather than smoothing the developing process.
If you will encounter a similar issue, here is an explanation:
we are using WebSphere 8.5.5 that has the support of JAAS. It is possible to logout, but the price will be tying it to the application server. Considering that in our plans is to move from WebSphere, this implementation is not an option.
The link to one of such guides lies here.
There are two alternatives for the future:
Wrap it in Spring Security since it offers support for JAAS;
Replace the custom module entirely relying on Spring Security's
functionality.

how to inject headers in a `#context HttpServletRequest`?

Let's say I have this code:
#ApplicationPath("...")
public class MyApp extends ResourceConfig {
public SalesLayerApplication() {
this.register(HeaderInjecterFilter.class);
this.register(Test.class);
}
}
#PreMatching
public class HeaderInjecterFilter implements ContainerRequestFilter {
#Override
public void filter(final ContainerRequestContext crc) throws IOException {
crc.getHeaders().add("foo", "bar");
}
}
#Path("/test")
public class Test {
#GET
#Produces(MediaType.TEXT_PLAIN)
public String dump(#Context final HttpServletRequest request) {
return request.getHeader("foo");
}
}
I was expecting to call the rest entry point /test and to retrieve the string bar.
But all I see is null
If I use #HeaderParam("foo") I correctly retrieve the variable, but I need to access throug the #Context HttpServletRequest.
Why would you expect that adding headers to the ContainerRequestContext would also add it to the HttpServletRequest? These are completely unrelated entities. Try injecting HttpHeaders or you can also inject the ContainerRequestContext directly.

How to Optionally Protect a Resource with Custom Dropwizard Filter

I'm using Dropwizard 0.9.2 and I want to create a resource that requires no authentication for GET and requires basic authentication for POST.
I have tried
#Path("/protectedPing")
#Produces(MediaType.TEXT_PLAIN)
public class ProtectedPing {
#GET
public String everybody() {
return "pingpong";
}
#PermitAll
#POST
public String authenticated(){
return "secret pingpong";
}
with
CachingAuthenticator<BasicCredentials, User> ca = new CachingAuthenticator<>(environment.metrics(), ldapAuthenticator, cbSpec);
AdminAuthorizer authorizer = new AdminAuthorizer();
BasicCredentialAuthFilter<User> bcaf = new BasicCredentialAuthFilter.Builder<User>().setAuthenticator(ca).setRealm("test-oauth").setAuthorizer(authorizer).buildAuthFilter();
environment.jersey().register(bcaf);
environment.jersey().register(RolesAllowedDynamicFeature.class);
environment.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));
environment.jersey().register(new ProtectedPing());
This seems to result in all requests to "/protectedPing" requiring basic auth.
In Dropwizard 0.9.2 the documentation says to create a custom filter if I have a resource that is optionally protected. I'm assuming I need to do that, but I don't know where to start, or if that I what I actually need to do.
this is more of a jersey problem than a dropwizard problem. You can have a look here: https://jersey.java.net/documentation/latest/filters-and-interceptors.html
Essentially what you want is:
Create an annotation that indicates that you want to test for authentication (e.g. #AuthenticatePost)
Create the resource and annotate the correct method with #AuthenticatePost
Create your authentication filter (probably kind of like what you did above).
In the dynamic feature, test for the annotation to be present on the passed in resource. This will hold true for post, false for get. Then register the AuthenticationFilter directly on the resource method instead of globally on the resource.
This would be a semi-complete example of how I would solve this:
public class MyDynamicFeature implements DynamicFeature {
#Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
if(resourceInfo.getResourceMethod().getAnnotation(AuthenticateMe.class) != null ) {
context.register(MyAuthFilter.class);
}
}
public class MyAuthFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// do authentication here
}
}
public #interface AuthenticateMe {
}
#Path("myPath")
public class MyResource {
#GET
public String get() {
return "get-method";
}
#POST
#AuthenticateMe
public String post() {
return "post-method";
}
}
}
Note, the DynamicFeature checks that the Authenticate Annotation is present, before registering the authentication with the feature context.
I hope that helps,
let me know if you have any questions.

How to redirect to external url from service class?

In my current spring project, I am adding several pairs of classes (controller/service) to provide payment option through several payment services. Each one of this classes have a structure like that:
controller
#Controller
#RequestMapping(value = "pagseguro")
public class pagseguroPaymentController extends paymentController<Pagseguro> {
...
#Override
#RequestMapping(value = "comprar", method = RequestMethod.POST)
public void comprar(String payerId, String guid) throws Exception {
this.payment.comprar(payerId, guid);
}
...
}
service
#Service
public class pagseguroPaymentService extends paymentService<Pagseguro> {
...
#Override
public void comprar(String payerId, String guid) throws Exception {
...
String response = checkout.register(null, false);
}
...
}
in the method comprar from service class, I need redirect the application to an URL stored in a String variable (response in the example above).
My initial idea is use the library java.net from Java and create a utilitary class like that:
public class Redirect {
public static String url;
public static void redirect() {
...
}
}
Anyone can give me a hint about how to accomplish that?
To perform a simple redirection, you can inject the HttpServletResponse in your Controller layer:
#RequestMapping(value = "comprar", method = RequestMethod.POST)
public void comprar(String payerId, String guid, HttpServletResponse response) throws Exception {
this.payment.comprar(payerId, guid);
}
And then simply do a redirect using that object:
response.sendRedirect("http://newUrl");
Now - as the Luiggi mentions in the comment - I would not do the redirect itself in the service layer, but rather return a boolean which decides if the redirect needs to be done - and then do it here, in the controller layer.
Hope it helps.

Controller library direct access to HttpServletRequest

I have numerous controllers in my application that extend a central controller class. Currently, in every controller function, I have to pass the request into this function in order for my function to grab the current username. Is it possible for this controller class to get the request on it's own without requiring it as an extra parameter?
public class Controller {
protected String environment;
public Controller () {
}
public ModelAndView prepareModel (HttpServletRequest request, ModelAndView model) {
contentDao.clearExpiredLocks();
model.addObject("currentUser", contentDao.findUser(request.getRemoteUser()));
//define current environment
this.environment = (request.getRequestURL().indexOf("localhost") > 0) ? "dev" : "uat";
model.addObject("environment", this.environment);
You can get the current HttpServletRequest as follows:
HttpServletRequest request = (HttpServletRequest) RequestContextHolder
.currentRequestAttributes()
.resolveReference(RequestAttributes.REFERENCE_REQUEST);
You can use this code in a method of your controller, or use it to expose request as a request-scoped bean and inject the corresponding scoped proxy as a field of your controller.
You can use something like this:
public abstract class AbstractController {
protected HttpServletRequest req
protected AbstractController(HttpServletRequest req) {
this.req = req
}
}
public class ConcreteController extends AbstractController {
protected ConcreteController(String name) {
super(name);
}
private void getUserName(){
this.req.getRemoteUser();
}
}
That's just one quick tip, I believe that there are more possibilities how to do that.
In my case, What I did is : I get user MainController.getLoginPerson() and use all user's info in all controllers. All controllers extends to MainController.
Here is method MainController.getLoginPerson():
MainController.getLoginPerson() {
// calls to authentication service method
authenticationService.getLoginPerson();
}
authenticationService.getLoginPerson() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
return (UserPrincipalImpl) auth.getPrincipal();
} else {
return null;
}
}

Categories