I am using spring security in my java project to secure web services.
All my web services are secured here are filter chain configuiration that i use:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilter(new JwtEmailAndPasswordAuthenticationFilter(authenticationManager(), jwtConfig, secretKey))
.addFilterAfter(new JwtTokenVerifier(secretKey, jwtConfig),JwtEmailAndPasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest()
.authenticated();
}
Now I have a requirement to create one web service that will be accessible to everybody.
For this purpose I add this row:
.and().authorizeRequests().antMatchers("/auth/reset").permitAll()
To chained filers and it looks like that:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilter(new JwtEmailAndPasswordAuthenticationFilter(authenticationManager(), jwtConfig, secretKey))
.addFilterAfter(new JwtTokenVerifier(secretKey, jwtConfig),JwtEmailAndPasswordAuthenticationFilter.class)
.authorizeRequests()
.and().authorizeRequests().antMatchers("/auth/reset").permitAll()
.anyRequest()
.authenticated();
}
After adding the row above I get exception JWT not found, it comes from this JwtTokenVerifier bean which is fired by
addFilterAfter.
My question is how can I prevent trigger addFilterAfter or any other filter that is needed only for the secured routes in case of the request for an unsecured web service?
UPDATE
Here is definition of JwtTokenVerifier bean:
public class JwtTokenVerifier extends OncePerRequestFilter {
private final SecretKey secretKey;
private final JwtConfig jwtConfig;
public JwtTokenVerifier(SecretKey secretKey,
JwtConfig jwtConfig) {
this.secretKey = secretKey;
this.jwtConfig = jwtConfig;
}
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
String authorizationHeader = request.getHeader(jwtConfig.getAuthorizationHeader());
if (Strings.isNullOrEmpty(authorizationHeader) || !authorizationHeader.startsWith(jwtConfig.getTokenPrefix())) {
filterChain.doFilter(request, response);
return;
}
String token = authorizationHeader.replace(jwtConfig.getTokenPrefix(), "");
try {
Jws<Claims> claimsJws = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token);
Claims body = claimsJws.getBody();
String email = body.getSubject();
var authorities = (List<Map<String, String>>) body.get("authorities");
Set<SimpleGrantedAuthority> simpleGrantedAuthorities = authorities.stream()
.map(m -> new SimpleGrantedAuthority(m.get("authority")))
.collect(Collectors.toSet());
Authentication authentication = new UsernamePasswordAuthenticationToken(
email,
null,
simpleGrantedAuthorities
);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (JwtException e) {
throw new IllegalStateException(String.format("Token %s cannot be trusted", token));
}
filterChain.doFilter(request, response);
} catch (JwtException e) {
throw e;
}
}
}
I realize you didn't ask this, but I'd first recommend that you use Spring Security's built-in JWT support instead of building your own. Security is hard, and using vetted support is likely more secure.
Regarding your question about having separate auth mechanisms for separate endpoints, you can instead publish two filter chains, one for open endpoints and one for token-based endpoints like so:
#Bean
#Order(0)
SecurityFilterChain open(HttpSecurity http) {
http
.requestMatchers((requests) -> requests.antMatchers("/auth/reset"))
.authorizeHttpRequests((authorize) -> authorize.anyRequest().permitAll());
return http.build();
}
#Bean
SecurityFilterChain tokenBased(HttpSecurity http) {
http
.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
.addFilter(...)
.addFilterAfter(...);
return http.build();
}
Have you tried something like that :
.and()
.authorizeRequests()
.antMatchers("...").permitAll()
.and()
.addFilter(...)
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
Related
I am using jwt token based spring security.
I have an endpoint '/sample-endpoint' which requires authentication. However, I need to bypass security for this endpoint when the request comes from a specific domain called xyz.com.
Is it possible to do so? If so, how to do that?
Here is what I have so far.
SecurityConfig
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// cant add the end point here as it would open up for everybody.
public static final String[] unauthedUrls = { "healthcheck","some-other-endpoint"}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.disable()
.csrf()
.disable()
.cors()
.and()
.headers().frameOptions()
.disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.addFilterAfter(jwtSecurityFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers(unauthedUrls)
.permitAll()
.anyRequest()
.authenticated();
}
Here is JwtSecurityFilter implementation.
public class JwtSecurityFilter extends OncePerRequestFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(JwtSecurityFilter.class);
private static final String JWT_PREFIX = "Bearer ";
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
setAuthenticationContext(request);
chain.doFilter(request, response);
}
private void setAuthenticationContext(HttpServletRequest request) {
try {
String token = getJwt(request);
if (StringUtils.isBlank(token)) {
throw new RuntimeException("Authorization token not provided");
}
// some logic here...
} catch (Exception ex) {
if (request != null && Arrays.stream(SecurityConfig.unauthedUrls).anyMatch(url -> request.getRequestURI().contains(url))) {
// it's a URL that isn't authenticated so an exception here is normal
// if we couldn't get a token
return;
}
LOGGER.warn("Unable to authenticate request: {} {}", ex.getMessage(), request == null ? null : request.getRequestURI());
}
}
private String getJwt(HttpServletRequest request) {
String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
if (StringUtils.isBlank(authHeader) || !authHeader.startsWith(JWT_PREFIX)) {
return "";
}
return authHeader.replaceFirst(Pattern.quote(JWT_PREFIX), "");
}
}
What you want is to ignore certain URLs for this override the configure method that takes WebSecurity object and ignores the pattern. For example, using the api:
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/v1/signup");
}
And remove that line from the HttpSecurity part. This will tell Spring Security to ignore this URL and don't apply any filters to them.
I have a better way:
http
.authorizeRequests()
.antMatchers("/api/v1/signup/**").permitAll()
.anyRequest().authenticated()
I am having a hard time configuring my spring security. The problem is, my authentication filter always skips my success and failure handlers whenever I authenticate via a custom UsernamePasswordAuthenticationFilter. I don't seem to know why this happens.
First off, I pass the authentication parameter as JSON, and filter out the username and password, then I pass those two parameters into a new UsernamePasswordAuthenticationToken(username, password) then I get the authentication manager and authenticate the returned token. At the point of success full authentication I expect that the success handler should take over but no it doesn't get called at all.
This is my security configuration.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.and()
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.authorizeRequests()
.antMatchers("/signup")
.permitAll()
.antMatchers("/", "/security/login", "/request", "/request.html")
.authenticated()
.and()
.formLogin()
.loginProcessingUrl("/security/login")
.successHandler(authenticationSuccessHandler())
.failureHandler(authenticationFailureHandler())
.and()
.logout()
.logoutUrl("/logout")
.permitAll()
.and()
.addFilterAfter
(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
//.and()
.userDetailsService(userDetailsServiceBean());
}
The relevant beans are
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServiceBean());
}
#Bean
#Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return new JdbcUserDetails();
}
#Bean
public RestAuthenticationSuccessHandler authenticationSuccessHandler(){
return new RestAuthenticationSuccessHandler();
}
#Bean
public RestAuthenticationFailureHandler authenticationFailureHandler(){
return new RestAuthenticationFailureHandler();
}
#Bean
JsonAuthenticationFilter authenticationFilter() throws Exception {
logger.debug("Authenication filter processing loggin request ");
JsonAuthenticationFilter filter = new JsonAuthenticationFilter();
filter.setAuthenticationManager(authenticationManagerBean());
return filter;
}
The filter is
public class JsonAuthenticationFilter extends UsernamePasswordAuthenticationFilter{
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
UsernamePasswordAuthenticationToken authRequest = this.getUserNamePasswordAuthenticationToken(request);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
and finally my success handler
class RestAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication)
throws ServletException, IOException {
logger.debug("Successful login");
System.out.println("\n\n\n\n\n\n\n\nresponse here\n\n\n\n\n\n\n");
response.getWriter().write("{This is a login success response}");
response.getWriter().flush();
response.getWriter().close();
}
I have been battling for too long
Spring Security will back off on a given bean configuration when you supply that bean.
So, because you supplied your filter (JsonAuthenticationFilter), Spring Security expects that you'll know best how to compose it.
So, then, you'd instead do:
#Bean
JsonAuthenticationFilter authenticationFilter() {
JsonAuthenticationFilter filter = new JsonAuthenticationFilter();
// .. other configs
filter.setAuthenticationSuccessHandler(new RestAuthenticationSuccessHandler());
filter.setAuthenticationFailureHandler(new RestAuthenticationFailureHandler());
}
It looks like there is a lot going on, so if that doesn't solve your issue, feel free to put together a sample, say on GitHub, and I'd be happy to look it over.
I have following configuration for my spring security
http
// if I gonna comment adding filter it's gonna work as expected
.addFilterBefore(tokenAuthenticationFilter, BasicAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/rest/_health")
.permitAll()
.anyRequest()
.authenticated()
.and()
.csrf()
.disable();
So without custom filter everything works as expected - I have access to /rest/_health and access denied to everything else.
But when I'm adding this filter - matchers don't work and filter works even for 'permitAll' resources.
Code from my filter looks like this:
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
try {
String token = httpRequest.getHeader(HttpHeaders.AUTHORIZATION);
Authentication authentication = authenticationManager.authenticate(
new TokenBasedAuthentication(token)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
} catch (AuthenticationException ex) {
authenticationEntryPoint.commence(httpRequest, httpResponse, ex);
}
}
Any suggestions?
The filter is executed before the checks on the endpoints. In your case the unsuccesful authentication aborts the filter chain and let the access point handle the rest. Whith this you do not allow anonymous access at all. You need to set the Authentication to null to indicate that an anonymous user is accessing the endpoint.
Try the following:
Authentication authentication = null;
String token = httpRequest.getHeader(HttpHeaders.AUTHORIZATION);
//check if not anonymous and preceed with authentication
if (token != null && !token.isEmpty()) {
try {
authentication = authenticationManager.authenticate(
new TokenBasedAuthentication(token));
} catch (AuthenticationException ex) {
//illigal access atempt
authenticationEntryPoint.commence(httpRequest, httpResponse, ex);
}
}
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
In my configuration (that extends WebSecurityConfigurerAdapter), I did it in this way:
http.csrf().disable().
addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/login*", "/logout*").permitAll().anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/secured/index")
.failureUrl("/login?error=true").permitAll()
.and()
.logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
.deleteCookies("JSESSIONID")
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.permitAll();
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/static/**", "/webjars/**", "/error*");
}
Maybe not perfect, but it works.
I have a problem with Spring Security authentication failure handler redirect with parameter.
In security config when I use
failureUrl("/login.html?error=true")
it works. But when I use custom authentication failure handler (as shown below), it always returns: url/login.html
getRedirectStrategy().sendRedirect(request, response, "/login.html?error=true");
or
response.sendRedirect(request.getContextPath() + "/login.html?error=true");
I don't know whats wrong. Why does it not show the parameter ?error=true?
Info: I am using Spring + JSF + Hibernate + Spring Security
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.usernameParameter("j_username")
.passwordParameter("j_password")
.loginProcessingUrl("/j_spring_security_check")
.failureHandler(customAuthenticationFailureHandler)// .failureUrl("/login.html?error=true")//.successHandler(authSuccsessHandler)
.defaultSuccessUrl("/dashboard.html")
.permitAll()
.and()
.logout()
.invalidateHttpSession(true)
.logoutSuccessUrl("/")
.permitAll()
.and()
.exceptionHandling()
.accessDeniedPage("/access.html")
.and()
.headers()
.defaultsDisabled()
.frameOptions()
.sameOrigin()
.cacheControl();
http
.csrf().disable();
}
This is custom authentication failure handler:
#Component
public class CustomAuthFailureHandler extends SimpleUrlAuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
getRedirectStrategy().sendRedirect(request, response, "/login.html?error=true");
}
}
I will change parameter for some cases.
You didn't allow anonymous access to URL /login.html?error=true, so you are redirected to the login page (/login.html).
AbstractAuthenticationFilterConfigurer#permitAll allows access (for anyone) to failure URL but not for custom failure handler:
Ensures the urls for failureUrl(String) as well as for the HttpSecurityBuilder, the getLoginPage() and getLoginProcessingUrl() are granted access to any user.
You have to allow access explicitly with AbstractRequestMatcherRegistry#antMatchers:
Maps a List of AntPathRequestMatcher instances that do not care which HttpMethod is used.
and ExpressionUrlAuthorizationConfigurer.AuthorizedUrl#permitAll:
Specify that URLs are allowed by anyone.
You don't have to allow the exact URL /login.html?error=true, because AntPathRequestMatcher ignores the query string:
Matcher which compares a pre-defined ant-style pattern against the URL ( servletPath + pathInfo) of an HttpServletRequest. The query string of the URL is ignored and matching is case-insensitive or case-sensitive depending on the arguments passed into the constructor.
Your modified configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login.html").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.usernameParameter("j_username")
.passwordParameter("j_password")
.loginProcessingUrl("/j_spring_security_check")
.failureHandler(customAuthenticationFailureHandler)// .failureUrl("/login.html?error=true")//.successHandler(authSuccsessHandler)
.defaultSuccessUrl("/dashboard.html")
.permitAll()
.and()
.logout()
.invalidateHttpSession(true)
.logoutSuccessUrl("/")
.permitAll()
.and()
.exceptionHandling()
.accessDeniedPage("/access.html")
.and()
.headers()
.defaultsDisabled()
.frameOptions()
.sameOrigin()
.cacheControl();
http
.csrf().disable();
}
In the case of OAuth token failure, I am getting below response, which is inconsistent with app response style.
{
"error": "invalid_token",
"error_description": "Invalid access token: 4cbc6f1c-4d47-44bd-89bc-92a8c86d88dbsdfsdfs"
}
I just wanted to use common response object for the consistency.
Following approach worked for me.
Build your resource server with your custom entry-point object
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.authenticationEntryPoint(new CustomOAuth2AuthenticationEntryPoint());
}
and here is your custom entry point
public class CustomOAuth2AuthenticationEntryPoint extends OAuth2AuthenticationEntryPoint{
public CustomOAuth2AuthenticationEntryPoint() {
super.setExceptionTranslator(new CustomOAuth2WebResponseExceptionTranslator());
}
}
here is your custom WebResponseExceptionTranslator, In my case I have just used a replica of DefaultWebResponseExceptionTranslator and rewritten handleOAuth2Exception method.
CustomOAuth2WebResponseExceptionTranslator implements WebResponseExceptionTranslator<Response> {
....
.....
private ResponseEntity<Response> handleOAuth2Exception(OAuth2Exception e) throws IOException {
int status = e.getHttpErrorCode();
HttpHeaders headers = new HttpHeaders();
headers.set("Cache-Control", "no-store");
headers.set("Pragma", "no-cache");
if (status == HttpStatus.UNAUTHORIZED.value() || (e instanceof InsufficientScopeException)) {
headers.set("WWW-Authenticate", String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, e.getSummary()));
}
ResponseEntity<Response> response =new ResponseEntity<>(new Response().message(e.getMessage()).status(StatusEnum.ERROR)
.errorType(e.getClass().getName()), HttpStatus.UNAUTHORIZED);
return response;
}
Result looks like
{
"status": "error",
"message": "Invalid access token: 4cbc6f1c-4d47-44bd-89bc-92a8c86d88dbsdfsdfs",
"error_type": "org.springframework.security.oauth2.common.exceptions.InvalidTokenException"
}
For multiple purposes I need to use
SecurityContextHolder.getContext().getAuthentication()
methods in my controllers/services.
I did migrate my app to Spring Boot 1.4.1 from XML configured Spring MVC app (now only Java configs), similar approach worked before.
I have a problem calling SecurityContextHolder.getContext().getAuthentication(), for example in this controller:
#RestController
#Secured("IS_AUTHENTICATED_ANONYMOUSLY")
#RequestMapping("utils")
public class UtilsController {
#RequestMapping(value = "/check_auth", method = RequestMethod.GET)
public Boolean getAuthState() throws SQLException {
if (SecurityContextHolder.getContext().getAuthentication() == null){
logger.info("Auth obj null");
}
if (SecurityContextHolder.getContext().getAuthentication().getName() != null && SecurityContextHolder.getContext().getAuthentication().getName() != "anonymousUser") {
return true;
} else return false;
}
}
it always returns null. Can't figure out why anonymous authentication is not working.
Here is the Spring Security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and()
.formLogin()
.successHandler(ajaxSuccessHandler)
.failureHandler(ajaxFailureHandler)
.loginProcessingUrl("/authentication")
.passwordParameter("password")
.usernameParameter("username")
.and()
.logout()
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.and()
.csrf().disable()
// .anonymous().disable()
.authorizeRequests()
// .anyRequest().anonymous()
.antMatchers("/utils").permitAll()
.antMatchers("/oauth/token").permitAll()
.antMatchers("/admin/*").access("hasRole('ROLE_ADMIN')")
.antMatchers("/user/*").access("hasRole('ROLE_USER')");
}
I did tried with and without #Secured annotation on the controller.
.authorizeRequests()
// .anyRequest().anonymous()
.antMatchers("/utils").permitAll()
different variations with this settings.
You are getting null with:
SecurityContextHolder.getContext().getAuthentication()
because you are not authenticating within you security configuration.
You can add a simple:
.authenticated()
.and()
// ...
.formLogin();
in case you're using form login.
Now after you'll authenticate each request you suppose to get something other than null.
Here's an example from Spring Security docs:
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/**", "/signup", "/about").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
.anyRequest().authenticated()
.and()
// ...
.formLogin();
}