is there any way to "reset" the csrf token after successful login? - spring

I just enabled the csrf token protection on my spring security (with xml) by adding the csrf tag.
It works great but I noticed when I hit the login form I am sending a csrf token, after a successful login the csrf token is still the same, is there anyway I can reset it after hit the login form?
After checking the docs I found no clue how to do this or if is due my repository? (Im not using any custom csrf repository)
Ideas?

The official documentation states that the CSRF token is stored in the HttpSession by default. Since the token is stored in the session, it will continue to get reused after the user has logged in.
If you really want to reset the token on a successful login, you will have to handle successful login events.
public class AuthenticationSuccessCsrfTokenResetHandler
extends SimpleUrlAuthenticationSuccessHandler {
private final CsrfTokenRepository repository;
public AuthenticationSuccessCsrfTokenResetHandler(final CsrfTokenRepository repository) {
this.repository = repository;
}
#Override
public void onAuthenticationSuccess(final HttpServletRequest request
, final HttpServletResponse response
, final Authentication authentication)
throws ServletException, IOException {
repository.saveToken(repository.generateToken(request), request, response)
}
}
Then, configure this handler in your Spring Security configuration.

Related

Spring-security - httponlycookie into existing jwt intergration?

I have been told it is insecure to just use JWT without HttpOnly cookie when using a seperate frontend-service.
As suggested here:
http://cryto.net/~joepie91/blog/2016/06/19/stop-using-jwt-for-sessions-part-2-why-your-solution-doesnt-work/
HttpOnly Cookie: https://www.ictshore.com/ict-basics/httponly-cookie/
I currently have a working JWT system so i'm trying to upgrade this to support the cookie implementation.
I firstly changed my SecurityConfiguration to the following:
private final UserDetailsService uds;
private final PasswordEncoder bcpe;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(uds).passwordEncoder(bcpe);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable();
http.addFilter(new CustomAuthenticationFilter(authenticationManagerBean()));
http.addFilterBefore(new CustomAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().logout().deleteCookies(CustomAuthorizationFilter.COOKIE_NAME)
.and().authorizeRequests().antMatchers("/login/**", "/User/refreshToken", "/User/add").permitAll()
.and().authorizeRequests().antMatchers(GET, "/**").hasAnyAuthority("STUDENT")
.anyRequest().authenticated();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception{ // NO FUCKING IDEA WHAT THIS DOES
return super.authenticationManagerBean();
}
From here I am trying to insert the actual cookie implementation into my CustomAuthorizationFilter:
public class CustomAuthorizationFilter extends OncePerRequestFilter { // INTERCEPTS EVERY REQUEST
public static final String COOKIE_NAME = "auth_by_cookie";
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if(request.getServletPath().equals("/login") || request.getServletPath().equals("/User/refreshToken/**")){ // DO NOTHING IF LOGGING IN OR REFRESHING TOKEN
filterChain.doFilter(request,response);
}
else{
String authorizationHeader = request.getHeader(AUTHORIZATION);
if(authorizationHeader != null && authorizationHeader.startsWith("Bearer ")){
try {
String token = authorizationHeader.substring("Bearer ".length());
//NEEDS SECURE AND ENCRYPTED vvvvvvv
Algorithm algorithm = Algorithm.HMAC256("secret".getBytes());
JWTVerifier verifier = JWT.require(algorithm).build(); // USING AUTH0
DecodedJWT decodedJWT = verifier.verify(token);
String email = decodedJWT.getSubject(); // GETS EMAIL
String[] roles = decodedJWT.getClaim("roles").asArray(String.class);
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
stream(roles).forEach(role -> { authorities.add(new SimpleGrantedAuthority(role)); });
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(email, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authToken);
filterChain.doFilter(request, response);
}
catch (Exception e){
response.setHeader("error" , e.getMessage() );
response.setStatus(FORBIDDEN.value());
Map<String, String> error = new HashMap<>();
error.put("error_message", e.getMessage());
response.setContentType(APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getOutputStream(), error);
}
}
else{ filterChain.doFilter(request, response); }
}
}
}
What I don't know is where to insert the cookie reading & where to wrap it. Does it wrap around the JWT?
I did see this implementation:
public class CookieAuthenticationFilter extends OncePerRequestFilter {
public static final String COOKIE_NAME = "auth_by_cookie";
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
FilterChain filterChain) throws ServletException, IOException {
Optional<Cookie> cookieAuth = Stream.of(Optional.ofNullable(httpServletRequest.getCookies()).orElse(new Cookie[0]))
.filter(cookie -> COOKIE_NAME.equals(cookie.getName()))
.findFirst();
if (cookieAuth.isPresent()) {
SecurityContextHolder.getContext().setAuthentication(
new PreAuthenticatedAuthenticationToken(cookieAuth.get().getValue(), null));
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
Though this mentions its the "authenticationFilter", I do have an authentication filter though it is less comparable to this CookieAuthenticationFilter than CustomAuthorizationFilter:
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authManager;
public CustomAuthenticationFilter authManagerFilter;
private UserService userService;
#Override // THIS OVERRIDES THE DEFAULT SPRING SECURITY IMPLEMENTATION
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String email = request.getParameter("email");
String password = request.getParameter("password");
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(email, password);
return authManager.authenticate(authToken);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
// SPRING SECURITY BUILT IN USER
User springUserDetails = (User) authentication.getPrincipal();
// NEEDS SECURE AND ENCRYPTED vvvvvvv
Algorithm algorithm = Algorithm.HMAC256("secret".getBytes()); // THIS IS USING AUTH0 DEPENDENCY
String access_token = JWT.create()
.withSubject(springUserDetails.getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + 120 * 60 * 1000)) // this should be 2 hours
.withIssuer(request.getRequestURI().toString())
.withClaim("roles", springUserDetails.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()))
.sign(algorithm);
String refresh_token = JWT.create()
.withSubject(springUserDetails.getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + 120 * 60 * 1000)) // this should be 2 hours
.withIssuer(request.getRequestURI().toString())
.withClaim("roles", springUserDetails.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()))
.sign(algorithm);
Map<String, String> tokens = new HashMap<>();
tokens.put("access_token", access_token);
tokens.put("refresh_token", refresh_token);
response.setContentType(APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getOutputStream(), tokens);
}
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
...
new ObjectMapper().writeValue(response.getOutputStream(), error);
}
}
}
Any suggestions are welcome!
By looking at all your custom code I would strongly recommend that you actually read the spring security documentation of the different authentication types that are available and look up the advantages and disadvantages.
And understand that there are security standards for how logins should be built and what you have built is insecure, non-scalable custom made which is very bad practice.
but here is a short recap:
FormLogin
The user authenticates themselves presenting a username and a password. In return, they will get a session cookie that contains a random string but is mapped to a key-value store on the server side.
The cookie is set to httpOnly and httpSecure which means it's harder to steal them and it's not vulnerable to XSS in the browser.
I just want to emphasize the cookie contains a random string, so if you want user information you either return the cookie after login and userinfo in the body or you do an additional call to a user endpoint and fetch user information.
The downside is that this solution does not scale if you want 5 backend servers you need something like Spring Session and set up a store, that stores the session so that it is shared between the backend servers.
Upside, we can just server-side invalidate the cookie whenever we want. We have full control.
oauth2
Well, this is the one most people know about, you want to login, and you are redirected, to an issuer (a different server). You authenticate with that server, the server gives you a temporary token that you can exchange for an opague token.
What is in opague token, well it's just a random text string that the issuer keeps track of.
Now when you want to call your backend you setup your backend as a resource server, that you present the token for in a header. The resource server extracts the token from the header, asks the issuer if the token is valid, and it answers yes or no.
Here you can revoke tokens, by going to the issuer and saying "this token is not valid anymore" and next time the token is presented it will check with the issuer that it is blocked and we are fine.
oauth2 + JWT
Like above but instead of having an opague token, we instead send a JWT to the client. So that, when the JWT has presented the resource server, does not have to ask the issuer if the token is valid. We can instead check the signature using a JWK. With this approach, we have one less call to the issuer to check the validity of the token.
JWT is just a format of a token. Opague token = random string, JWT = signed data in the format of JSON and used as a token.
JWTs were never meant to replace cookies, people just started using them instead of cookies.
But what we loose is the ability to know to revoke tokens. As we don't keep track of the JWTs in the issuer and we don't ask the issuer on each call.
We can reduce the risk here by having tokens that are short-lived. Maybe 5 minutes. But remember ITS STILL A RISK, for 5 mins malicious actors can do damage.
Your solution
If we look at your custom solution, which many people on the internet are building which has many many flaws is that you have built a FormLogin solution that gives out JWTs and hence comes with all the problems of JWTs.
So your token can be stolen in the browser as it does not have the security that comes with cookies. We have no ability to revoke tokens if it gets stolen. It is not scalable and it is custom written which means one bug and the entire application's data is compromised.
So basically all the bad things from the solutions above are combined here into one super bad solution.
My suggestion
You remove all your custom code and look at what type of application you have.
If it is a single server small application, use FormLogin, don't use JWTs at all. Cookies have worked for 20 years and they are still fine. Don't use JWTs just because want to use JWTs.
If you are writing a larger application, use a dedicated authorization server like okta, curity, spring authorization server, keycloak.
Then setup your servers to be resource servers using the built-in resource server functionality that comes with spring security and is documented in the JWT chapter in the docs.
JWTs were from the beginning never meant to be exposed to clients, since you can read everything in them they were meant to be used between servers to minimize calls to issuers because the data is signed so each server could check the signature by themselves.
Then the entire javascript community and lazy developers started writing custom insecure solutions to give out JWTs to the client.
Now everyone just googles a tutorial of spring security with JWT and builds something custom and insecure and then ask on stack overflow when "someone has pointed out that their solution is insecure".
If you are serious about building a secure login read the following:
Spring security official documentation, chapters formlogin, oauth2, JWT
The oauth2 specification
The JWT specification
Curity has good documentation about oauth2 https://curity.io/resources/learn/code-flow/
FormLogin spring
https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/form.html
oauth2 spring
https://docs.spring.io/spring-security/reference/servlet/oauth2/index.html
configure your application to handle JWTs
https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/jwt.html
Some pointers about your code
this is completely unneeded. You have overridden a function and then you are calling the default implementation, also think about your languages in your comments.
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception{ // NO FUCKING IDEA WHAT THIS DOES
return super.authenticationManagerBean();
}
Also, this entire class can be removed
public class CustomAuthorizationFilter extends OncePerRequestFilter
If you want to handle JWTs and make your server a resource server all you do is as the documentation states https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/jwt.html#oauth2resourceserver-jwt-sansboot
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
// This line sets up your server to use the built in filter
// and accept JWT tokens in headers
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
return http.build();
}
you can then setup a JWTDecoder using the built-in Nimbuslibrary that comes with spring security
#Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withJwkSetUri(this.jwkSetUri)
.jwsAlgorithm(RS512).jwsAlgorithm(ES512).build();
}
Since it's a Bean it will automatically get injected, so we don't have to manually set anything.
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
Here you have stated that you want the server to be stateless which means you have disabled cookies as cookies are what the server uses to retain the state from the clients. And then you are trying to implement a custom cookie filter.
Once again, you have to decide, are you going to use FormLogin with cookies, or oauth2 + JWT because now you are doing a mish-mash between.
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(uds).passwordEncoder(bcpe);
}
Is most likely not needed as I assume that both uds and bcpe are beans, components, etc., and will automatically get injected. No need to make something a bean THEN manually set it. You make something a bean so you DON'T have to set it manually. But you are doing both.

Cookie Authentication instead of JWT Bearer Token after a successful Oauth2 Login in Spring Boot

I'm using callicoder's spring-boot-react-oauth2-social-login-demo
sample to implement a rest api using Oauth2 client. Sample works without a problem.
However after a successful Oauth2 authentication, I want to issue a cookie instead of JWT Token to secure access to my controllers. In order to do this, I added the lines below determineTargetUrl on OAuth2AuthenticationSuccessHandler. This sets a cookie containing JWT token created by TokenProvider.
CookieUtils.addCookie(response, appProperties.getAuth().getAuthenticationCookieName(), token, (int) appProperties.getAuth().getTokenExpirationMsec());
And then I created a CookieAuthenticationFilter similar to TokenAuthenticationFilter which checks the cookie set by OAuth2AuthenticationSuccessHandler.
public class CookieAuthenticationFilter extends OncePerRequestFilter {
#Autowired
private AppProperties appProperties;
#Autowired
private TokenProvider tokenProvider;
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
Optional<String> jwt = CookieUtils.getCookie(request, appProperties.getAuth().getAuthenticationCookieName()).map(Cookie::getValue);
if (StringUtils.hasText(String.valueOf(jwt)) && tokenProvider.validateToken(String.valueOf(jwt))) {
Long userId = tokenProvider.getUserIdFromToken(String.valueOf(jwt));
UserDetails userDetails = customUserDetailsService.loadUserById(userId);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception ex) {
logger.error("Could not set user authentication in security context", ex);
}
filterChain.doFilter(request, response);
}
}
and on SecurityConfig I replaced tokenAuthenticationFilter bean to cookieAuthenticationFilter
http.addFilterBefore(cookieAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
When I run the project, Oauth2 authentication is made successfully and cookie is set. However when I request a secured controller method, CookieAuthenticationFilter.doFilterInternal is not hit and request directly goes to RestAuthenticationEntryPoint.commence and exception is thrown with message Full authentication is required to access this resource .
Do I have to change any more configuration to change authentication to cookie from Bearer (JWT)?
The problem was a result of a missing exception without catch. The sample and the code works as expected.

Convert SAML 2.0 to JWT while redirecting from /saml/sso

I have a requirement to create a service provider form ADFS IDP. IDP is sending a SAML 2.0 token and in service side I am receiving it.
I have used spring security same extension plugin in service provider.
My code’s flow is mentioned below
/saml/login ——> will make a call to ADFS(IDP)———>redirect to saml/sso (with SAML token)
Now from this same/sso redirection to Front end (client will happen, which requested the token). I want to send back JWT instead of SAML to send back to browser.
What will be the best way to do it. How can I make /saml/sso to covert SAML to JWT in successRedirectHandler.
Sample handler
#Bean
public SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() {
SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler =
new SavedRequestAwareAuthenticationSuccessHandler();
successRedirectHandler.setDefaultTargetUrl("/landing");
return successRedirectHandler;
}
Please note that I am using Nimbus JSON JWT jar for SAML to JWT conversion. I would prefer not to create a separate controller to convert SAML to JWT. Any help and pointers will be helpful.
Basically, after the authentication on the IDP side, you'll receive the assertions in response. You'll extract the attributes and perform some validation on the service side. You'll create JWT token with desired attributes. After that, you can redirect to target url with the token. Below is a snippet of code.
public class SAMLLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
public SAMLLoginSuccessHandler() {}
#Override
public void onAuthenticationSuccess(final HttpServletRequest request,
final HttpServletResponse response, final Authentication authentication)
throws IOException, ServletException {
if (authentication.getPrincipal() instanceof UserDetails
|| authentication.getDetails() instanceof UserDetails) {
UserDetails details;
String failureRedirectUrl = Constants.REDIRECTION_WEB;
if (authentication.getPrincipal() instanceof UserDetails) {
details = (UserDetails) authentication.getPrincipal();
} else {
details = (UserDetails) authentication.getDetails();
}
String username = details.getUsername();
SAMLCredential credential = (SAMLCredential) authentication.getCredentials();
List<Attribute> attributes = credential.getAttributes();
// validate user related information coming in assertions from IDP
// TODO:JWT Token generation code
// eventually you want to send that code to client therefore append the token
// in the url to which you want to redirect
String redirectUri; // set the redirect uri
response.sendRedirect(redirectUri);
}
super.onAuthenticationSuccess(request, response, authentication);
}
}

Spring Boot - JWT authentication without db calls

Is it possible to implement simple JWT authentication (not caring about invalidating tokens - I'll do it in cache) without database calls to load user into Security Context? I see my current implementation hits database with every call to api (to load user into security context). Below you can see part of implementation of JwtAuthenticationFilter extending OncePerRequestFilter:
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
String jwt = getJwtFromRequest(request);
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
Long userId = tokenProvider.getUserIdFromJWT(jwt);
UserDetails userDetails = customUserDetailsService.loadUserById(userId);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception ex) {
logger.error("Could not set user authentication in security context", ex);
}
filterChain.doFilter(request, response);
}
And here is the call to database, which I would like to avoid (with every authenticated call to api):
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
UserRepository userRepository;
// This method is used by JWTAuthenticationFilter
#Transactional
public UserDetails loadUserById(Long id) {
User user = userRepository.findById(id).orElseThrow(
() -> new UsernameNotFoundException("User not found with id : " + id)
);
return UserPrincipal.create(user);
}
}
I found some kind of solution of problem to build UserPrincipal object (it implements UserDetails interface) with only user id, username and granted authorities, and without e.g. password, which I cannot read from JWT token itself (the rest I can), but I am not sure if it's secure and and considered as a good-practice solution (UserDetails class requires password field and storing it JWT would not be wise I think). I need UserPrincipal instance (implementing UserDetails interface) to support as argument to UsernamePasswordAuthenticationToken, as you can see in the first paragraph.
One approach can be having two filter as follows.
CustomAuthenticationFilter that serves only login request/endpoint. You can do the following in this filter,
Call the db and validate the credential and retrieve the roles of the user
Generate the JWT token and you can store the user id or email along with roles in the subject of the JWT token. As we are adding user specific details I would recommend to encrypt the JWT token.
CustomAuthroizationFilter that serves all other requests/endpoints. You can do the following in this filter,
Validate JWT token
Retrieve the user id or email along with roles of the user from the subject of the JWT token.
Build spring authentication (UsernamePasswordAuthenticationToken) and set it in SecurityContextHolder like you did.
This way you will be calling db only during the login request not for all other api endpoints.

Why to invalidate a session in a security filter, CSRF token is not updated

In a Spring Security application, a user is making a GET call to a private /secure url with activated csrf.
The user sends a credential cookie and then the user is authenticated and JSESSIONID is generated.
In the same browser becomes to make a GET call to /secure, with the JSESSIONID cookie and a credential cookie by another user.
In this case an owner security filter produces a change of user, invalid session of the first user, generate a
new session and authenticates the new credential.
This works until the new authenticated user sends a POST with the new JSESSIONID, but the CSRF token is getting to the old.
CsrfFilter is before my authentication filter, and CsrfAuthenticationStrategy not regenerate the CSRF token.
What is the right way to invalidate a session and the token csrf be updated?
The invalidation of session in spring security is automatic once you relogin as new user using the old JSESSION ID.
Basically you just have to bind the token generated on the new session generated by the authentication filter.
Try this solution, another filter after authentication
https://github.com/aditzel/spring-security-csrf-filter/blob/master/src/main/java/com/allanditzel/springframework/security/web/csrf/CsrfTokenResponseHeaderBindingFilter.java
Same issue reported via this thread,
https://github.com/aditzel/spring-security-csrf-token-interceptor/issues/1
I created a HttpSessionCsrfTokenRepository bean type, which is the default implementation of CsrfTokenRepository, and use both to configure spring security repository csrf as my filter, so the two sites use the same bean.
Config Bean
#Bean
public void CsrfTokenRepository dafaultCsrfTokenRepository() {
return new HttpSessionCsrfTokenRepository();
}
Spring Security
#Autowired
private CsrfTokenRepository csrfTokenRepository;
private static HttpSecurity addCSRF(HttpSecurity http, CsrfTokenRepository csrfTokenRepository) throws Exception {
http.csrf().csrfTokenRepository(csrfTokenRepository);
return http;
}
Invalidate session in filter
private void invalidateCurrentSession(HttpServletRequest request, HttpServletResponse response) {
SecurityContextHolder.clearContext();
HttpSession session = request.getSession(false);
if (session != null) {
CsrfToken csrfToken=csrfTokenRepository.loadToken(request);
session.invalidate();
request.getSession();
csrfTokenRepository.saveToken(csrfToken, request, response);
}
}
If someone wanted to override the default behavior, you could define a bean that implements CsrfTokenRepository as #Primary

Resources