I'm currently developping an application with Quarkus usig an http form authentication. After reading "Form based authentication" page (https://quarkus.io/guides/security-built-in-authentication) I'm facing an issue with the logout stage. The documentation does not mention any option to logout the current session.
As Quarkus internally uses Vert.x, I tried the following:
#Path("/auth")
public class AuthController {
#LoggerName("AuthController")
Logger log;
#ConfigProperty(name = "quarkus.http.auth.form.cookie-name")
String COOKIE_NAME;
#ConfigProperty(name = "quarkus.http.auth.form.location-cookie")
String REDIRECT_COOKIE_NAME;
#ConfigProperty(name = "quarkus.http.auth.form.login-page")
String LOGIN_PAGE;
#GET
#Path("/logout")
public void logout(#Context RoutingContext ctx) {
var c1 = ctx.removeCookie(COOKIE_NAME);
var c2 = ctx.removeCookie(REDIRECT_COOKIE_NAME);
log.info(String.format("c1 = %s, c2 = %s", c1.getName(), c2.getName()));
ctx.redirect(LOGIN_PAGE);
}
}
This does not works like expected. The log.info logs well ([AuthController] (executor-thread-0) c1 = MyCookieName, c2 = quarkus-redirect-location) and the redirection works fine. However the session persists.
How can i fixed it ?
Thanks for the help,
After several research and tries, a solution I found is to invalidate the cookie with Javascript using:
document.cookie = "MyCookieName=; Max-Age=0";
This invalidate the cookie and redirect to the login page.
For the record, something like this seems to work (with resteasy-reactive):
import io.vertx.core.http.HttpServerResponse;
import org.jboss.resteasy.reactive.RestResponse;
#Path("/logout")
public class UserLogout {
#ConfigProperty(name = "quarkus.http.auth.form.cookie-name")
String cookieName;
#Inject
UriInfo uriInfo;
#POST
public RestResponse<Object> logout(HttpServerResponse response) {
URI loginUri = uriInfo.getRequestUriBuilder().replacePath("/login").build();
// Note that we set invalidate=true to expire the cookie
response.removeCookie(cookieName, true);
return RestResponse.seeOther(loginUri);
}
}
PS. io.vertx.ext.web.RoutingContext.removeCookie(String, boolean) is deprecated and #Context is unnecessary in resteasy-reactive.
Related
How can I create a session in a spring mvc application from a given ID instead of a generated one?
I want to fixate the session.
The fixation will be started by a trusted ui service. This trusted service forwards all requests. Thus the fixation can't begin in browser. It is not intended to do it without this ui service.
Providing a HttpSessionIdResolver bean does not work, since it only changes the location in HTTP response. Eg Session ID in HTTP header after authorization.
Are there any solutions without creating a shadow session management?
Session is required for keycloak integration. Guess it's not possible to use keycloak in stateless mode.
Thanks in advance
It is possible to fixate a session with spring-session.
#Configuration
#EnableSpringHttpSession
public class SessionConfig {
#Bean
public HttpSessionIdResolver httpSessionIdResolver() {
return new HttpSessionIdResolver() {
public List<String> resolveSessionIds(HttpServletRequest request) {
final var sessionId = request.getHeader("X-SessionId");
request.setAttribute(SessionConfig.class.getName() + "SessionIdAttr", sessionId);
return List.of(sessionId);
}
public void setSessionId(HttpServletRequest request, HttpServletResponse response, String sessionId) {}
public void expireSession(HttpServletRequest request, HttpServletResponse response) {}
};
}
#Bean
public SessionRepository<MapSession> sessionRepository() {
return new MapSessionRepository(new HashMap<>()) {
#Override
public MapSession createSession() {
var sessionId =
(String)RequestContextHolder
.currentRequestAttributes()
.getAttribute(SessionConfig.class.getName()+"SessionIdAttr", 0);
final var session = super.createSession();
if (sessionId != null) {
session.setId(sessionId);
}
return session;
}
};
}
In #resolveSessionIds() you can read the ID and store for later use.
createSession() is called when no session has been found and a new one is required. Here you can create the session with previously remembered ID in MapSession#setId(String).
Even if is possible, IMO it is not a good idea. There might be other architectural solutions/problems.
We are implementing a simple health check API our load balancers can call to help with the routing of requests. If the status of the application is "standby", requests should not be sent to it. Only the admins can set the state to "up" or "standby", but anyone (including the load balancers) can get the status of the application.
We are trying this with Spring Boot 2, but are having problems configuring security to grant anonymous access to just one of the routes. Consider the following controller:
#RestController
public class AppStatusController {
private static final String STATUS = "status";
String state = "standby";
private String getState() {
return state;
}
private Map<String, String> getStatusMap() {
Map<String, String> retval = new HashMap<>();
retval.put(STATUS, getState());
return retval;
}
// GET calls are public, all others require AuthN & AuthZ
#GetMapping(path = "/appstatus", produces = "application/json")
public Map<String, String> getStatus() {
return getStatusMap();
}
// Only those with the ADMIN role can POST to this endpoint
#PostMapping(path = "/appstatus", consumes = "application/json", produces = "application/json")
public Map<String, String> setStatus(#RequestBody Map state) {
// Validate and update the state
return getStatusMap();
}
}
There is only one endpoint, /appstatus, but one method is called with an HTTP GET and the other with an HTTP POST. We want calls to getStatus to be public, but allow Spring Security to control access to setStatus. One might expect an annotation such as #Anonymous or something similar to be applied to the getStatus() method but we can't seem to find one.
Some have suggested using a separate #Configuration class and setting up antMatchers but it's not clear how we can match on the HTTP method.
Does anyone have suggestions on how to configure Spring Security to allow public access to GET method requests but control access to other methods?
EDIT: We are trying to avoid any authentication on the getStatus() call. We can't store auth credentials in the health check probe and can't perform a login exchange. This is a simple GET request to see if the application is up and ready for operation.
Have you tried using Method Security Expressions?
It looks like this will do what you want:
// GET calls are public, all others require AuthN & AuthZ
#GetMapping(path = "/appstatus", produces = "application/json")
#PreAuthorize("permitAll")
public Map<String, String> getStatus() {
return getStatusMap();
}
// Only those with the ADMIN role can POST to this endpoint
#PostMapping(path = "/appstatus", consumes = "application/json", produces = "application/json")
#PreAuthorize("hasRole('ADMIN')")
public Map<String, String> setStatus(#RequestBody Map state) {
// Validate and update the state
return getStatusMap();
}
Note: I don't know what roles your admins have, so I used 'ADMIN' as a placeholder.
I just started to learn Spring Boot today, and I wanted to create a GET/POST request for my Spring Boot Project. When I tried to access the URL that has the post request it shows 405 error saying that "Request method 'GET' not supported".
I think it is something wrong about my code for the POST request, but I don't know where I did wrong. I tried to search for the a tutorial that teaches how to write a proper GET/POST request, so I couldn't find anything good.
If you guys have any good website that teaches basic HTTP requests in Spring Boot, that will be great. I tried to find answers at StackOverflow, but I didn't find anything answers.
The Spring Boot project I have been using is the one from the official Spring.io website: https://spring.io/guides/gs/rest-service/
I wanted to call the POST request for my project so I have a better understanding of the HTTP.
Here is the source code for the controller:
package hello;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.web.bind.annotation.*;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
#RestController
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
// GET Request
#RequestMapping(value="/greeting", method = GET)
public Greeting greeting(#RequestParam(value="name", defaultValue="World") String name) {
return new Greeting(counter.incrementAndGet(), name);
}
#RequestMapping(value = "/testpost", method = RequestMethod.POST)
public String testpost() {
return "ok";
}
}
Here is the source code for the Application:
package hello;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
And here is the source code for the Greeting Object
package hello;
public class Greeting {
private final long id;
private final String content;
public Greeting(long id, String content) {
this.id = id;
this.content = content;
}
public long getId() {
return id;
}
public String getContent() {
return content;
}
}
I can get the GET request working by using the "/greeting" URL.
I tried to access the "/testpost" url but it shows 405 error that the GET method is not supported.
There was an unexpected error (type=Method Not Allowed, status=405).
Request method 'GET' not supported
If you try to open the http://localhost:8080/testpost by directly opening in browser, it won't work because opening in browser makes a GET request.
I am not sure how you are trying to do a post request, I tried to do the same post request from postman and able to get the response. Below is the screenshot.
It looks like you are trying to make post request directly from web browser which will not work.
When you hit a URL directly from web browser address bar, it is considered as GET request. Since in your case, there is no GET API as /testpost , it is giving error.
Try to use rest client such as Postman or curl command to make post request.
I tried your post end-point with postman and it is working properly. PFA snapshot for your reference.
Hope this helps.
From where you are trying POST request. If from browser windows you calling POST call, then it will not work, browser will send only GET request. Have you tried from postman or from UI side. It will work.
I'm using Spring Boot 1.5.4, Spring Data REST, Spring Security. I created a #Controller mapped to a specific path that doesn't require authentication because it used from a sms gateway for report incoming texts.
So I've just to create a controller to read those parameters and then save the text on the db. And here there is the problem. To store data I use repositories that are secured, while in the controller I've not any kind of security (in fact I cannot ask the provider to secure its calls).
I tried to set an authentication context programatically but seems not working:
#Controller
#RequestMapping(path = "/api/v1/inbound")
#Transactional
public class InboundSmsController {
private Logger log = LogManager.getLogger();
#RequestMapping(method = RequestMethod.POST, path = "/incomingSms", produces = "text/plain;charset=ISO-8859-1")
public ResponseEntity<?> incomingSms(#RequestParam(name = "Sender", required = true) String sender,
#RequestParam(name = "Destination", required = true) String destination,
#RequestParam(name = "Timestamp", required = true) String timestamp,
#RequestParam(name = "Body", required = true) String body) {
log.info(String.format("Text received from %s to %s at %s with content: %s", sender, destination, timestamp, body));
setupAuthentication();
try {
int transitsWithSameTextToday = transitCertificateRepository.countByTextAndDate(body, Instant.now()); //This is the method that raises an Auth exception
....
....
} finally(){
clearAuthentication();
}
SecurityContext context;
/**
* Set in the actual context the authentication for the system user
*/
private void setupAuthentication() {
context = SecurityContextHolder.createEmptyContext();
Collection<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_ADMIN");
Authentication authentication = new UsernamePasswordAuthenticationToken("system", "ROLE_ADMIN", authorities);
context.setAuthentication(authentication);
}
private void clearAuthentication() {
context.setAuthentication(null);
}
The method countByTextAndDate is annotated with #PreAuthorize("isAuthenticated()")
I'm surprised also setting the Auth context I've this error. Am I doing something wrong? Is this the best way to reach my goal?
I don't want to annotate my method with #PermitAll because that method is also exposed by Spring Data REST and I don't want anyone can use that.
You are looking for AccessDecisionManager's RunAsManager. Here's the link that could help you with this :
http://www.baeldung.com/spring-security-run-as-auth
Happy Coding!!!
I want to connect to a REST server with a jaxrs client using apache cxf. The server has an url to authenticate and some other urls to do the actual stuff. After the login the server creates a session and keeps the connection open for 30 min. My problem is that the client doesn't store the cookies and I always get a new (not authenticated) session on the server.
I configured the clients in my spring application context.
<jaxrs:client id="loginResource"
serviceClass="com.mycompany.rest.resources.LoginResource"
address="${fsi.application.url}">
</jaxrs:client>
<jaxrs:client id="actionResource"
serviceClass="com.mycompany.rest.resources.ActionResource"
address="${fsi.application.url}">
</jaxrs:client>
How can I configure both clients to use the same session or share the session-cookie between the clients?
I have been struggling with the same problem, and I just finally arrived at a solution.
1) Make the client retain cookies.
WebClient.getConfig(proxy).getRequestContext().put(
org.apache.cxf.message.Message.MAINTAIN_SESSION, Boolean.TRUE);
Perhaps there's a way to accomplish the above via configuration vs. programmatically.
2) Copy the cookies from one client to the other.
public static void copyCookies(Object sourceProxy, Object targetProxy) {
HTTPConduit sourceConduit = WebClient.getConfig(sourceProxy).getHttpConduit();
HTTPConduit targetConduit = WebClient.getConfig(targetProxy).getHttpConduit();
targetConduit.getCookies().putAll(sourceConduit.getCookies());
}
After using proxy A to authenticate, I call the above method to share its cookies with proxy B, which does the actual work.
I use the following request/response filter to pass the session-id in each request:
ClientResponseFilter >> used to extract the session-id from session-cookie
ClientRequestFilter >> pass the session-id as cookie header in each request
private class SessionIdRequestFilter implements ClientRequestFilter, ClientResponseFilter {
/** cookie header key */
private static final String COOKIE_KEY = "Cookie"; //$NON-NLS-1$
/** name of the session cookie */
private String cookieName = "JSESSIONID";
/** holds the session id from the cookie */
private String sessionId;
#Override
public void filter(ClientRequestContext request) throws IOException {
// append session cookie to request header
if (!request.getHeaders().containsKey(COOKIE_KEY) && sessionId != null) {
request.getHeaders().add(COOKIE_KEY, getCookieName() + "=" + sessionId); //$NON-NLS-1$
}
}
#Override
public void filter(ClientRequestContext request, ClientResponseContext response) throws IOException {
// find sessionId only if not already set
if (sessionId == null) {
Map<String, NewCookie> cookies = response.getCookies();
if (cookies != null && !cookies.isEmpty()) {
NewCookie cookie = cookies.get(getCookieName());
if (cookie != null) {
sessionId = cookie.getValue();
}
}
}
}
private String getCookieName() {
return cookieName;
}
}
To register the filter use:
RestClientBuilder.newBuilder().baseUri(apiUri).register(new SessionIdRequestFilter());