I am writing several web applications based on both spring security and the spring security saml extension (RC2).
I have single sign on working with multiple service providers and an Identity provider in a basic fashion (based off the example defined in the spring saml docs).
When the user accesses a protected resource on the SP, he is forwarded to a protected resource on the IDP. So because the user isn't logged in yet, they are redirected to a login page (standard spring security stuff). After logging in, the original request is played back and the authNRequest/Response is done and the user is redirected to the original secured resource.
I now have a requirement that ensures that all services providers must ask the identity provider whether the user is logged in before each request (instead of doing it locally at the SP).
It is my understanding that a local (SP) and remote (IDP) security context is stored and queried during each request and if there isn't a valid context, the user is then forwarded to the identity provider to go through the auth process.
So my question is, is there a way I can configure saml/spring security on the SP side to always "ping" or ask the IDP to check if the current user is logged in or is this sort of thing unnecessary/unsupported.
thanks in advance
You are right, Spring SAML queries the local security context during each request and forwards user to IDP once it becomes invalid.
The typical mechanism which defines when the context becomes invalid is usage of SAML's attribute SessionNotOnOrAfter. The attribute is included inside the Assertion's AuthenticationStatement sent back from IDP. Spring SAML will automatically re-authenticate the user once the time goes beyond the value provided in the SessionNotOnOrAfter.
In case you would like to re-authenticate on every request, you could for example add a new custom filter similar to this:
package fi.test;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.FilterInvocation;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
public class ReAuthenticateFilter extends GenericFilterBean {
private static final String FILTER_APPLIED = "__spring_security_filterReAuthenticate_filterApplied";
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
protected void invoke(FilterInvocation fi) throws IOException, ServletException {
if ((fi.getRequest() != null) && (fi.getRequest().getAttribute(FILTER_APPLIED) != null)) {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} else {
if (fi.getRequest() != null) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
}
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
if (authentication != null) {
authentication.setAuthenticated(false);
}
}
}
}
You would then include the filter in your Spring configuration:
<security:http entry-point-ref="samlEntryPoint">
<security:custom-filter after="SECURITY_CONTEXT_FILTER" ref="reAuthenticateFilter"/>
...
</security:http>
<bean id="reAuthenticateFilter" class="fi.test.ReAuthenticateFilter"/>
Re-authenticating on every request is rather expensive operation (a round-trip to the IDP through user's browser) and is likely to result in poor responsiveness of the application.
Related
I have a Spring application that is secured using Oauth provided by Twitch. What I am trying to do is make it so that when the user clicks the logout button they have to reenter their Twitch credentials to log back into the site. From the reading I have done my understanding of the situation is that single sign off is fairly hard to achieve with Oauth. That said, Twitch's API seems to indicate that there is a way to tell them to invalidate an Oauth token: https://dev.twitch.tv/docs/authentication#revoking-access-tokens.
I have seen some information about a prompt=login parameter in OIDC but I have not been able to find any information about it or how to use it (on top of that I'm pretty sure that will require the users to reenter their credentials EVERY time rather than simply when they logout).
My initial approach to this problem was the number 1 answer in this thread but that did not actually change anything. I still was not required to enter my credentials upon trying to access one of the restricted endpoints, Spring simply quickly reauthenticated with Twitch and sent me through.
My current approach was to directly hit the endpoint in the Twitch api with a POST request (this was inspired by a comment in this thread). This approach is also not doing anything. I will include the code of that approach below.
I would much prefer to solve this problem with something built into Spring (holding out hope that there is something that I have somehow missed). An interesting thing I have been noticing happening is that when I hit /logout my browser will be redirected to Twitch's authorization endpoint which is making me think that for some reason Spring is trying to send a token revoke request to the authorization endpoint OR is logging me back into the endpoint as soon as I am logged out of it. Thought this was worth mentioning.
Any help replicating the initially mentioned behavior would be much appreciated.
SpringSecurityConfiguration:
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
#Configuration
#EnableWebSecurity
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity httpSecurity) throws Exception {
//This allows users to access the "/" and "/Info" endpoints without authenticating with Twitch. To go anywhere else they will have to authenticate.
httpSecurity.antMatcher("/**").authorizeRequests().antMatchers("/", "/Info", "/token/deletion").permitAll().anyRequest().authenticated().and().oauth2Login().and()
//This configures logout tells spring to do the logout with the method in the logoutSuccessHandler
.logout().logoutSuccessUrl("http://localhost:8080/token/deletion").invalidateHttpSession(true).clearAuthentication(true).deleteCookies("JSESSIONID", "JWT");
}
application.properties:
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
spring.security.oauth2.client.registration.twitch=twitch
spring.security.oauth2.client.registration.twitch.client-id=redacted
spring.security.oauth2.client.registration.twitch.client-secret=redacted
spring.security.oauth2.client.registration.twitch.client-authentication-method=post
spring.security.oauth2.client.registration.twitch.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.twitch.redirect-uri=http://localhost:8080/login/oauth2/code/twitch
spring.security.oauth2.client.registration.twitch.scope=user:read:email
spring.security.oauth2.client.registration.twitch.client-name=Twitch
spring.security.oauth2.client.provider.twitch.authorization-uri=https://id.twitch.tv/oauth2/authorize
spring.security.oauth2.client.provider.twitch.token-uri=https://id.twitch.tv/oauth2/token
spring.security.oauth2.client.provider.twitch.jwk-set-uri=https://id.twitch.tv/oauth2/keys
spring.security.oauth2.client.provider.twitch.user-info-uri=https://id.twitch.tv/oauth2/userinfo
spring.security.oauth2.client.provider.twitch.user-info-authentication-method=post
spring.security.oauth2.client.provider.twitch.user-name-attribute=sub
TokenRemovalController:
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.net.ssl.HttpsURLConnection;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
#Controller
public class TokenRemovalController {
#RequestMapping("/token/deletion")
public void removeTokenFromTwitch(HttpServletResponse response, #RegisteredOAuth2AuthorizedClient("twitch") OAuth2AuthorizedClient authorizedClient) throws IOException {
//Get access token of current user
String accessToken = authorizedClient.getAccessToken().getTokenValue();
//POST Request to Twitch endpoint
URL url = new URL("https://id.twitch.tv/oauth2/revoke");
HttpsURLConnection https = (HttpsURLConnection)url.openConnection();
https.setRequestMethod("POST");
https.setDoOutput(true);
https.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
String data = "client_id=redacted&token=" + accessToken;
byte[] out = data.getBytes(StandardCharsets.UTF_8);
OutputStream stream = https.getOutputStream();
stream.write(out);
String redirectString = "/error";
if (https.getResponseCode() == 200) {
redirectString = "/Info";
}
https.disconnect();
response.sendRedirect(redirectString);
}
}
Project Structure:
Another thing I forgot to mention was that if I delete all of my browsers cookies then I do get logged out of my site. Is it potentially possible to replicate this effect when users attempt to log out (obviously without deleting all of the users other cookies)?
I'm trying to add single sign on to two legacy systems on different domains. That currently have working "regular" login.
I found this https://stackoverflow.com/a/9925146 but I'm unsure about the step 1 more specifically this
"implement functionality to serialize and write the Authentication object to a Session cookie with a global scope."
If I understand this correctly I should extract the sessionID and add it to a new cookie with a global scope.
I started by trying to extract the sessionID like so
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException,ServletException {
Cookie cookie = null;
UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication;
if (authentication.getDetails() != null) {
WebAuthenticationDetails dets = (WebAuthenticationDetails) auth.getDetails();
System.out.println("sessionID: " + dets.getSessionId());
}
response.addCookie(cookie);
super.onAuthenticationSuccess(request,response,authentication);
}
}
To verify that I'm on the right track i print the sessionID to terminal and compare it to the sessionID that spring-security sets in the browser. If I understand correctly they should match. They don't match. Am I misunderstanding the solution suggested in the answer?
Single sign on is a very difficult problem to get right. I really would not recommend attempting to implement it unless you have a good grasp of the problem and how to solve it. If you can I highly recommend you try to use Oauth2 instead of implementing it yourself.
https://www.baeldung.com/sso-spring-security-oauth2 might give you a starting point.
If you are using an application server like JBoss or WebSphere you may be able to use their SSO option instead.
I have a project where I have one application "a" with rest services and another one ("b") for a web page where the users can login and use 1 or 2 rest services of "a" (e.g. posting a comment).
The problem is, the rest services are all secured and shall authorize via httpBasic every time (no session).
The services needed for "b" on the other hand shall have a session.
so:
requests with the rest services directly -> httpBasic everytime(no session)
login via webpage/webpage handling requests -> session via login
Is there a way to achieve this? My only solution right now would be to delete the session after the httpBasic process, but I don't know how to do that.
httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.authorizeRequests().anyRequest().permitAll()
.and().httpBasic();
If you really wish to delete session data it can be achieved by implementing LogoutSuccessHandler interface.
package com.arjun.security;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Service;
import com.informage.arnav.domain.CassandraLoginSession;
#Service
public class AppLogoutSuccessHandler implements LogoutSuccessHandler {
#Autowired
private CassandraLoginSessionDao cassandraLoginSessionDao;
public AppLogoutSuccessHandler() {
super();
}
#Override
public void onLogoutSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
if (authentication != null) {
final Object principal = authentication.getPrincipal();
final AppUserDetails appUserDetails;
if (principal != null && principal instanceof AppUserDetails) {
appUserDetails = (AppUserDetails) principal;
CassandraLoginSession cassandraLoginSession=cassandraLoginSessionDao.findBySessionId(appUserDetails.getSessionId().toString());
//can also delete based on userId
//CassandraLoginSession cassandraLoginSession=cassandraLoginSessionDao.findByUserId(appUserDetails.getUserId());
cassandraLoginSessionDao.hardDelete(cassandraLoginSession);
//delete the session data from DB where session is stored
}
}
}
}
In your security configuration you need to configure this handler.
<logout logout-url="/logout" success-handler-ref="appLogoutSuccessHandler" />
You can write following to permit access to your webservice without login:
http.authorizeRequests()
.antMatchers("/webserviceNotRequiredAuthorization*").permitAll();
Then you must write following to which you webservices need authorization :
http.authorizeRequests().antMatchers("/webserviceRequiredAuthorization").hasAnyRole("ROLE_ADMIN");
in short first write the URL pattern for which authorization is not required and then write the URL pattern for which authorization is required.
http.authorizeRequests()
.antMatchers("/webserviceNotRequiredAuthorization*").permitAll();
http.authorizeRequests().antMatchers("/webserviceRequiredAuthorization").hasAnyRole("ROLE_ADMIN");
I have a JSP, Servlet, Hibernate application. In this application I have a very weird problem. That is, if the session got expired (in other words "time out") and when the user click on a link the page will be redirected to the index page, but after that the user is not able to log in and access the last link he clicked. I will describe it step by step in below.
User log into the application. Session get created.
He access the path /Passport
User is now idle, session get expired.
User come back and click on link to access /Visa . Since the session is now idle, user will be redirected to index page.
User log in.
Click on the link to access /Visa (from anywhere where the link is available)
. The link is an where it links to its path like
Visa?idEmployee=1
Now the problem. User is redirected back to index page.
I have Filter to monitor whether the session is null and whether the required session attributes are not null. If the request do not fulfill the mentioned 2 conditions, the request will be sent back to the index.
The filter code is below.
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package Filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
*
* #author user
*/
public class AuthenticationFilter_Level1 implements Filter
{
#Override
public void init(FilterConfig filterConfig) throws ServletException
{
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException
{
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
HttpSession session = request.getSession(false);
Integer attribute = null;
if(session!=null && session.getAttribute("idSubUser")!=null)
{
chain.doFilter(req, res);
}
else
{
//response.sendRedirect("index.html");
RequestDispatcher dispatch = request.getRequestDispatcher("index.html");
dispatch.forward(req, res);
}
}
#Override
public void destroy()
{
}
}
In web.xml, I have added the filter from servlet to servlet, like below.
<filter-mapping>
<filter-name>AuthenticationFilter_Level1</filter-name>
<url-pattern>/RegSrvlt</url-pattern>
<url-pattern>/AdminPopulateSrvlt</url-pattern>
<url-pattern>/AgentPopulate</url-pattern>
......
Filter session timeout is configured as below.
<session-config>
<session-timeout>
1
</session-timeout>
</session-config>
So, what is happening here?
Update
When the above error happens, the URL actually looks like http://localhost:8080/xxx/Visa?idEmployee=1 even though it is redirected!
UPDATE
I found this has no connection with the filter. Then what can make this?
else
{
if (session != null) {
session.invalidate();
}
...
And check the where sessions are created (i.e. public JSPs/servlets).
Another possible cause could be a problem of caching in browser :
client browser asks for /Visa
server sends index.html => browser caches it as it is a nice html page
...
on same browser session, user clicks on a /Visa links
without even asking anything to server, browser displays cached index.html page
How do confirm :
server side, by looking the server logs to see if the page was actually requested
client or server side by using a network spy such as wireshark and look if a request is sended
client side by emptying the cache and not the cookies
How to fix :
try to add a header asking not to cache the page before forwarding to index.html - it should be possible but I do not exactly know the proper header configuration
replace the forward to index.html with a redirection. That way the browser should not cache index.html for the /Visa URL
Problem:
We have a Spring MVC-based RESTful API which contains sensitive information. The API should be secured, however sending the user's credentials (user/pass combo) with each request is not desirable. Per REST guidelines (and internal business requirements), the server must remain stateless. The API will be consumed by another server in a mashup-style approach.
Requirements:
Client makes a request to .../authenticate (unprotected URL) with credentials; server returns a secure token which contains enough information for the server to validate future requests and remain stateless. This would likely consist of the same information as Spring Security's Remember-Me Token.
Client makes subsequent requests to various (protected) URLs, appending the previously obtained token as a query parameter (or, less desirably, an HTTP request header).
Client cannot be expected to store cookies.
Since we use Spring already, the solution should make use of Spring Security.
We've been banging our heads against the wall trying to make this work, so hopefully someone out there has already solved this problem.
Given the above scenario, how might you solve this particular need?
We managed to get this working exactly as described in the OP, and hopefully someone else can make use of the solution. Here's what we did:
Set up the security context like so:
<security:http realm="Protected API" use-expressions="true" auto-config="false" create-session="stateless" entry-point-ref="CustomAuthenticationEntryPoint">
<security:custom-filter ref="authenticationTokenProcessingFilter" position="FORM_LOGIN_FILTER" />
<security:intercept-url pattern="/authenticate" access="permitAll"/>
<security:intercept-url pattern="/**" access="isAuthenticated()" />
</security:http>
<bean id="CustomAuthenticationEntryPoint"
class="com.demo.api.support.spring.CustomAuthenticationEntryPoint" />
<bean id="authenticationTokenProcessingFilter"
class="com.demo.api.support.spring.AuthenticationTokenProcessingFilter" >
<constructor-arg ref="authenticationManager" />
</bean>
As you can see, we've created a custom AuthenticationEntryPoint, which basically just returns a 401 Unauthorized if the request wasn't authenticated in the filter chain by our AuthenticationTokenProcessingFilter.
CustomAuthenticationEntryPoint:
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.sendError( HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized: Authentication token was either missing or invalid." );
}
}
AuthenticationTokenProcessingFilter:
public class AuthenticationTokenProcessingFilter extends GenericFilterBean {
#Autowired UserService userService;
#Autowired TokenUtils tokenUtils;
AuthenticationManager authManager;
public AuthenticationTokenProcessingFilter(AuthenticationManager authManager) {
this.authManager = authManager;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
#SuppressWarnings("unchecked")
Map<String, String[]> parms = request.getParameterMap();
if(parms.containsKey("token")) {
String token = parms.get("token")[0]; // grab the first "token" parameter
// validate the token
if (tokenUtils.validate(token)) {
// determine the user based on the (already validated) token
UserDetails userDetails = tokenUtils.getUserFromToken(token);
// build an Authentication object with the user's info
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails((HttpServletRequest) request));
// set the authentication into the SecurityContext
SecurityContextHolder.getContext().setAuthentication(authManager.authenticate(authentication));
}
}
// continue thru the filter chain
chain.doFilter(request, response);
}
}
Obviously, TokenUtils contains some privy (and very case-specific) code and can't be readily shared. Here's its interface:
public interface TokenUtils {
String getToken(UserDetails userDetails);
String getToken(UserDetails userDetails, Long expiration);
boolean validate(String token);
UserDetails getUserFromToken(String token);
}
That ought to get you off to a good start.
You might consider Digest Access Authentication. Essentially the protocol is as follows:
Request is made from client
Server responds with a unique nonce string
Client supplies a username and password (and some other values) md5 hashed with the nonce; this hash is known as HA1
Server is then able to verify client's identity and serve up the requested materials
Communication with the nonce can continue until the server supplies a new nonce (a counter is used to eliminate replay attacks)
All of this communication is made through headers, which, as jmort253 points out, is generally more secure than communicating sensitive material in the url parameters.
Digest Access Authentication is supported by Spring Security. Notice that, although the docs say that you must have access to your client's plain-text password, you can successfully authenticate if you have the HA1 hash for your client.
Regarding tokens carrying information, JSON Web Tokens (http://jwt.io) is a brilliant technology. The main concept is to embed information elements (claims) into the token, and then signing the whole token so that the validating end can verify that the claims are indeed trustworthy.
I use this Java implementation: https://bitbucket.org/b_c/jose4j/wiki/Home
There is also a Spring module (spring-security-jwt), but I haven't looked into what it supports.
Why don't you start using OAuth with JSON WebTokens
http://projects.spring.io/spring-security-oauth/
OAuth2 is an standardized authorization protocol/framework. As per Official OAuth2 Specification:
You can find more info here