Challenge login, Basic Authentication - jetty

I'm using jetty 9, embedded.
I want to send a challenge when a username doesn't exist in my properties file.
I have extended the BasicAuthenticator class to catch the Exception normally thrown when a username doesn't exist. I can't see how to then after challenge the user with a login again.
public class MyBasicAuthenticator extends BasicAuthenticator{
#Override
public Authentication validateRequest(ServletRequest req,
ServletResponse res, boolean mandatory)
{
Authentication authentication = null;
try{
authentication = super.validateRequest(req,res,mandatory);
return authentication;
}catch (Exception e){
return Authentication.SEND_FAILURE;
}
}
Authentication.SEND_FAILURE prevents the ErrorHandler being called, but it just loads a blank page. SEND_FAILURE comes from the Authentication interface.
JavaDoc: http://download.eclipse.org/jetty/stable-9/apidocs/org/eclipse/jetty/security/authentication/BasicAuthenticator.html
According to the answer to this question Jetty6 had a send challenge method, Jetty9 doesn't.
Jetty UserRealm redirect on 3th failed login

This is a Basic Auth issue.
try {
authentication = super.validateRequest(req, res, mandatory);
} catch (Exception e) {
try{
HttpServletResponse r = (HttpServletResponse) res;
r.setHeader("WWW-Authenticate", "Basic");
r.setStatus(401);
} catch(Exception ew){}
authentication = Authentication.SEND_FAILURE;
}
Return 401 and a http header that includes "WWW-Authenticate":"Basic"
Authentication.SEND_FAILURE prevents an exception further down the handler chain.

Related

No longer working after removing my site from Facebook via "Apps and Websites"

My website (Spring MVC) allows users to sign up by using their Facebook accounts and sign into my site later with their Facebook accounts. I use scribejava (version 6.6.3) (https://github.com/scribejava/scribejava) for the Oauth integration with Facebook.
I have tested a use case and am unable to find a way to resolve it. Here is the list of steps:
The tester goes to my site's "Log in" page, clicks "Log in with Facebook", grants permissions at Facebook, gets redirected to my site, and signs out. This is a normal and successful flow.
The tester sign into Facebook at Facebook.com
The tester goes to Settings->Apps and Websites and removes my site
The tester goes to my site's "Log in" page, clicks "Log in with Facebook" button, gets redirected to Facebook, and sees an error message.
At step 4, the tester always gets an error message at the Facebook site instead of asking the tester to grant permissions again. See the following screenshot:
I cannot find a way at Facebook to remove this message when clicking on the "Log in with Facebook" button. Here is my code for the web interface. Did I miss something?
#RequestMapping( value="/facebook", method = RequestMethod.GET)
public void facebook(HttpServletRequest request,
#RequestParam(value = "page", required = true) String page,
HttpServletResponse response) throws Exception {
try {
OAuth20Service service = new ServiceBuilder(config.getProperty("facebook.clientId"))
.apiSecret(config.getProperty("facebook.clientSecret"))
.callback(getCallback())
.build(FacebookApi.instance());
String authUrl = service.getAuthorizationUrl();
response.sendRedirect(authUrl);
} catch (Exception e) {
response.sendRedirect("/oauthFail");
}
}
#RequestMapping( value="/facebook/callback", method = RequestMethod.GET)
public void facebookCallback(HttpServletRequest servletRequest,
#RequestParam(value = "code", required = false) String code,
#RequestParam(value = "error", required = false) String error,
HttpServletResponse servletResponse
) throws Exception {
try {
OAuth20Service service = new ServiceBuilder(config.getProperty("facebook.clientId"))
.apiSecret(config.getProperty("facebook.clientSecret"))
.callback(getCallback())
.build(FacebookApi.instance());
OAuth2AccessToken accessToken = service.getAccessToken(code);
final OAuthRequest request = new OAuthRequest(Verb.GET, "https://graph.facebook.com/v3.2/me");
service.signRequest(accessToken, request);
final com.github.scribejava.core.model.Response response = service.execute(request);
String body = response.getBody();
JSONObject jObject = new JSONObject(body);
String email = jObject.getString("email");
//success. use the email to create an account or if the email address exists, direct a userto their account page
} catch (Exception e) {
response.sendRedirect("/oauthFail");
}
}
How to handle this situation? I feel either something is wrong is my code or scribejava has a framework issue. Or this is a Facebook specific issue?
I have just tested the case. Couldn't reproduce.
Did you try running this Example
https://github.com/scribejava/scribejava/blob/master/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/FacebookExample.java
?
I think your problem can be with API versions logic in Facebook.
You can try to explicitly use the latest one .build(FacebookApi.customVersion("3.2"))

Same-Site flag for session cookie in Spring Security

Is it possible to set Same-site Cookie flag in Spring Security?
And if not, is it on a roadmap to add support, please? There is already support in some browsers (i.e. Chrome).
New Tomcat version support SameSite cookies via TomcatContextCustomizer. So you should only customize tomcat CookieProcessor, e.g. for Spring Boot:
#Configuration
public class MvcConfiguration implements WebMvcConfigurer {
#Bean
public TomcatContextCustomizer sameSiteCookiesConfig() {
return context -> {
final Rfc6265CookieProcessor cookieProcessor = new Rfc6265CookieProcessor();
cookieProcessor.setSameSiteCookies(SameSiteCookies.NONE.getValue());
context.setCookieProcessor(cookieProcessor);
};
}
}
For SameSiteCookies.NONE be aware, that cookies are also Secure (SSL used), otherwise they couldn't be applied.
By default since Chrome 80 cookies considered as SameSite=Lax!
See SameSite Cookie in Spring Boot and SameSite cookie recipes.
For nginx proxy it could be solved easily in nginx config:
if ($scheme = http) {
return 301 https://$http_host$request_uri;
}
proxy_cookie_path / "/; secure; SameSite=None";
UPDATE from #madbreaks:
proxy_cookie_flags iso proxy_cookie_path
proxy_cookie_flags ~ secure samesite=none;
Instead of a Filter, In your Authentication Success Handler, you can mention in this way.
#Override
public void onAuthenticationSuccess(
HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException {
response.setStatus(HttpServletResponse.SC_OK);
clearAuthenticationAttributes(request);
addSameSiteCookieAttribute(response);
handle(request, response);
}
private void addSameSiteCookieAttribute(HttpServletResponse response) {
Collection<String> headers = response.getHeaders(HttpHeaders.SET_COOKIE);
boolean firstHeader = true;
// there can be multiple Set-Cookie attributes
for (String header : headers) {
if (firstHeader) {
response.setHeader(HttpHeaders.SET_COOKIE,
String.format("%s; %s", header, "SameSite=Strict"));
firstHeader = false;
continue;
}
response.addHeader(HttpHeaders.SET_COOKIE,
String.format("%s; %s", header, "SameSite=Strict"));
}
}
It was mentioned in one of the answers. Couldn't find the link after I've implemented it.
All possible solutions here failed for me. Every time I tried a filter or interceptor, the Set-Cookie header had not yet been added. The only way I was able to make this work was by adding Spring Session and adding this bean into one of my #Configuration files:
#Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setSameSite("none");
return serializer;
}
Anyway hope this helps someone else in my same situation.
You can always set cookie values by yourself in the Java world if you can get an instance of the HttpServletResponse.
Then you can do:
response.setHeader("Set-Cookie", "key=value; HttpOnly; SameSite=strict")
In spring-security you can easily do this with a filter, here is an example:
public class CustomFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse resp = (HttpServletResponse) response;
resp.setHeader("Set-Cookie", "locale=de; HttpOnly; SameSite=strict");
chain.doFilter(request, response);
}
}
Add this filter to your SecurityConfig like this:
http.addFilterAfter(new CustomFilter(), BasicAuthenticationFilter.class)
Or via XML:
<http>
<custom-filter after="BASIC_AUTH_FILTER" ref="myFilter" />
</http>
<beans:bean id="myFilter" class="org.bla.CustomFilter"/>
It isn't possible. There is support for this feature in Spring Session: https://spring.io/blog/2018/10/31/spring-session-bean-ga-released
I came up with a solution similar to Ron's one. But there is one important thing to note:
Cookies for cross-site usage must specify SameSite=None; Secure
to enable inclusion in third party context.
So I've included Secure attribute in header. Also, you don't have to override all three methods when you don't use them. It is only required when you are implementing HandlerInterceptor.
import org.apache.commons.lang.StringUtils;
public class CookiesInterceptor extends HandlerInterceptorAdapter {
final String sameSiteAttribute = "; SameSite=None";
final String secureAttribute = "; Secure";
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
addEtagHeader(request, response);
Collection<String> setCookieHeaders = response.getHeaders(HttpHeaders.SET_COOKIE);
if (setCookieHeaders == null || setCookieHeaders.isEmpty())
return;
setCookieHeaders
.stream()
.filter(StringUtils::isNotBlank)
.map(header -> {
if (header.toLowerCase().contains("samesite")) {
return header;
} else {
return header.concat(sameSiteAttribute);
}
})
.map(header -> {
if (header.toLowerCase().contains("secure")) {
return header;
} else {
return header.concat(secureAttribute);
}
})
.forEach(finalHeader -> response.setHeader(HttpHeaders.SET_COOKIE, finalHeader));
}
}
I used xml in my project so I had to add this to my configuration file:
<mvc:interceptors>
<bean class="com.zoetis.widgetserver.mvc.CookiesInterceptor"/>
</mvc:interceptors>
Using the interceptor in SpringBoot.
I'm looking for a resolution for adding SameSite as you, and I only want to add the attribute to the existing "Set-Cookie" instead of creating a new "Set-Cookie".
I have tried several ways to meet this requirement, including:
adding a custom filter as #unwichtich said,
and more I overrode basicAuthenticationFilter. It does add the SameSite attribute. While the timing when Spring will add the "Set-Cookie" is hard to catch. I thought in onAuthenticationSuccess() method, the response must have this header, but it doesn't. I'm not sure whether it's the fault of my custom basicAuthenticationFilter's order.
using cookieSerializer, but the spring-session version comes up to a problem. Seems only the latest version support it, but I still can't figure out the version number should be added into the dependency list.
Unfortunately, none of them above can add the samesite well as expected.
Finally, I found the interceptor in spring can help me to make it.
It took me a week to get it. Hope this can help you if anyone has the same problem.
#Component
public class CookieServiceInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
#Override
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
//check whether it has "set-cookie" in the response, if it has, then add "SameSite" attribute
//it should be found in the response of the first successful login
Collection<String> headers = response.getHeaders(HttpHeaders.SET_COOKIE);
boolean firstHeader = true;
for (String header : headers) { // there can be multiple Set-Cookie attributes
if (firstHeader) {
response.setHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=strict"));
firstHeader = false;
continue;
}
response.addHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=strict"));
}
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception exception) throws Exception {
}
}
and you also need to make this interceptor work in your application, which means you should add a bean as below:
#Autowired
CookieServiceInterceptor cookieServiceInterceptor;
#Bean
public MappedInterceptor myInterceptor() {
return new MappedInterceptor(null, cookieServiceInterceptor);
}
This interceptor has a flaw, it can't add samesite when the request is redirected(ex.return 302) or failed(ex. return 401), while it makes my app fail when SSO. Eventually, I have to use the Tomcat cookie, because I don't embed tomcat in my springboot app. I add
<Context>
<CookieProcessor sameSiteCookies="none" />
</Context>
in a context.xml under /META-INF of my app. It will add SameSite attribute in set-cookie header for each response. Note that this behavior is possible since Tomcat 9.0.21 and 8.5.42. according to https://stackoverflow.com/a/57622508/4033979
For Spring Webflux (reactive environment) this worked for me:
#Configuration
#EnableSpringWebSession
public class SessionModule {
#Bean
public ReactiveSessionRepository<MapSession> reactiveSessionRepository() {
return new ReactiveMapSessionRepository(new ConcurrentHashMap<>());
}
#Bean
public WebSessionIdResolver webSessionIdResolver() {
CookieWebSessionIdResolver resolver = new CookieWebSessionIdResolver();
resolver.setCookieName("SESSION");
resolver.addCookieInitializer((builder) -> {
builder.path("/")
.httpOnly(true)
.secure(true)
.sameSite("None; Secure");
});
return resolver;
}
}
You can add cookie by yourself by using ResponseCookie and adding it to your HttpServletResponse.
ResponseCookie cookie = ResponseCookie.from("cookiename", "cookieValue")
.maxAge(3600) // one hour
.domain("test.com")
.sameSite("None")
.secure(true)
.path("/")
.build();
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
I have tested this solution for spring-webmvc without spring-security, but I think it should also work for spring-boot.
Using the SessionRepositoryFilter bean from spring-session-core
You can extend default java HttpSession with a spring Session and replace JSESSIONID cookie with a custom one, like this:
Set-Cookie: JSESSIONID=NWU4NzY4NWUtMDY3MC00Y2M1LTg1YmMtNmE1ZWJmODcxNzRj; Path=/; Secure; HttpOnly; SameSite=None
Additional spring Session cookie flags can be set using DefaultCookieSerializer:
#Configuration
#EnableSpringHttpSession
public class WebAppConfig implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) {
servletContext
.addFilter("sessionRepositoryFilter", DelegatingFilterProxy.class)
.addMappingForUrlPatterns(null, false, "/*");
}
#Bean
public MapSessionRepository sessionRepository() {
final Map<String, Session> sessions = new ConcurrentHashMap<>();
MapSessionRepository sessionRepository =
new MapSessionRepository(sessions) {
#Override
public void save(MapSession session) {
sessions.entrySet().stream()
.filter(entry -> entry.getValue().isExpired())
.forEach(entry -> sessions.remove(entry.getKey()));
super.save(session);
}
};
sessionRepository.setDefaultMaxInactiveInterval(60*5);
return sessionRepository;
}
#Bean
public SessionRepositoryFilter<?> sessionRepositoryFilter(MapSessionRepository sessionRepository) {
SessionRepositoryFilter<?> sessionRepositoryFilter =
new SessionRepositoryFilter<>(sessionRepository);
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
cookieSerializer.setCookieName("JSESSIONID");
cookieSerializer.setSameSite("None");
cookieSerializer.setUseSecureCookie(true);
CookieHttpSessionIdResolver cookieHttpSessionIdResolver =
new CookieHttpSessionIdResolver();
cookieHttpSessionIdResolver.setCookieSerializer(cookieSerializer);
sessionRepositoryFilter.setHttpSessionIdResolver(cookieHttpSessionIdResolver);
return sessionRepositoryFilter;
}
}
I have extended a bit MapSessionRepository implementation, since it does NOT support firing SessionDeletedEvent or SessionExpiredEvent - I have added clearing of expired sessions before adding new ones. I think this might be enough for a small application.
Apparently, with spring boot you can write this and it gets picked up.
#Configuration
public static class WebConfig implements WebMvcConfigurer {
#Bean
public CookieSameSiteSupplier cookieSameSiteSupplier(){
return CookieSameSiteSupplier.ofNone();
}
}
Or ... even simpler, spring boot since 2.6.0 supports setting it in application.properties.
Spring documentation about SameSite Cookies
server.servlet.session.cookie.same-site = none

Appending cookies in ASP.Net core when response status code = 500

In ASP.Net Core application, how can I set a cookie and throw an exception during the same request?
In a Web API call, whenever I set a cookie using Response.Cookies.Append() the cookie header is only sent to the browser when returning from the call without throwing any exception.
If I append the cookie and then throw an exception, the cookie header is lost.
What gives? Am I missing something fundamental here? This doesn't seem like expected behavior.
Instead of not handling the exception (returning a 500), you could catch the exception and output a cookie with a controlled response. Something like:
[HttpGet]
public IActionResult Get()
{
try
{
throw new Exception("some exception");
}
catch (Exception ex)
{
// issue cookie and return response
Response.Cookies.Append("someCookie", "my value");
return BadRequest();
}
}

Camel exchange expired via jetty continuation

Is there a possibility in Apache Camel to register a handler for managing exchanges that cannot be written to jetty endpoint http response because continuation timeout has been reached?
I'll just add my notes on that because I made it available in my project by modifying CamelContinuationServlet in the if (continuation.isExpired()) block like this
if (continuation.isExpired()) {
String id = (String) continuation.getAttribute(EXCHANGE_ATTRIBUTE_ID);
// remember this id as expired
expiredExchanges.put(id, id);
log.warn("Continuation expired of exchangeId: {}", id);
consumer.getBinding().doWriteExceptionResponse(new TimeoutException(), response);
return;
}
in combination with a custom HttpBinding called ErrorHandlingHttpBinding in my code like this
public class ErrorHandlingHttpBinding extends DefaultHttpBinding {
#Override
public void doWriteExceptionResponse(Throwable exception, HttpServletResponse response) throws IOException {
if (exception instanceof TimeoutException) {
response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
response.getWriter().write("Continuation timed out...");
} else {
super.doWriteExceptionResponse(exception, response);
}
}
}
registered as spring bean with id="errorHandlingHttpBinding" and referred in the component string as jetty:http://localhost:21010/?useContinuation=true&continuationTimeout=1&httpBindingRef=errorHandlingHttpBinding.
No this is not possible. Maybe you need to set a higher timeout if you have some slow processing exchanges.
You are welcome to dive in the Jetty APIs to see if you can find a hook for such a onTimeout event and see what it takes to support that in camel-jetty.

WSO2 User self registration

I am trying to call the WSO2 Identity server web service using a java client.
public static void main(String[] args) {
UserInformationRecoveryService service = new UserInformationRecoveryService();
UserInformationRecoveryServicePortType port = service
.getUserInformationRecoveryServiceHttpsSoap11Endpoint();
BindingProvider prov = (BindingProvider) port;
prov.getRequestContext()
.put(BindingProvider.USERNAME_PROPERTY, "admin");
prov.getRequestContext()
.put(BindingProvider.PASSWORD_PROPERTY, "admin");
try {
List<UserIdentityClaimDTO> list = port
.getUserIdentitySupportedClaims("http://schemas.xmlsoap.org/ws/2005/05/identity");
System.out.println(port
.registerUser("user96", "Asdf#234", list, "profile", "")
.getUserId().getValue());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
When I pass in password not matching the password criteria, I can see the error message in WSO2 logs:
Password pattern policy violated. Password should contain a digit[0-9], a lower case letter[a-z], an upper case letter[A-Z], one of !##$%&* characters
But when the password is compliant, I just get an error
org.wso2.carbon.identity.mgt.services.UserInformationRecoveryServiceIdentityMgtServiceException_Exception: Error occurred while adding user : user96
Nothing in WSO2 log and the user is not added. Any clue as to what is wrong here?
Better enable debug logs in the log configuration file.
[IS_Home]/repository/conf/log4j.properties
Add the following entry
log4j.logger.org.wso2.carbon.user.core=DEBUG
This will give the debug logs which will help to understand the issue