spring 4.1 javaConfg setting to get requestCache working - spring

similar to this:
Spring 3.1: Redirect after login not working
when an authenticated user becomes inauthenticated while deep-linking into a single page web app.
Spring security redirects to logon but:
SavedRequest savedRequest = new HttpSessionRequestCache().getRequest(request, response);
is null
thus i cannot devine the url or params to send re-authenticated user to requested page
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers() //redacted .
.antMatchers("/**").permitAll()
.and()
.formLogin()
.loginPage("/x/y/logon")
.usernameParameter("userLogon") //redacted
.loginProcessingUrl("/x/y/logon") //redacted
.defaultSuccessUrl("/x/", true)
.failureUrl("/x/y/logon?error=true")
.and()
.logout()
.logoutUrl("/x/y/logout")
.logoutSuccessUrl("/x/")
.permitAll();
}
}
-- controller --
#RequestMapping(method=RequestMethod.GET, value="/y/logon")
public ModelAndView logonHandler(HttpServletRequest request, HttpServletResponse response) {
List<Client> clients = //manager call for list of clients to show at logon
SavedRequest savedRequest = new HttpSessionRequestCache().getRequest(request, response);
if (savedRequest != null) {
String reqUrl = savedRequest.getRedirectUrl();
String[] urlParams = reqUrl.split("&");
String prefix = "";
String urlParam = "";
String cid = "";
try {
urlParam = urlParams[1];
} catch(IndexOutOfBoundsException ioob) { }
if (reqUrl.contains("cid=")) { cid = reqUrl.substring(reqUrl.indexOf("cid=")+4, reqUrl.indexOf("&")); }
if (reqUrl.contains("?")) { reqUrl = reqUrl.substring(0, reqUrl.indexOf("?")); }
prefix = reqUrl.substring(reqUrl.indexOf("/x/")+6, reqUrl.indexOf("/x/")+8);
reqUrl = reqUrl.substring(reqUrl.indexOf(prefix)+2);
if (reqUrl.contains("/")) {
reqUrl = reqUrl.substring(0, reqUrl.indexOf("/"));
}
request.setAttribute("requestUrl", prefix+reqUrl);
request.setAttribute("urlParam", urlParam);
request.setAttribute("cid", cid);
}
request.setAttribute("IPAddress", request.getRemoteAddr());
return new ModelAndView("x/logon", "clients", clients);
}
problem is, SavedRequest is null
is this an issue with:
alwaysUseDefaultTargetUrl property?
if yes, how in javaConfig does one set this property?
----- on edit to address comments ------
i'll explain my understanding of ea. .formLogon() settings:
logonPage() will be read by spring and control redirect to logon page when you are not authorized (cookie expire/db record del, etc). There are many ways that a session can not be authorized and spring needs to know what page to send unauth requests to. My manual logon handler only handles requests to the logon url.
usernameParameter() is to change from the default form input name thus obfuscating that one is using spring security.
loginProcessingUrl() this seems to conflict with the custom logonHandler, but i think its req to handle the post and allow for spring to create a secure sesson.
defaultSucessUrl() tells spring where to go after successful logon (the post request).
failureUrl() defines the url for failed logon.
nowhere in my custom logon handler for the get request, are those settings in conflict... i think... but i've read the docs and the Spring Security 3 book and lots of online resources and i still do not feel as though i have a solid understanding of spring security... so i may be way off

Related

Query a password protected WebService in Spring-Boot via HealthIndicator

I need to query a WebService and check its results to calculate a Health.status() in SpringBoot.
Health is then used for k8s in its liveness probe: /actuator/health.
However, the WebService that I need to query needs a username and password, whereas the endpoint itself does not, as it is consumed by k8s and Prometheus. Please see the following configuration:
http
.authorizeRequests()
.requestMatchers(EndpointRequest.to("info", "health")).permitAll()
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.sessionManagement().sessionCreationPolicy(STATELESS)
.and()
.csrf().disable()
As the WebService in question can be queried when httpBasic() is used to authenticate, I need to somehow use basic authentication in HealthIndicator without passing these values in the request.
What I want to do is to have "health" permitted for unauthorised requests, but still transparently authenticate with a technical user to query a third party service. Please see comment in following snippet:
override fun health(): Health {
val status = Health.up()
val beforeServiceCall = System.nanoTime()
val partner =
try {
// COMMENT FOR StackOverflow
// this call here needs authentication resp. a token which is only generated when basicAuth is used
partnerService.getPartner(
process = metricsProperties.process,
partnerNumber = metricsProperties.partnernumber
)
} catch (e: Exception) {
null
}
val afterServiceCall = System.nanoTime()
return status
.withDetail(PARTNER_FOUND, partner.let {
if (it != null) {
1.0
} else {
status.unknown()
0.0
}
})
.withDetail(PARTNER_QUERY_TIME_MS, ((afterServiceCall - beforeServiceCall) / 1e6).toDouble())
.build()
tl;dr;
I need to somehow trigger the WebSecurityConfigurerAdapter's configure method from within my HealthIndicator to obtain a valid token to be able to query a third party service. How can this configure method be triggered explicitly?
#Configuration
#EnableWebSecurity
class SecurityConfig(omitted params) :
WebSecurityConfigurerAdapter() {
override fun configure(auth: AuthenticationManagerBuilder) {
auth
.authenticationProvider(object : AuthenticationProvider {
override fun authenticate(authentication: Authentication): Authentication? {
val authorizationGrant = ResourceOwnerPasswordCredentialsGrant(authentication.name,
Secret(authentication.credentials as String))
val tokenRequest = TokenRequest(URI(keycloakUrl),
ClientID(clientID),
authorizationGrant)
val tokenResponse = TokenResponse
.parse(tokenRequest
.toHTTPRequest()
.send())
if (tokenResponse.indicatesSuccess()) {
return UsernamePasswordAuthenticationToken(
authentication.name,
null,
emptyList())
} else {
throw BadCredentialsException("Error logging in")
}
}
override fun supports(authentication: Class<*>): Boolean {
return authentication == UsernamePasswordAuthenticationToken::class.java
}
})
.eraseCredentials(false)
}

Manage Multiple Login page with Spring authorization server

Is this possible to manage multiple login page with spring authorization server?
Suppose, we have 2 client and both client want a different login page
client 1 need /login url
client 2 need /login2 url..
I believe the question is how to brand a login page based on the current clientId. We can use any technique available in Spring Security, as it is fully available and not hidden when using Spring Authorization Server.
As you point out, one way to handle this would be to perform a custom redirect when authentication is required. This would be handled in a custom AuthenticationEntryPoint. You can build a delegate with a mapping of clientIds to login urls. Normally, I'd encourage you to try it yourself (learning new things is fun, right!?), but in this case, here's an example:
public class BrandedAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final Map<String, AuthenticationEntryPoint> authenticationEntryPoints;
private final AuthenticationEntryPoint defaultEntryPoint = new LoginUrlAuthenticationEntryPoint("/login");
public BrandedAuthenticationEntryPoint(Map<String, String> loginUrls) {
Map<String, AuthenticationEntryPoint> map = new HashMap<>();
loginUrls.forEach((clientId, loginUrl) ->
map.put(clientId, new LoginUrlAuthenticationEntryPoint(loginUrl)));
this.authenticationEntryPoints = Collections.unmodifiableMap(map);
}
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
AuthenticationEntryPoint delegate = this.defaultEntryPoint;
// Attempt to resolve a specific login url based on clientId
String clientId = request.getParameter("clientId");
if (clientId != null) {
delegate = this.authenticationEntryPoints.getOrDefault(clientId, this.defaultEntryPoint);
}
delegate.commence(request, response, authException);
}
}
SAS and Form Login are two different filter chains in the default sample, so you would apply this in the normal way on both filter chains:
http.exceptionHandling(exceptionHandling -> exceptionHandling
.authenticationEntryPoint(new BrandedAuthenticationEntryPoint(...))
);
More information on AuthenticationEntryPoint is available in the reference docs.

How to hook into Spring Security authentication process?

Currently I have this trivial configuration:
// Kotlin code
override fun configure(http: HttpSecurity) {
http
.formLogin()
.loginPage("/entry")
.loginProcessingUrl("/auth")
.usernameParameter("usr")
.passwordParameter("pwd")
.defaultSuccessUrl("/", true)
.failureHandler { request, response, exception ->
// Can't figure out what to enter here (see below).
}
}
If authentication fails, I have two requirements:
Flash error message into the session (avoiding 'error' param in query string). It seems I can't inject RedirectAttributes into this lambda; is there a workaround?
I want to send back the login (but not the password) that user entered before submitting login form, in order to repopulate the field. How do I do that?
I was able to figure it out.
#Configuration
#EnableWebSecurity
class SecurityConfig: WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http
.formLogin()
.loginPage("/entry")
.loginProcessingUrl("/auth")
.usernameParameter("usr")
.passwordParameter("pwd")
.defaultSuccessUrl("/", true)
.failureHandler { request, response, _ ->
request.session.setAttribute("loginError", "Login Error!")
request.session.setAttribute("failedUsername", request.getParameter("usr"))
response.sendRedirect("/entry")
}
}
}
Then, you have to set up login controller to customize serving of login form:
#Controller
#RequestMapping("/entry")
internal class LoginController {
#GetMapping
fun getLoginForm(session: HttpSession, model: Model): String {
if (session.getAttribute("loginError") != null) {
model.addAttribute("loginError", "Login Error!")
session.removeAttribute("loginError")
model.addAttribute("failedUsername", session.getAttribute("failedUsername"))
session.removeAttribute("failedUsername")
}
return "login"
}
}
Then, you can use loginError and failedUsername model attributes in your templates:
<div th:if="${loginError}">Incorrect login/password</div>
<!-- ... -->
<input type="text" name="usr" th:value="${failedUsername}">
Basically we are emulating "flashing" messages into session. We carry these messages in the session and remove them as soon as they are read and passed on into the model. It’s possible that redirect will go wrong and messages will remain in the session, but they are harmless on their own, plus they will be removed the next time user visits /entry page.
As a result, now there is no ?error in page URL, and the user is not required to retype username.

Make Angular client login to Spring backend with JWT

I added JWT to my Spring backend following this guide: https://auth0.com/blog/securing-spring-boot-with-jwts/
When I send a PUT request using a software like Postman everything works fine, but as soon as I'm trying to login with my Angular client the data in the HttpServletRequest is empty.
I check the data in the attemptAuthentication method of the JWTLoginFilter in the following way:
#Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
throws AuthenticationException, IOException, ServletException {
String reqBody = req.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
// this appears to be empty on angular client calls
System.out.println(reqBody);
ObjectMapper objectMapper = new ObjectMapper().configure(Feature.AUTO_CLOSE_SOURCE, true)
.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
AccountCredentials creds = objectMapper.readValue(reqBody, AccountCredentials.class);
return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(creds.getUsername(),
creds.getPassword(), Collections.emptyList()));
}
I'm sending the request out of the client like this:
const user = {
username: "asdf",
password: "asdf"
};
// imported from '#angular/http'
const headers = new Headers({
'Content-Type': 'application/json'
});
const body = JSON.stringify(user);
return this.http
.put("http://localhost:8080/api/login", body, {headers})
.toPromise()
.then(response => response.json().data as User)
.catch(this.handleError);
My suggestion would be that I'm sending the request body in a wrong way, but I can't see, what I'm doing wrong.
I tried:
sending the body as a regular JS object
sending it wrapped in another object
sending it as a JSON string as shown in the example
using POST instead of PUT (although it works with PUT in Postman)
changing the Content-Type Header to other values
None of this made any data appear in the backend.
If you need more information about anything, please ask me.
I got it.
I needed to allow CORS also on my HttpSecurity object as following:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.so()
.on();
}
}
I'm not really sure, why the requests I sent with Postman could get received without CORS enabled in my HttpSecurity, but nevertheless it's working now.
I hope I can help others with this in future.
This might be the reason, Spring OAuth 2 normally expect a POST request with URL encoded username and password. So try something like this.
return this.http.post(Url, "grant_type=password&username=" + username + "&password=" + password)
I'm not 100% sure if this the case as mine was a Spring-MVC, but I expect it to be very similar.

How to set, get and validate sessions in JSF like PHP $_SESSION[''] [duplicate]

I would like to block the access of some page even if the user knows the url of some pages.
For example, /localhost:8080/user/home.xhtml (need to do the login first) if not logged then redirect to /index.xhtml.
How do that in JSF ? I read in the Google that's needed a filter, but I don't know how to do that.
You need to implement the javax.servlet.Filter class, do the desired job in doFilter() method and map it on an URL pattern covering the restricted pages, /user/* maybe? Inside the doFilter() you should check the presence of the logged-in user in the session somehow. Further you also need to take JSF ajax and resource requests into account. JSF ajax requests require a special XML response to let JavaScript perform a redirect. JSF resource requests need to be skipped otherwise your login page won't have any CSS/JS/images anymore.
Assuming that you've a /login.xhtml page which stores the logged-in user in a JSF managed bean via externalContext.getSessionMap().put("user", user), then you could get it via session.getAttribute("user") the usual way like below:
#WebFilter("/user/*")
public class AuthorizationFilter implements Filter {
private static final String AJAX_REDIRECT_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<partial-response><redirect url=\"%s\"></redirect></partial-response>";
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
HttpSession session = request.getSession(false);
String loginURL = request.getContextPath() + "/login.xhtml";
boolean loggedIn = (session != null) && (session.getAttribute("user") != null);
boolean loginRequest = request.getRequestURI().equals(loginURL);
boolean resourceRequest = request.getRequestURI().startsWith(request.getContextPath() + ResourceHandler.RESOURCE_IDENTIFIER + "/");
boolean ajaxRequest = "partial/ajax".equals(request.getHeader("Faces-Request"));
if (loggedIn || loginRequest || resourceRequest) {
if (!resourceRequest) { // Prevent browser from caching restricted resources. See also https://stackoverflow.com/q/4194207/157882
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
response.setHeader("Pragma", "no-cache"); // HTTP 1.0.
response.setDateHeader("Expires", 0); // Proxies.
}
chain.doFilter(request, response); // So, just continue request.
}
else if (ajaxRequest) {
response.setContentType("text/xml");
response.setCharacterEncoding("UTF-8");
response.getWriter().printf(AJAX_REDIRECT_XML, loginURL); // So, return special XML response instructing JSF ajax to send a redirect.
}
else {
response.sendRedirect(loginURL); // So, just perform standard synchronous redirect.
}
}
// You need to override init() and destroy() as well, but they can be kept empty.
}
Additionally, the filter also disabled browser cache on secured page, so the browser back button won't show up them anymore.
In case you happen to use JSF utility library OmniFaces, above code could be reduced as below:
#WebFilter("/user/*")
public class AuthorizationFilter extends HttpFilter {
#Override
public void doFilter(HttpServletRequest request, HttpServletResponse response, HttpSession session, FilterChain chain) throws ServletException, IOException {
String loginURL = request.getContextPath() + "/login.xhtml";
boolean loggedIn = (session != null) && (session.getAttribute("user") != null);
boolean loginRequest = request.getRequestURI().equals(loginURL);
boolean resourceRequest = Servlets.isFacesResourceRequest(request);
if (loggedIn || loginRequest || resourceRequest) {
if (!resourceRequest) { // Prevent browser from caching restricted resources. See also https://stackoverflow.com/q/4194207/157882
Servlets.setNoCacheHeaders(response);
}
chain.doFilter(request, response); // So, just continue request.
}
else {
Servlets.facesRedirect(request, response, loginURL);
}
}
}
See also:
Our Servlet Filters wiki page
How to handle authentication/authorization with users in a database?
Using JSF 2.0 / Facelets, is there a way to attach a global listener to all AJAX calls?
Avoid back button on JSF web application
JSF: How control access and rights in JSF?
While it's of course legitimate to use a simple Servlet filter, there are alternatives like
Spring Security
Java EE Security
Apache Shiro

Resources