Is there any configuration provided by Spring OAuth2 that does the creation of a cookie with the opaque or JWT token?
The configuration that I've found on the Internet so far describes the creation of an Authorization Server and a client for it. In my case the client is a gateway with an Angular 4 application sitting on top of it in the same deployable. The frontend makes requests to the gateway that routes them through Zuul.
Configuring the client using @EnableOAuth2Sso
, an application.yml and a WebSecurityConfigurerAdapter makes all the necessary requests and redirects, adds the information to the SecurityContext but stores the information in a session, sending back a JSESSIONID cookie to the UI.
Is there any configuration or filter needed to create a cookie with the token information and then use a stateless session that I can use? Or do I have to create it myself and then create a filter that looks for the token?
@SpringBootApplication
@EnableOAuth2Sso
@RestController
public class ClientApplication extends WebSecurityConfigurerAdapter{
@RequestMapping("/user")
public String home(Principal user) {
return "Hello " + user.getName();
}
public static void main(String[] args) {
new SpringApplicationBuilder(ClientApplication.class).run(args);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/**").authorizeRequests()
.antMatchers("/", "/login**", "/webjars/**").permitAll()
.anyRequest()
.authenticated()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
server:
port: 9999
context-path: /client
security:
oauth2:
client:
clientId: acme
clientSecret: acmesecret
accessTokenUri: http://localhost:9080/uaa/oauth/token
userAuthorizationUri: http://localhost:9080/uaa/oauth/authorize
tokenName: access_token
authenticationScheme: query
clientAuthenticationScheme: form
resource:
userInfoUri: http://localhost:9080/uaa/me
Browser in-memory scenarios. Auth0 recommends storing tokens in browser memory as the most secure option. Using Web Workers to handle the transmission and storage of tokens is the best way to protect the tokens, as Web Workers run in a separate global scope than the rest of the application.
Sessions allow a user's authentication to be tracked between multiple HTTP requests to a service. The OAuth2 Proxy uses a Cookie to track user sessions and will store the session data in one of the available session storage backends.
Make sure you have imported these classes present in javax.servlet:
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
Initialize cookie like this:
Cookie jwtCookie = new Cookie(APP_COOKIE_TOKEN, token.getToken());
jwtCookie.setPath("/");
jwtCookie.setMaxAge(20*60);
//Cookie cannot be accessed via JavaScript
jwtCookie.setHttpOnly(true);
Add cookie in HttpServletResponse:
response.addCookie(jwtCookie);
If you are using angular 4 and spring security+boot , then this github repo can become a big help:
Reference blog for this repo is:
I ended up solving the problem by creating a filter that creates the cookie with the token and adding two configurations for Spring Security, one for when the cookie is in the request and one for when it isn't. I kind of think this is too much work for something that should be relatively simple so I'm probably missing something in how the whole thing is supposed to work.
public class TokenCookieCreationFilter extends OncePerRequestFilter {
public static final String ACCESS_TOKEN_COOKIE_NAME = "token";
private final UserInfoRestTemplateFactory userInfoRestTemplateFactory;
@Override
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain) throws ServletException, IOException {
try {
final OAuth2ClientContext oAuth2ClientContext = userInfoRestTemplateFactory.getUserInfoRestTemplate().getOAuth2ClientContext();
final OAuth2AccessToken authentication = oAuth2ClientContext.getAccessToken();
if (authentication != null && authentication.getExpiresIn() > 0) {
log.debug("Authentication is not expired: expiresIn={}", authentication.getExpiresIn());
final Cookie cookieToken = createCookie(authentication.getValue(), authentication.getExpiresIn());
response.addCookie(cookieToken);
log.debug("Cookied added: name={}", cookieToken.getName());
}
} catch (final Exception e) {
log.error("Error while extracting token for cookie creation", e);
}
filterChain.doFilter(request, response);
}
private Cookie createCookie(final String content, final int expirationTimeSeconds) {
final Cookie cookie = new Cookie(ACCESS_TOKEN_COOKIE_NAME, content);
cookie.setMaxAge(expirationTimeSeconds);
cookie.setHttpOnly(true);
cookie.setPath("/");
return cookie;
}
}
/**
* Adds the authentication information to the SecurityContext. Needed to allow access to restricted paths after a
* successful authentication redirects back to the application. Without it, the filter
* {@link org.springframework.security.web.authentication.AnonymousAuthenticationFilter} cannot find a user
* and rejects access, redirecting to the login page again.
*/
public class SecurityContextRestorerFilter extends OncePerRequestFilter {
private final UserInfoRestTemplateFactory userInfoRestTemplateFactory;
private final ResourceServerTokenServices userInfoTokenServices;
@Override
public void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) throws IOException, ServletException {
try {
final OAuth2AccessToken authentication = userInfoRestTemplateFactory.getUserInfoRestTemplate().getOAuth2ClientContext().getAccessToken();
if (authentication != null && authentication.getExpiresIn() > 0) {
OAuth2Authentication oAuth2Authentication = userInfoTokenServices.loadAuthentication(authentication.getValue());
SecurityContextHolder.getContext().setAuthentication(oAuth2Authentication);
log.debug("Added token authentication to security context");
} else {
log.debug("Authentication not found.");
}
chain.doFilter(request, response);
} finally {
SecurityContextHolder.clearContext();
}
}
}
This is the configuration for when the cookie is in the request.
@RequiredArgsConstructor
@EnableOAuth2Sso
@Configuration
public static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserInfoRestTemplateFactory userInfoRestTemplateFactory;
private final ResourceServerTokenServices userInfoTokenServices;
/**
* Filters are created directly here instead of creating them as Spring beans to avoid them being added as filters * by ResourceServerConfiguration security configuration. This way, they are only executed when the api gateway * behaves as a SSO client.
*/
@Override
protected void configure(final HttpSecurity http) throws Exception {
http
.requestMatcher(withoutCookieToken())
.authorizeRequests()
.antMatchers("/login**", "/oauth/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().requireCsrfProtectionMatcher(csrfRequestMatcher()).csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(new TokenCookieCreationFilter(userInfoRestTemplateFactory), AbstractPreAuthenticatedProcessingFilter.class)
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)
.addFilterBefore(new SecurityContextRestorerFilter(userInfoRestTemplateFactory, userInfoTokenServices), AnonymousAuthenticationFilter.class);
}
private RequestMatcher withoutCookieToken() {
return request -> request.getCookies() == null || Arrays.stream(request.getCookies()).noneMatch(cookie -> cookie.getName().equals(ACCESS_TOKEN_COOKIE_NAME));
}
And this is the configuration when there is a cookie with the token. There is a cookie extractor that extends the BearerTokenExtractor
functionality from Spring
to search for the token in the cookie and an authentication entry point that expires the cookie when the authentication fails.
@EnableResourceServer
@Configuration
public static class ResourceSecurityServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(final ResourceServerSecurityConfigurer resources) {
resources.tokenExtractor(new BearerCookiesTokenExtractor());
resources.authenticationEntryPoint(new InvalidTokenEntryPoint());
}
@Override
public void configure(final HttpSecurity http) throws Exception {
http.requestMatcher(withCookieToken())
.authorizeRequests()
.... security config
.and()
.exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/"))
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.logout().logoutSuccessUrl("/your-logging-out-endpoint").permitAll();
}
private RequestMatcher withCookieToken() {
return request -> request.getCookies() != null && Arrays.stream(request.getCookies()).anyMatch(cookie -> cookie.getName().equals(ACCESS_TOKEN_COOKIE_NAME));
}
}
/**
* {@link TokenExtractor} created to check whether there is a token stored in a cookie if there wasn't any in a header
* or a parameter. In that case, it returns a {@link PreAuthenticatedAuthenticationToken} containing its value.
*/
@Slf4j
public class BearerCookiesTokenExtractor implements TokenExtractor {
private final BearerTokenExtractor tokenExtractor = new BearerTokenExtractor();
@Override
public Authentication extract(final HttpServletRequest request) {
Authentication authentication = tokenExtractor.extract(request);
if (authentication == null) {
authentication = Arrays.stream(request.getCookies())
.filter(isValidTokenCookie())
.findFirst()
.map(cookie -> new PreAuthenticatedAuthenticationToken(cookie.getValue(), EMPTY))
.orElseGet(null);
}
return authentication;
}
private Predicate<Cookie> isValidTokenCookie() {
return cookie -> cookie.getName().equals(ACCESS_TOKEN_COOKIE_NAME);
}
}
/**
* Custom entry point used by {@link org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter}
* to remove the current cookie with the access token, redirect the browser to the home page and invalidate the
* OAuth2 session. Related to the session, it is invalidated to destroy the {@link org.springframework.security.oauth2.client.DefaultOAuth2ClientContext}
* that keeps the token in session for when the gateway behaves as an OAuth2 client.
* For further details, {@link org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2RestOperationsConfiguration.SessionScopedConfiguration.ClientContextConfiguration}
*/
@Slf4j
public class InvalidTokenEntryPoint implements AuthenticationEntryPoint {
public static final String CONTEXT_PATH = "/";
@Override
public void commence(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException authException) throws IOException, ServletException {
log.info("Invalid token used. Destroying cookie and session and redirecting to home page");
request.getSession().invalidate(); //Destroys the DefaultOAuth2ClientContext that keeps the invalid token
response.addCookie(createEmptyCookie());
response.sendRedirect(CONTEXT_PATH);
}
private Cookie createEmptyCookie() {
final Cookie cookie = new Cookie(TokenCookieCreationFilter.ACCESS_TOKEN_COOKIE_NAME, EMPTY);
cookie.setMaxAge(0);
cookie.setHttpOnly(true);
cookie.setPath(CONTEXT_PATH);
return cookie;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With