I was trying to get SecurityContext::getAuthentication as follow:
#Component
public class AuthenticationEntryPoint implements ServerAuthenticationEntryPoint {
#Override
public Mono<Void> commence(ServerWebExchange serverWebExchange, AuthenticationException authException) {
return ReactiveSecurityContextHolder.getContext()
.switchIfEmpty(Mono.error(new IllegalStateException("ReactiveSecurityContext is empty")))
.map(SecurityContext::getAuthentication)
.flatMap(a -> { return Mono.empty();});}
however, the context is empty. looks like the reactive chain was broken.
here is a snippet of the securityconfig:
#EnableReactiveMethodSecurity
public class SecurityConfig {
private AuthenticationEntryPoint authenticationEntryPoint;
#Bean
public SecurityWebFilterChain configure(final ServerHttpSecurity http) {
http
.csrf().disable()
.authorizeExchange()
...
.and()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
...
return http.build();
}
wonder if there is anything missing or if there is another way to get the context within the authenticationEntryPoint.
Related
Im begginer in spring security! I have MVC + REST application written on Spring Boot. I have a code for securing my app:
#Configuration
#EnableWebSecurity
#EnableMethodSecurity(securedEnabled = true)
public class SecurityConfig {
#Bean
#Order(1)
public SecurityFilterChain apiFilterChain(
HttpSecurity httpSecurity) throws Exception {
return httpSecurity.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(
SessionCreationPolicy.STATELESS).and()
.securityMatcher("/api/**")
.authorizeHttpRequests(authorize ->
authorize.requestMatchers("/api/user/**").hasRole("ROOT")
.anyRequest().authenticated())
.httpBasic(basic ->
basic.authenticationEntryPoint(
(request, response, exp)->
response.setStatus(401)))
.build();
}
#Bean
public SecurityFilterChain formFilterChain(
HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.authorizeHttpRequests(authorize ->
authorize.requestMatchers("/login*", "/web-res/**").permitAll()
.anyRequest().authenticated())
.formLogin(form ->
form.loginPage("/login")
.failureUrl("/login?error"))
.logout(logout ->
logout.logoutUrl("/logout")
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.clearAuthentication(true)
.deleteCookies("JSESSIONID"))
.build();
}
}
Thats work fine, but when i trying getting data from MVC (that was authorized with formFilterChain) with ajax to /api/** (that controls by apiFilterChain) - i need use basic auth.
How i can fix that to take data from /api/** with authorized by form login method?
My method after reading documentation:
#Configuration
#EnableWebSecurity
#EnableMethodSecurity(securedEnabled = true)
public class SecurityConfig {
private final Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>
.AuthorizationManagerRequestMatcherRegistry> securedRequests = authorize -> {
authorize.requestMatchers("/login*", "/web-res/**").permitAll()
.requestMatchers("/api/user/**").hasRole("ROOT")
.anyRequest().authenticated();
};
#Bean
#Order(1)
public SecurityFilterChain apiFilterChain(
HttpSecurity httpSecurity) throws Exception {
return httpSecurity.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(
SessionCreationPolicy.NEVER).and()
.authorizeHttpRequests(securedRequests)
// checking for "Authorization" header
.securityMatcher(request ->
request.getHeader("Authorization") != null)
.httpBasic(basic ->
basic.authenticationEntryPoint(
(request, response, exp)->
response.setStatus(401)))
.build();
}
#Bean
public SecurityFilterChain formFilterChain(
HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.authorizeHttpRequests(securedRequests)
.formLogin(form ->
form.loginPage("/login")
.failureUrl("/login?error"))
.logout(logout ->
logout.logoutUrl("/logout")
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.clearAuthentication(true)
.deleteCookies("JSESSIONID"))
.build();
}
}
First filter will working if request has header "Authorization"
// checking for "Authorization" header
...
.securityMatcher(request ->
request.getHeader("Authorization") != null)
...
If header "Authorization" not found - then executing formFilterChain.
I'm trying to add security headers to my Spring Boot application.
It already had a Java class with multiple filters extending from WebSecurityConfigurerAdapter. But whenever I try to add the annotation #EnableWebSecurity to this class or even with a new custom one I always receive NullPointerException for the bean springSecurityFilterChain.
Changing the order to add some filters seems to solve this problem but whenever I try to enter the app I can't because it seems the HTTP Authorization header field is null (which I recover inside one of my custom filters).
Do any have a clue of what is happening?
EDIT: After some days of cheking this I noted that the Authorization header was not the problem as the code is built to let that call enter without it and before any change it was already sent without header.
Still with the same call and the changes I'm receiving a 403 FORBIDDEN (before any change this call was receiving 302 FOUND).
This happens before even reaching the controller and I can only get debugging until the filter.
As there were no other changes in the code except the #EnableWebSecurity and the way to add one filter I suspect the problem is around here but i can't find what is causing it exactly.
EDIT: I'm adding the code in case anyone need to see it.
This is the class that has the multiple filters:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity //ADDED THIS ONE
public class MultipleEntryPointsSecurityConfig {
#Configuration
#Order(1)
public class OauthSecurityAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private OAuth2RestTemplate restTemplate;
#Bean
public CustomFilterOneFilter customFilterOneFilter() {
final CustomFilterOneFilter filter = new CustomFilterOneFilter ("/testLogin");
filter.setRestTemplate(restTemplate);
return filter;
}
#Bean
public FilterRegistrationBean<OAuth2ClientContextFilter> oauth2ClientFilterRegistration(
OAuth2ClientContextFilter filter) {
FilterRegistrationBean<OAuth2ClientContextFilter> registration = new FilterRegistrationBean<OAuth2ClientContextFilter>();
registration.setFilter(filter);
registration.setOrder(-100);
return registration;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.antMatcher("/login")
.cors()
.and()
.csrf().disable()
//CHANGED THIS
// .addFilterAfter(openIdConnectFilter(), OAuth2ClientContextFilter.class)
//FOR THESE TWO
.addFilterAfter(new OAuth2ClientContextFilter(), AbstractPreAuthenticatedProcessingFilter.class)
.addFilterAfter(openIdConnectFilter(), OAuth2ClientContextFilter.class)
.httpBasic()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/testLogin"))
.and()
.logout()
.logoutSuccessUrl("/logout")
.permitAll()
.and()
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated();
// #formatter:on
}
}
#Configuration
#Order(2)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public JwtSecurityFilter authenticationJwtTokenFilter() {
return new JwtSecurityFilter();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
.cors()
.and()
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/**").authenticated();
http
.addFilterAfter(new UsernamePasswordAuthenticationFilter(), AbstractPreAuthenticatedProcessingFilter.class)
.addFilterAfter(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
//CHANGED THE BELOW ONE FOR THE TWO ABOVE
//http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
#Configuration
#Order(3)
public static class PublicConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/**").permitAll()
.antMatchers("/api/v1/login/**").permitAll();
}
}
}
And this is the custom filter where I try to recover the Authorization header:
#Component
public class JwtSecurityFilter extends OncePerRequestFilter{
#Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
//FAILS HERE!
if(authHeader == null || !authHeader.startsWith("Bearer ")) {
SecurityContextHolder.getContext().setAuthentication(null);
chain.doFilter(request, response);
return;
}
...
}
}
I have s SpringBoot application with freemarker templates.
MvcConfig:
#Configuration
public class MvcConfig implements WebMvcConfigurer {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
}
}
WebSecurityConfig:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public PasswordEncoder getPaswwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.defaultSuccessUrl("/user")
.and()
.rememberMe()
.and()
.logout().permitAll();
}
Login controller:
#Controller
#RequestMapping
public class LogInController {
#GetMapping("/")
public String greeting(Map<String, Object> model) {
return "redirect:/login";
}
}
When I ran my application and go to http://localhost:8080/login it returns an exception:
javax.servlet.ServletException: Circular view path [login]: would dispatch back to the current handler URL [/login] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)
You're doing a recursive action~
When you try to access http://localhost:8080/login
your endpoint implementation redirects you to the same place
Acessing localhost:8090/login => redirect:/login
then try to access the same url
Acessing localhost:8090/login => redirect:/login
I followed the tutorial https://auth0.com/blog/implementing-jwt-authentication-on-spring-boot/ to integrate jwt to my application. I'm trying to add a custom successhandler to my application. Unfortunately, the successhandler is not triggering upon successful login request. I'm trying to return a user object instead of empty response with token in header. My handler is
#Component
public class AuthSuccessHandler implements AuthenticationSuccessHandler {
#Autowired
private ApplicationUserRepository userRepository;
#Autowired
private ObjectMapper objectMapper;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
UserDetails user = (UserDetails) authentication.getPrincipal();
AuthInfo authInfo = new AuthInfo();
authInfo.setUser(userRepository.findOneByUserName(user.getUsername()));
authInfo.setToken(response.getHeader("Authentication"));
response.getWriter().write(objectMapper.writeValueAsString(authInfo));
response.setContentType("application/json");
response.setStatus(200);
}
}
I have configured it in WebSecurityConfigurer.configure() as
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
.antMatchers(HttpMethod.GET, "/public/**").permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.successHandler(successHandler)
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager(), securityConfig))
.addFilter(new JWTAuthorizationFilter(authenticationManager(), securityConfig))
// this disables session creation on Spring Security
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#pvpkiran's solution didn't work. On debugging, I find that authenticationfilter is running twice and authorization filter is not triggering. Given below is the list of filters available in the application logs
Security filter chain: [
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
CorsFilter
LogoutFilter
JWTAuthenticationFilter
JWTAuthorizationFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
]
Try this. Create a local method like this.
public JwtAuthenticationTokenFilter authenticationTokenFilter() {
JwtAuthenticationTokenFilter filter = new JwtAuthenticationTokenFilter();
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationSuccessHandler(successHandler);
return filter;
}
And change your configure method like this
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
.antMatchers(HttpMethod.GET, "/public/**").permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(authenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
Another point is it is better not to make your AuthSuccessHandler a bean (remove #Component) and create a object of AuthSucessHandler like this in Your security config. Reason being, If you declare your AuthSuccessHandler as a bean, spring will scan it and add two SuccessHandlers(
1. When it scans the bean,
2. When you add it in your SecurityConfig.
)
#EnableWebSecurity
#Configuration
public class JwtSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private ApplicationUserRepository userRepository;
#Autowired
private ObjectMapper objectMapper;
public JwtAuthenticationTokenFilter authenticationTokenFilter() {
JwtAuthenticationTokenFilter filter = new JwtAuthenticationTokenFilter();
filter.setAuthenticationManager(authenticationManager());
AuthSuccessHandler successHandler = new AuthSuccessHandler(userRepository, objectMapper):
filter.setAuthenticationSuccessHandler(successHandler);
return filter;
}
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
.antMatchers(HttpMethod.GET, "/public/**").permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(authenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
Change your AuthSuccessHandler like this
public class AuthSuccessHandler implements AuthenticationSuccessHandler {
private ApplicationUserRepository userRepository;
private ObjectMapper objectMapper;
public AuthSuccessHandler(ApplicationUserRepository userRepository, ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
this.userRepository = userRepository;
}
........
}
Hi I have implemented Spring security in my spring boot web application with JWT filters. But the default authentication is happening at url http://localhost:8080/login . How to change /login to some url I need like /rest/auth/login?
My WebSecurity class is
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
public WebSecurity( UserDetailsService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder )
{
this.userDetailsService = userDetailsService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
#Override
protected void configure( HttpSecurity http ) throws Exception
{
http.cors().and().csrf().disable().authorizeRequests().antMatchers(HttpMethod.POST, "/rest/auth/**").permitAll()
.antMatchers("/static/*").permitAll().antMatchers("/").permitAll()
/* .anyRequest().authenticated() */.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()));
}
#Override
public void configure( AuthenticationManagerBuilder auth ) throws Exception
{
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
#Override
public void configure( org.springframework.security.config.annotation.web.builders.WebSecurity web )
throws Exception
{
web.ignoring().antMatchers("/static/**");
}
#Bean
CorsConfigurationSource corsConfigurationSource()
{
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}
I have a login page in my resource folder under static directory. The way Spring security works is, when user sends userName and password from the form, client has to send those credentials to /login path in the server, so that spring security verifies those credentials and creates token. But I want to change that default path /login to /rest/auth/login
In your AuthenticationFilter you can call setFilterProcessesUrl during construction, example:
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
setFilterProcessesUrl("/api/v1/tokens"); // <--- like this
}
...
Hope it helps.
You need to tweak the WebSecurityConfig.java and JWTAuthenticationFilter.
#Override
protected void configure( HttpSecurity http ) throws Exception
{
http.csrf().disable()
.authorizeRequests()
.antMatchers("/rest/noauth/**").permitAll()
.antMatchers("/rest/login").permitAll()
.antMatchers("/rest/logout").permitAll()
.antMatchers("/src/**").permitAll()
.antMatchers("/v2/api-docs/**", "/configuration/ui/**", "/swagger-resources/**",
"/configuration/security/**", "/swagger-ui.html/**", "/webjars/**")
.permitAll()
.anyRequest().authenticated()
.and()
.logout().addLogoutHandler(logoutHandler).logoutSuccessHandler(logoutSuccessHandler)
.logoutUrl("/rest/logout")
.and()
.addFilterBefore(
new JWTAuthenticationFilter("/rest/login",
UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JWTAuthorizationFilter(authenticationManager(), authTokenModelRepository),
UsernamePasswordAuthenticationFilter.class);
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
and make your JWTAuthenticationFilter extends AbstractAuthenticationProcessingFilter which has a constructor which takes the filterProcessingURl and I passed /rest/login as the parameter.
public class JWTAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(JWTAuthenticationFilter.class);
private AuthenticationManager authenticationManager;
private TokenService tokenService;
private UserModel credentials;
private RefreshTokenService refreshTokenService;
private AuthTokenModelRepository authTokenModelRepository;
private UserModelRepository userModelRepository;
public JWTAuthenticationFilter( String loginUrl, AuthenticationManager authenticationManager,
TokenService tokenService, RefreshTokenService refreshTokenService,
AuthTokenModelRepository authTokenModelRepository, UserModelRepository userModelRepository )
{
super(new AntPathRequestMatcher(loginUrl));
}
After the above configuration, the JWTAuthenticationFilter will be executed for the request /rest/login.
Just to complement Jordy Baylac's answer: in Kotlin I was struggling how to set the filter once I'm injecting the dependencies via main constructor. My solution was do something like this:
class AuthenticationFilter(
authenticationManager: AuthenticationManager?,
private var jwtUtilsComponent: JwtUtilsComponent,
) : UsernamePasswordAuthenticationFilter() {
private val authManager: AuthenticationManager? = authenticationManager
init {
setFilterProcessesUrl("/${ApiProperties.BASE_PATH}/login")
}
// more code
}
then it worked very well.
In the configure method try to add loginProcessungUrl() method. You can set the value in the parameter
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
.loginProcessingUrl(LOGIN_URL);
You need to provide the url to the login page and the url that would process the authentication. This can be done by overriding the method like this:
#Override
protected void configure( HttpSecurity http ) throws Exception
{
http.cors().and().csrf().disable().
authorizeRequests().
antMatchers(HttpMethod.POST, "/rest/auth/**").
permitAll()
.antMatchers("/static/*").permitAll()
.antMatchers("/").permitAll()
.and().formLogin().
/*This is where the juice is*/
loginPage("/login").loginProcessingUrl("/rest/auth/login")
/* .anyRequest().authenticated() */.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()));
}
The loginPage("/login") can be replaced with the route to your static login page while the loginProcessingUrl is the url of the controller that processes your login logic. Ensure that controllers exist for both /login and /loginProcesingUrl.
Modify "HttpSecurity", as follows, example:
#Override
protected void configure( HttpSecurity http ) throws Exception {
http.cors().and().csrf().disable().authorizeRequests().antMatchers(HttpMethod.POST, "/rest/auth/**").permitAll()
.antMatchers("/static/*").permitAll().antMatchers("/").permitAll()
/* .anyRequest().authenticated() */
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/rest/auth/login")
.permitAll()
.and()
.logout()
.permitAll();
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()));
}