Unable to use permitAll() with Spring Boot 2.3.4 to allow access to Swagger UI after integrating with API-Key Authentication - spring-boot

I tried integrating API-Key authentication mechanism to Spring Boot Application in the following way:
Created a CustomAPIKeyAuthFilter that extends AbstractPreAuthenticatedProcessingFilter where it gets the preauthenticated principal from the headers of the request.
public class CustomAPIKeyAuthFilter extends AbstractPreAuthenticatedProcessingFilter {
private String principalRequestHeader;
private String principalAuthKey;
public CustomAPIKeyAuthFilter(String principalRequestHeader, String principalAuthKey) {
this.principalRequestHeader = principalRequestHeader;
this.principalAuthKey = principalAuthKey;
}
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
return request.getHeader(principalRequestHeader);
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
// anything to be returned here??
return "TBD";
}
}
Created WebSecurityConfig that extends WebSecurityConfigurerAdapter. In this one, the custom filter is injected inside the overridden method protected void configure(HttpSecurity httpSecurity) {}
#EnableWebSecurity
#Order(1)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${superuser}")
private String principalRequestHeader;
#Value("${superuserauthkey}")
private String principalRequestValue;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
CustomAPIKeyAuthFilter filter = new CustomAPIKeyAuthFilter(principalRequestHeader, principalRequestValue);
filter.setAuthenticationManager(new AuthenticationManager() {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String principal = (String) authentication.getPrincipal();
if (principalRequestValue.equals(principal)){
authentication.setAuthenticated(true);
} else {
throw new BadCredentialsException("Missing API Key");
}
return authentication;
}
});
httpSecurity.
cors().and().
csrf().disable().authorizeRequests()
.antMatchers("**swagger**").permitAll() // this is the part that is not working for me
.anyRequest().authenticated()
.and()
.addFilter(filter)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
As you can see from the comment above, even though I used permitAll, I get the error 401 No pre-authenticated principal found in request at runtime if I try to access Swagger UI which was working before introducing spring-boot-starter-security related dependencies in my pom.xml. Is there a better way to exclude swagger UI alone from the list of URL end points that need API-key based authentication ?
Note: I am using springfox-swagger2 implementation of Swagger and the version used is 2.8.0.

Swagger have api endpoint which should be allowed in security level, add the below snippet in WebSecurityConfig.class
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/v2/api-docs",
"/configuration/ui",
"/swagger-resources/**",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**");
}
You could also try permitAll() to the patterns included.This will exclude the swagger from being authenticated.

Related

Authorize using acess token

I have secured my application using oauth2 standard flow with amazon cognito. It works fine.
Sometimes i want to link to my webapp through my mobile app, and i dont want the user to sign in again. How do i create an #AnonymousAllowed endpoint that takes in an access token, authenticates using spring security and redirects to the homepage of my application?
I've tried creating an endpoint that returns the jsessionid, then another anonymous endpoint that accepts a jsessionid, sets the cookie and redirects to the homepage, but it didnt work.
I've tried setting the authorization header to the access token. But i feel like that wont work properly because the token will expire after few minutes, and spring wont handle authorization for me, rather i have to do it "manually"
I don't know if this can help in your situation. But you can check if anything you can refer from below. It's a custom filter with custom token validator.
You can use Custom TokenAuthenticationFilter by extending AbstractAuthenticationProcessingFilter
public class CustomTokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
#Autowired
private TokenValidatorService tokenValidatorService;
public CustomTokenAuthenticationFilter(final RequestMatcher requiresAuth) {
super(requiresAuth);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
String token= httpServletRequest.getHeader(CommonConstants.OAUTH_HEADER_KEY_AUTHORIZATION);
//Get the custom header
//Validate token using custom validator based on header value
Optional<OAuthResponseData> oauthResponseData = tokenValidatorService.validateAccessToken(token, provider);
...
Authentication requestAuthentication = new UsernamePasswordAuthenticationToken(oauthResponseData.get(), oauthResponseData.get().getOauthToken());
return getAuthenticationManager().authenticate(requestAuthentication);
}
#Override
protected void successfulAuthentication(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain, final Authentication authResult) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);
chain.doFilter(request, response);
}
}
In TokenValidatorService you can implement OAuth2TokenValidator to validate the token
Spring Security config class:
public class SecurityConfig extends GlobalAuthenticationConfigurerAdapter {
#Configuration
public static class CustomTokenSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
protected CustomTokenAuthenticationProvider customTokenAuthenticationProvider;
#Override
public void configure(final WebSecurity webSecurity) {
webSecurity.ignoring()
...
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
http .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.and()
.addFilterBefore(tokenAuthenticationFilter(), AnonymousAuthenticationFilter.class)
.authorizeRequests()
.requestMatchers(PROTECTED_URLS).authenticated()
.and()
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable();
}
#Bean
public CustomTokenAuthenticationFilter tokenAuthenticationFilter() throws Exception {
final CustomTokenAuthenticationFilter filter = new CustomTokenAuthenticationFilter(PROTECTED_URLS);
filter.setAuthenticationManager(authenticationManager());
// filter.setAuthenticationSuccessHandler(successHandler());
return filter;
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customTokenAuthenticationProvider);
}
}
}

How to configure two security configs with two filters in spring boot correctly?

I've implmemented security in my spring boot microservices project, the requirment is to have
two types of configurations, one for user request (from angular) and one from other services.
The design is to use JWT token for user request and API key for system calls.
Here is the config file (one file) but have also try to split it to two files with no impact:
#Configuration
#EnableWebSecurity
public class SecurityConfig {
#Configuration
#Order(1)
public static class APISecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${my.api.key.header}")
private String principalRequestHeader;
#Value("${my.api.key.token}")
private String principalRequestValue;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors().disable().csrf().disable();
httpSecurity
.antMatcher("/api/users/**")
.authorizeRequests() //
.anyRequest().authenticated()
.and()
.addFilterBefore(new APIKeyAuthFilter(principalRequestHeader, principalRequestValue), UsernamePasswordAuthenticationFilter.class);
}
}
#Order(2)
#Configuration
public static class MySecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsService userDetailsService;
#Bean
public AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/users/**");
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors().disable().csrf().disable();
httpSecurity
.authorizeRequests()
.antMatchers("/users/UserEmailExist", "/users/User/Add", "/users/Authenticate",
"/users/User/ChangePassword")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/users/**").hasAnyRole(ROLE_ADMIN_USER, ROLE_MANAGER_USER)
.anyRequest().authenticated()
.and()
.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
}
Each config has a filter attached to it, here the api one:
public class APIKeyAuthFilter extends GenericFilterBean {
private String principalRequestHeader;
private String principalRequestValue;
public APIKeyAuthFilter(String principalRequestHeader, String principalRequestValue) {
super();
this.principalRequestHeader = principalRequestHeader;
this.principalRequestValue = principalRequestValue;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if(request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
String apiKey = getApiKey((HttpServletRequest) request);
if(apiKey != null) {
if(apiKey.equals(principalRequestValue)) {
ApiKeyAuthenticationToken apiToken = new ApiKeyAuthenticationToken(apiKey, AuthorityUtils.NO_AUTHORITIES);
SecurityContextHolder.getContext().setAuthentication(apiToken);
} else {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setStatus(401);
httpResponse.getWriter().write("Invalid API Key");
return;
}
}
}
chain.doFilter(request, response);
}
}
Here is the filter for jwt (normal user from angular):
public class AuthTokenFilter extends OncePerRequestFilter {
#Autowired
private JwtUtils jwtUtils;
#Autowired
private MyUserDetailsService userDetailsService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
String jwt = parseJwt(request);
if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
String username = jwtUtils.getUserNameFromJwtToken(jwt);
MSUserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
logger.error("Cannot set user authentication: {}", e);
}
filterChain.doFilter(request, response);
}
}
I've created two different controllers, one with prefix /api/users and second /users.
Here is what happen in two different scenarios:
The user login from Angular, get jwt token and process request which end up in the Jwt filter,
this scenarion looking good with no issues as the user is able to process request as long
he is authenticate.
Microservice send a request with api-key to url with /api/users prefix, it ended up on the same
filter the normal user ended which is not correct and without JWT token he is actually
able to proceed to the controller and process the request without going
to the correct filter.
The only solution I have is to have only one filter and process the header
for api-key and jwt but it doesn't seem right.
I've looked online and try to figure out what I'm doing wrong but no clue as of now.
An update on this issue so I hope it will help to the community.
Firstly, I removed the following code and this mainly fix the problem:
// #Override
// public void configure(WebSecurity web) throws Exception {
// web.ignoring().antMatchers("/api/users/**");
// }
The way the solution work as a whole is that the first configuration #Order(1) you
define .antMatcher which means the configuration will work only for urls that match
the prefix.
So now, scenario 1. User from Angular go the the JWT filter only.
scenario 2. API user will lend in the API filter first! But once it's done (After succesfull authentication) it still
continue to the JWT filter but becuase it doesn't have JWT the filter not doing anything.
I would like to avoid to other filter in case of API call but the solution work,
problem solved.
I must say that security in spring boot is the most complex I came across so far from other features.
Because the AuthTokenFilter is instantiated with #Bean, which causes the filter to be added to the ApplicationFilterChain, after the APIKeyAuthFilter is processed, it can also enter the AuthTokenFilter.

Solution for dual security in spring boot application - OAuth2 (jwt token bearer) + X509 (certificates)

I tried to create a spring boot configuration with dual security checks on requests (Oauth2 token bearer and X509 certificates). I had 2 alternative ideas in mind, but cannot make it work either
dedicated endpoints for each type of security validation (/certif
for certification validation, /token for token validation)
all endpoints checked with either token or certificate validation
anything successfully would apply
This is my configuration that tries to achieve idea no 1:
#EnableResourceServer
#Configuration
#Order(1)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Value("${xxx.auth.resourceId}")
private String resourceId;
#Autowired
private DefaultTokenServices tokenServices;
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(resourceId)
.tokenServices(tokenServices)
.tokenExtractor(new BearerTokenExtractor());
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/unsecured/**")
.antMatchers("/token/**")
.and().authorizeRequests()
.antMatchers("/unsecured/**").permitAll()
.anyRequest().authenticated()
;
}
}
#EnableResourceServer
#Configuration
#Order(2)
public class X509ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/certif/**")
.and()
.authorizeRequests()
.antMatchers("/certif/**").hasAuthority("AUTH")
.and().x509().subjectPrincipalRegex("CN=(.*?)(?:,|$)").userDetailsService(userDetailsService());
}
#Bean
public UserDetailsService userDetailsService() {
return new UserDetailsService() {
#Override
public UserDetails loadUserByUsername(String username) {
if (username.startsWith("xxx") || username.startsWith("XXX")) {
return new User(username, "",
AuthorityUtils
.commaSeparatedStringToAuthorityList("AUTH"));
}
throw new UsernameNotFoundException("User not found!");
}
};
}
}
For some reason I cannot make it work because filter OAuth2AuthenticationProcessingFilter seems to be deleting the authorization token created by filter X509AuthenticationFilter when I make a call with a certificate to /certif/info. I must mention that ResourceServerConfiguration is working ok when used alone and the /token/info endpoint is called with a token.
Mentioned filters are in spring-security-oauth:2.3.8 & spring-security-web:5.6.2
Orders have been changed in every direction but they seem to have no effect on how the filters are applied.
Any idea what is going on and how can I avoid this problem in order to achieve the desired behaviour?
You can try to config just one Configuration class.
You can join the two methods named as "configure", in just one.
I didn't test this code, but tell me if you did it working.
#Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/unsecured/**")
.antMatchers("/token/**")
.antMatchers("/certif/**")
.and().authorizeRequests()
.antMatchers("/unsecured/**").permitAll()
.anyRequest().authenticated()
.antMatchers("/certif/**").hasAuthority("AUTH")
.and().x509().subjectPrincipalRegex("CN=(.*?)(?:,|$)").userDetailsService(userDetailsService());
;
}

KeyCloak Spring Boot - Add custom code on auth success

I am using KeyCloak integration with Spring Boot as in this guide. I have my security config like below:
class KeycloakSecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests().antMatchers("/**").authenticated()
.anyRequest().permitAll();
}
}
I want to add some custom code for onAuthenticationSuccess before KeyCloak redirects me to the actual resource. I tried implementing a custom class with AuthenticationSuccessHandler and do formLogin().successHandler(...). This didn't work. How can I get this working??
If you still prefer to use Spring Boot KeyCloak, something like this will work.
public class KeyCloakAuthSuccessHandler extends KeycloakAuthenticationSuccessHandler {
public KeyCloakAuthSuccessHandler(AuthenticationSuccessHandler fallback) {
super(fallback);
}
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
if (authentication.getPrincipal() instanceof KeycloakPrincipal) {
AccessToken token = ((KeycloakPrincipal<?>) authentication.getPrincipal()).getKeycloakSecurityContext().getToken();
}
super.onAuthenticationSuccess(request, response, authentication);
}
}
And in your security config or similar file that extends KeyCloakWebSecurityConfigurerAdapter do the following:
#Bean
#Override
protected KeycloakAuthenticationProcessingFilter keycloakAuthenticationProcessingFilter() throws Exception {
KeycloakAuthenticationProcessingFilter filter = new KeycloakAuthenticationProcessingFilter(authenticationManagerBean());
filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy());
filter.setAuthenticationSuccessHandler(successHandler());
return filter;
}
#NotNull
#Bean
public KeyCloakAuthSuccessHandler successHandler() {
return new KeyCloakAuthSuccessHandler(new SavedRequestAwareAuthenticationSuccessHandler());
}

How to tune authenticationEntryPoint behaviour Spring Security

I have Spring Boot 2 based Security Gateway performing OAuth2 authentication sitting before GUI app and back-end.
It is configured like
#Configuration
#EnableOAuth2Client
#EnableWebSecurity
public class SecurityGatewayConfig extends WebSecurityConfigurerAdapter{
#Bean
public SecurityGatewayAuthenticationFilter filter() {
return new SecurityGatewayAuthenticationFilter("/login");
}
#Override
public void configure(HttpSecurity http) {
http
.addFilterAfter(new OAuth2ClientContextFilter(), AbstractPreAuthenticatedProcessingFilter.class)
.addFilterAfter(filter(), OAuth2ClientContextFilter.class)
.httpBasic().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
.and()
...
It redirect requests to /login and SecurityGatewayAuthenticationFilter performs authentication against external OAuth2 provider.
It is good for GIU app. However, when accessing back-end services(they have /api/ in the path) I need different behaviour: If request is not authenticated, do not redirect, but immediately return 401 error.
Any idea, how to configure Spring Security for that?
If i got you questions right, what you can do is work with different ConfigurationAdapters. The basic idea looks like:
#Order(1)
#Configuration
#EnableOAuth2Sso
public static class SecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("defaultMatcher")
private RequestMatcher defaultMatcher;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatcher(defaultMatcher)...
}
}
#Order(2)
#Configuration
public static class OtherConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatcher(yourRequestMatcher())...
}
}
Spring will evalutate each request in the order you add with Order() if you use #EnableResourceServer, this will always have Order(3)
You can then build your request matchers as example like this (in this example it matches all, but excludes explicitly some other):
#Bean
public RequestMatcher defaultMatcher(#Qualifier("apiMatcher") RequestMatcher api, #Qualifier("anyother") RequestMatcher anyother) {
final RequestMatcher all = new AntPathRequestMatcher("/**");
final RequestMatcher nonApi = new NegatedRequestMatcher(new OrRequestMatcher(api, anyother));
return new AndRequestMatcher(all, nonApi);
}
Hope that helps.
best regards,
WiPu
you need add entry point filter
#Component
public final class CustomAuthenticationEntryPoint implements
AuthenticationEntryPoint {
#Override
public void commence(final HttpServletRequest request, final
HttpServletResponse response, final AuthenticationException
authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
When a client accesses resources without authentication...

Resources