I am getting started with Spring Web Security for my application and I am trying to implement stateless JWT authentication. Curretly, the configure method in the Web Security config is the following
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/login", "/register", "/authenticate/{uuid}").permitAll()
.anyRequest().authenticated()
;
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
My jwtAuthenticationEntryPoint is the following:
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
log.warn("Responding with unauthorized error. Message - {}", authException.getMessage());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Sorry, You're not authorized to access this resource.");
}
}
The authentication works correctly besides for the "/authenticate/{uuid}" endpoint. The request is allowed (Status 200 and correct return of the function) but I keep getting the warning from the jwtAuthenticationEntryPoint class ("Responding with unauthorized error") in the console.
Why is the EntryPoint getting triggered for that specific request and how can I resolve it?
EDIT:
AuthenticationController:
#RestController
#CrossOrigin
public class AuthenticationController {
#RequestMapping(value = "/authenticate/{uuid}", method = RequestMethod.GET)
public ResponseEntity<?> authenticate(#PathVariable String uuid){
return ResponseEntity.ok(uuid);
}
}
Pls use web.ignoring() to try as the below:
public void configure(WebSecurity web) throws Exception {
String [] notauthlist = new String[]{"/login", "/register","/authenticate/**"};
web.ignoring().antMatchers(notauthlist);
}
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests().anyRequest().authenticated()
;
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
Related
I have a problem regarding on the controllers request from a spring boot application.
I have made a certificate in order to run the app on https. The certificate works fine, it is valid.
My main problem is when i test my methods from the controller through postman they(the url reques) work fine on https and http...it shouldn't work on http. Can someone help on this ?
This is my WebSecurityConfig class:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
public static final String AUTHENTICATED_HEADER_NAME = "Authenticated";
public static final String AUTHENTICATED_TRUE = "true";
public static final String AUTHENTICATED_FALSE = "false";
#Autowired
public void globalUserDetails(final AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
auth.userDetailsService(authenticationManager).passwordEncoder(passwordEncoder);
}
#Override
#Bean(value = "authenticationManagerBean")
public org.springframework.security.authentication.AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Configuration
#Order(1)
public static class HTTPBasicSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
//todo check how we can change the root url of swagger
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/documentation**", "/configuration/**", "/v2/api-docs**", "/swagger-ui.html", "/webjars/**", "/swagger-resources/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
//todo http basic allows access to all urls after login
http
.httpBasic()
.and()
.csrf().disable()
.antMatcher("/api/**")
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest()
.authenticated();
}
}
#Configuration
#Order(2)
public static class FormLoginSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
//todo more investigation is required to check if it is safe to ignore csrf for login
.ignoringAntMatchers("/login")
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll()
.successHandler((httpServletRequest, httpServletResponse, authentication) -> {
httpServletResponse.setHeader(AUTHENTICATED_HEADER_NAME, AUTHENTICATED_TRUE);
})
.failureHandler((httpServletRequest, httpServletResponse, e) -> {
httpServletResponse.setHeader(AUTHENTICATED_HEADER_NAME, AUTHENTICATED_FALSE);
httpServletResponse.setStatus(SC_UNAUTHORIZED);
})
.and()
.logout().permitAll()
.and()
.addFilterBefore(new CorsFilter(), ChannelProcessingFilter.class)
.addFilterBefore(new CsrfHeaderFilter(), CsrfFilter.class);
http.exceptionHandling().authenticationEntryPoint((HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) -> {
if (authException != null) {
response.setStatus(SC_UNAUTHORIZED);
}
});
}
}
#Configuration
#Order(3)
public static class TestClass extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.headers()
.httpStrictTransportSecurity()
.includeSubDomains(true)
.maxAgeInSeconds(31536000);
}
}
}
and this is my spring boot version
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.2.RELEASE</version>
<relativePath></relativePath>
</parent>
Your question is touching on several points:
you can require clients to require secure channels, by adding the security.require_ssl=true configuration property (see the Spring Boot reference documentation about HTTPS)
or use the following configuration snippet http.requiresChannel().anyRequest().requiresSecure();
you might want to enforce that as well with HSTS in Spring Security
None of the above helped the situation I was in.
I figured out that chrome (postman) was automatically transforming my http requests to https.
On the other browsers http requests didn't worked.
I'm using Java Web Token (JWT) for authentication in my web app. I want to create a /logout Rest endpoint that deletes the JSession cookie on the client side, invalidates the session AND does whatever else is necessary.
The front end is a SPA written in React.
I have the following configure method in the WebSecurityConfig class that extends WebSecurityConfigurerAdapter:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/login").permitAll()
.and()
.authorizeRequests()
.antMatchers("/signup").permitAll()
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilterBefore(
new JWTAuthenticationFilter(userDetailsServiceBean()),
UsernamePasswordAuthenticationFilter.class);
}
I had the following code, but it returns 404 Not found error with the path set to /login. I want to get a HTTP response of 200 and some cleanups instead. what should I do ?
.logout().logoutUrl("/logout").logoutSuccessHandler(logoutHandler).logoutSuccessUrl("/login").invalidateHttpSession(true)
After some research I found out that if I want to use stateless Rest API design, I shouldn't use any cookie, including JSESSIONID.
Therefore I changed my code to:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilterBefore(
new JWTAuthenticationFilter(userDetailsServiceBean()),
UsernamePasswordAuthenticationFilter.class);
}
Could you first try for login request like this. First add JWTLoginFilter in your WebSecurityConfig.
Pasting code from my sample project:
http.csrf().disable() // disable csrf for our requests.
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers(HttpMethod.POST,"/login").permitAll()
.anyRequest().authenticated()
.and()
// We filter the api/login requests
.addFilterBefore(new JWTLoginFilter("/login", authenticationManager()), UsernamePasswordAuthenticationFilter.class)
// And filter other requests to check the presence of JWT in header
.addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new CORSFilter(), ChannelProcessingFilter.class);
You wouldn't require CORSFilter if your front end and back end are on same server.
Also find below JWTLoginFilter class
public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter
{
private TokenAuthenticationService tokenAuthenticationService;
public JWTLoginFilter(String url, AuthenticationManager authenticationManager) {
super(new AntPathRequestMatcher(url));
setAuthenticationManager(authenticationManager);
tokenAuthenticationService = new TokenAuthenticationService();
}
#Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
throws AuthenticationException, IOException, ServletException {
AccountCredentials credentials = new ObjectMapper().readValue(httpServletRequest.getInputStream(), AccountCredentials.class);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(credentials.getUsername(), credentials.getPassword());
return getAuthenticationManager().authenticate(token);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication)
throws IOException, ServletException {
String name = authentication.getName();
tokenAuthenticationService.addAuthentication(response, name);
}
}
AccountCredential class is simple POJO class containing two fields username and password, which I was using to receive the request.
Also please note that UsernamePasswordAuthenticationFilter that we are using require two fields in login request 'username' and 'password'. Like {"username":"user1","password":"secret1"}
Registered handler in security configuration:
.formLogin().loginPage(/login).failureHandler(new CustomAuthenticationFailureHandler())
My custom failure handler:
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
private String failureUrl="/login?error";
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
this.redirectStrategy.sendRedirect(request, response, this.failureUrl);
}
}
onAuthenticationFailure is getting triggered when auth failures happen but along with that one more request is also triggering by Spring, please find the another req stack,
LoginUrlAuthenticationEntryPoint.commence(HttpServletRequest, HttpServletResponse, AuthenticationException) line: 169
ExceptionTranslationFilter.sendStartAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, AuthenticationException) line: 204
ExceptionTranslationFilter.handleSpringSecurityException(HttpServletRequest, HttpServletResponse, FilterChain, RuntimeException) line: 178
So this request is overriding my sendredirect request.
Should I need to configure/do anything else to achieve this ? Please help on this.
Thanks in advance
My configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(SignUpFilter(userService), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers(
"/",
"/register",
"/retrieveUsernameForm",
.permitAll()
.and()
.anonymous()
.authenticationProvider(anonymousAuthenticationProvider())
.authenticationFilter(anonymousAuthenticationFilter())
.and()
.formLogin()
.loginPage(SecurityController.URI_LOGIN)
.failureHandler(new CustomAuthenticationFailureHandler())
.permitAll()
.defaultSuccessUrl(securityController.createUri(SecurityController.PATH_PART_LOGIN))
.and()
.rememberMe()
.rememberMeParameter("remember-me")
.key(REMEMBERME_TOKEN_KEY)
.rememberMeServices(tokenBasedRememberMeServices())
.and()
.logout()
.permitAll()
.and()
.headers()
.frameOptions().disable()
.addHeaderWriter(new StaticHeadersWriter("X-Frame-Options", "SAMEORIGIN"))
.and()
.csrf().disable();
http.formLogin()
.addObjectPostProcessor(new ObjectPostProcessor<UsernamePasswordAuthenticationFilter>() {
#Override
public UsernamePasswordAuthenticationFilter postProcess(UsernamePasswordAuthenticationFilter filter) {
filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategyForContentAndStoreUsers());
return filter;
}
});
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
}
I'm trying to restrict access to a page called dashboard.html to unauthenticated users. So far, I've had no success. Here's my WebSecurityConfigurerAdapter:
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class CustomWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationSuccessHandler authenticationSuccessHandler;
#Autowired
private CustomAuthenticationFailureHandler authenticationFailureHandler;
#Autowired
private CustomUserDetailsService userDetailsService;
#Autowired
private TokenAuthenticationService tokenAuthenticationService;
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/index.html", "/",
"/login.html","/signup.html", "/videos/**",
"/login", "/logout", "/images/**", "/fonts/**",
"/css/**", "/js/**", "/pages/**", "/sass/**"
).permitAll()
.and()
.authorizeRequests()
.antMatchers("/dashboard/**", "/dashboard.html/**").authenticated()
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilterBefore(new StatelessLoginFilter("/login", tokenAuthenticationService, userDetailsService, authenticationManager()), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService), UsernamePasswordAuthenticationFilter.class)
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
.usernameParameter("email")
.passwordParameter("password")
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailureHandler)
.and()
.logout()
.logoutSuccessUrl("/")
.deleteCookies("JSESSIONID")
.permitAll()
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.addFilterAfter(new CsrfTokenFilter(), CsrfFilter.class);
}
}
Whenever I have it set up this way, whenever I try to login, an infinite redirect loop is caused. The browser tries to navigate to dashboard.html but is restricted. This causes a redirect to the login page, which tries to redirect to the dashboard since there is a valid token.
If I have it set up like below, everyone can access dashboard.html and make calls to the /dashboard endpoint which is not desired:
http
.authorizeRequests()
.antMatchers("/index.html", "/",
"/login.html","/signup.html", "/videos/**",
"/login", "/logout", "/images/**", "/fonts/**",
"/css/**", "/js/**", "/pages/**", "/sass/**",
"/dashboard/**", "/dashboard.html/**").permitAll()
.and()
.authorizeRequests()
.anyRequest().authenticated()
My login uses JWT tokens and uses the filter below to set the SecurityContext placeholder:
class StatelessLoginFilter extends AbstractAuthenticationProcessingFilter {
private final TokenAuthenticationService tokenAuthenticationService;
private final CustomUserDetailsService userDetailsService;
protected StatelessLoginFilter(String urlMapping, TokenAuthenticationService tokenAuthenticationService,
CustomUserDetailsService userDetailsService, AuthenticationManager authManager) {
super(new AntPathRequestMatcher(urlMapping));
this.userDetailsService = userDetailsService;
this.tokenAuthenticationService = tokenAuthenticationService;
setAuthenticationManager(authManager);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
final BusinessUser user = new ObjectMapper().readValue(request.getInputStream(), BusinessUser.class);
final UsernamePasswordAuthenticationToken loginToken = new UsernamePasswordAuthenticationToken(
user.getEmail(), user.getPassword());
return getAuthenticationManager().authenticate(loginToken);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, Authentication authentication) throws IOException, ServletException {
final BusinessUser authenticatedUser = userDetailsService.loadUserByUsername(authentication.getName());
final UserAuthentication userAuthentication = new UserAuthentication(authenticatedUser);
tokenAuthenticationService.addAuthentication(response, userAuthentication);
SecurityContextHolder.getContext().setAuthentication(userAuthentication);
}
I'm using the line SecurityContextHolder.getContext().setAuthentication(userAuthentication); to set the authentication. This works perfectly fine. If a user is found in the DB matching the credentials sent from the user, then the security context is usable to retrieve various data associated to the user.
MY QUESTION: How can I restrict the page dashboard.html and calls to the /dashboard endpoint to unauthenticated users (those without an authentication object inside the SecurityContextHolder)?
You can use a custom RequestMatcher in combination with denyAll. First, your custom matcher:
public class PermittedPagesMatcher implements RequestMatcher {
#Override
public boolean matches(HttpServletRequest httpServletRequest) {
if (matchesToPaths(httpServletRequest,"/index.html", "/", "/login.html","/signup.html", "/videos/**", "/login", "/logout", "/images/**", "/fonts/**", "/css/**", "/js/**", "/pages/**", "/sass/**", "/dashboard/**", "/dashboard.html/**")) {
return true;
}
if (matchesToPaths(httpServletRequest, "/dashboard/**", "/dashboard.html/**")) {
return httpServletRequest.getUserPrincipal() == null;
}
return false;
}
private boolean matchesToPaths(HttpServletRequest httpServletRequest, String... paths) {
for (String p : paths) {
if (new AntPathRequestMatcher(p).matches(httpServletRequest)) {
return true;
}
}
return false;
}
}
This custom RequestMatcher filters your request to permitted pages to all of your default pages and the dashboard is only available if the request is not authenticated.
Second, combine the matcher and denyAll()
http
.authorizeRequests()
.requestMatchers(new PermittedPagesMatcher())
.permitAll()
.and()
.antMatchers("/dashboard/**", "/dashboard.html/**")
.denyAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
denyAll() ensures, that by default no one is allowed to access this page.
Attention: The order of permit and deny is important!
I have a Spring Boot application, in which most of the pages are secured (you need to log in to access them) using following security configuration.
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
class SecurityConfiguration extends
WebSecurityConfigurerAdapter {
#Autowired
public void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user")
.password("myapp")
.roles("ADMIN")
.and()
.withUser("guest")
.password("guest")
.roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/index.html", "/home.html", "/login.html", "/")
.permitAll()
.anyRequest().authenticated().and()
.csrf()
.csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
}
private CsrfTokenRepository csrfTokenRepository() {
final HttpSessionCsrfTokenRepository repository =
new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
In my application class, I have a service publicData, which I want to be accessible without authentication (even, if the user isn't logged in).
#SpringBootApplication
#RestController
public class MyAppApplication {
#RequestMapping("/resource")
public Map<String,Object> home() {
final Map<String,Object> model = new HashMap<>();
model.put("id", UUID.randomUUID().toString());
model.put("content", "Hello World");
return model;
}
#RequestMapping("/publicData")
public String publicData() {
return ...;
}
#RequestMapping("/user")
public Principal user(final Principal user) {
return user;
}
public static void main(final String[] args) {
SpringApplication.run(MyAppApplication.class, args);
}
}
How can I do this?
I tried
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/index.html", "/home.html", "/login.html", "/")
.permitAll()
.anyRequest().authenticated().and()
.csrf()
.csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)
.and()
.authorizeRequests()
.antMatchers("/publicData").permitAll();
}
but it didn't work.
You can create a role with all permission to access and grant that access just in some methods using Spring security annotation
http://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html
Of course every user need to get this role automatically when he connect in your application.
<http use-expressions="true">
<intercept-url pattern="/*"
access="hasRole('admin')"/>
</http>
Then in your free access method
#PreAuthorize("hasRole('admin')")
public void create(Contact contact);