I'm trying to migrate classic Spring Boot Application to Reactive Spring Boot Application, but I have a problems with this task.
How to migrate the code below
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api").anonymous()
.antMatchers("/api/**").authenticated().and()
.httpBasic();
http
.authorizeRequests()
.antMatchers("/login").anonymous()
.antMatchers("/", "/error", "/**/favicon.ico", "/css/**", "/fonts/**", "/js/**", "/images/avatar.png", "/images/logo.png", "/profile", "/profile/find", "/profile/view/**", "/api/register").permitAll()
.anyRequest().authenticated().and()
.formLogin().loginPage("/login").loginProcessingUrl("/profile/login").failureUrl("/login?error").usernameParameter("usr").passwordParameter("pass").and()
.logout().logoutUrl("/logout").invalidateHttpSession(true).deleteCookies("jsessionid","nebp").logoutSuccessUrl("/login?logout").and()
.rememberMe().key("nebpps").tokenValiditySeconds(2419200).rememberMeParameter("remember_me").rememberMeCookieName("nebp").useSecureCookie(true).and()
.csrf().ignoringAntMatchers("/api/**").and()
.exceptionHandling().accessDeniedPage("/403");//.and()
//.requiresChannel().anyRequest().requiresSecure();
}
#Bean(name = "passwordEncoder")
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
to style code like below
#Configuration
#EnableWebFluxSecurity
public class SecurityConfiguration {
#Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
return http
.csrf().disable()
.authorizeExchange()
.pathMatchers("/login", "/logout").permitAll()
.pathMatchers("/i18n/**",
"/css/**",
"/fonts/**",
"/icons-reference/**",
"/img/**",
"/js/**",
"/vendor/**").permitAll()
.anyExchange()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.and()
.logout()
.logoutUrl("/logout")
.and()
.build();
}
//in case you want to encrypt password
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
I accepted that certain elements can not be defined as before like usernameParameter.
First of all, how to set that the given path (/logout) is only for anonymous users.
Secondly, how to have CSRF enabled, but to have exclusion for addresses beginning with /api
Related
I want two different login pages. One for admins and the other one for users.
Using the code below only the class with #Order(1) works when I remove the #Order annotation from the static classes, only the last one works. I used them in two different ConfigSecurity files, and it didn't work.
I m expecting for both of them to work. Yet only one is working.
package com.example.FlightAgency.security;
import com.example.FlightAgency.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
#Configuration
#EnableWebSecurity
public class SecurityConfig {
#Configuration
#Order(1)
public static class App1ConfigurationAdapter {
#Bean
public UserDetailsService userDetailsService() {
return new UserService();
}
#Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
#Bean
public SecurityFilterChain filterChainApp1(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.requestMatchers("/user/**").hasAuthority("USER")
.and()
.formLogin()
.loginPage("/user/login")
.usernameParameter("email")
.loginProcessingUrl("/user/login")
.defaultSuccessUrl("/user/")
.permitAll()
.and()
.logout()
.logoutUrl("/user/logout")
.logoutSuccessUrl("/user/login")
.deleteCookies("JSESSIONID")
.and()
.exceptionHandling()
.accessDeniedPage("/403")
.and()
.csrf().disable();
return http.build();
}
}
#Configuration
#Order(2)
public static class App2ConfigurationAdapter {
#Bean
public SecurityFilterChain filterChainApp2(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.requestMatchers("/admin/**").hasAuthority("ADMIN")
.and()
.formLogin()
.loginPage("/admin/login")
.usernameParameter("email")
.loginProcessingUrl("/admin/login")
.defaultSuccessUrl("/admin/")
.permitAll()
.and()
.logout()
.logoutUrl("/admin/logout")
.logoutSuccessUrl("/admin/login")
.deleteCookies("JSESSIONID")
.and()
.exceptionHandling()
.accessDeniedPage("/403")
.and()
.csrf().disable();
return http.build();
}
}
} `
Both security filter chains are not restricted (default is /**). You have to restrict the first one with securityMatcher, see Spring Security Reference:
Multiple HttpSecurity Instances
We can configure multiple HttpSecurity instances just as we can have multiple <http> blocks in XML. The key is to register multiple SecurityFilterChain #Beans. The following example has a different configuration for URL’s that start with /api/.
#Configuration
#EnableWebSecurity
public class MultiHttpSecurityConfig {
#Bean
public UserDetailsService userDetailsService() throws Exception {
// ensure the passwords are encoded properly
UserBuilder users = User.withDefaultPasswordEncoder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users.username("user").password("password").roles("USER").build());
manager.createUser(users.username("admin").password("password").roles("USER","ADMIN").build());
return manager;
}
#Bean
#Order(1)
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/**")
.authorizeHttpRequests(authorize -> authorize
.anyRequest().hasRole("ADMIN")
)
.httpBasic(withDefaults());
return http.build();
}
#Bean
public SecurityFilterChain formLoginFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.formLogin(withDefaults());
return http.build();
}
}
Configure Authentication as usual.
Create an instance of SecurityFilterChain that contains #Order to specify which SecurityFilterChain should be considered first.
The http.securityMatcher states that this HttpSecurity is applicable only to URLs that start with /api/.
Create another instance of SecurityFilterChain. If the URL does not start with /api/, this configuration is used. This configuration is considered after apiFilterChain, since it has an #Order value after 1 (no #Order defaults to last).
I am trying this code but get this exception:nested exception is:
java.lang.IllegalStateException: Can't configure antMatchers after anyRequest.
Java code
package com.cris.cms.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(encodePWD());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable(); http.authorizeRequests().antMatchers("/rest/**").authenticated().anyRequest().permitAll().and()
.authorizeRequests().antMatchers("/secure/**").authenticated().anyRequest().hasAnyRole("ADMIN").and()
.formLogin().permitAll();
}
#Bean
public BCryptPasswordEncoder encodePWD() {
return new BCryptPasswordEncoder();
}
}
You don't need to call .authorizeRequests() multiple times but even if you call it, it is not an error. If you remove the .authorizeRequests() multiple calls, and format it, it would be look as follows:
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeRequests()
.antMatchers("/rest/**").authenticated()
.anyRequest().permitAll()
.antMatchers("/secure/**").authenticated()
.anyRequest().hasAnyRole("ADMIN")
.and()
.formLogin()
.permitAll();
}
So now think of it as of multiple if else if else like below
if (condition1) {
doX();
} else if (condition2) {
doY();
} else {
doZ()
}
As it self explains, anyRequest() will evaluate to true always. So if you put anyRequest() as the first condition, it will never go past the first if block. So you always have to put anyRequest() as the last line within authorizeRequests()
So put specific urls matching first. If you have antMatchers("/rest/**") and antMatchers("**"), you have to put antMatchers("/rest/**")
Similarly you cannot have more than one else blocks because it is the last one without condition. You have to remove one of .anyRequest().permitAll() or .anyRequest().hasAnyRole("ADMIN")
So it will become
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeRequests()
.antMatchers("/rest/**").authenticated()
.antMatchers("/secure/**").authenticated()
.antMatchers("/admin specific url").hasAnyRole("ADMIN")
.anyRequest().permitAll()
.and()
.formLogin()
.permitAll();
}
I have read How to apply spring security filter only on secured endpoints?, which seems to be the closest to my question, but does not sufficiently answer it.
Further below you will see a WebSecurityConfigurerAdapter-configuration I am currently using. It will not remain like this as I will not expose h2-console later on.
My problem is, that JwtAuthenticationFilter is always executed. I'd rather want the filter to be executed on requests, which demand authentication (in my particular case: only what's described here:
.authorizeRequests()
.anyRequest()
.authenticated()
).
How to achieve this?
P.s.: my application login works as expected while H2-console does, too, but keeps throwing io.jsonwebtoken.SignatureException, because the JWT H2-console generates and uses is naturally different from the one my application uses.
WebSecurityConfigurerAdapter:
package com.particles.authservice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import com.particles.authservice.jwt.JwtAuthenticationEntryPoint;
import com.particles.authservice.jwt.JwtAuthenticationFilter;
import com.particles.authservice.service.UserService;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Override
public void configure(final AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(userService)
.passwordEncoder(passwordEncoder());
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
//#formatter:off
http
.cors()
.and()
.csrf()
.disable()
.headers()
.frameOptions()
.disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/",
"/favicon.ico",
"/**/*.png",
"/**/*.gif",
"/**/*.svg",
"/**/*.jpg",
"/**/*.html",
"/**/*.css",
"/**/*.js")
.permitAll()
.antMatchers("/h2-console/**").permitAll()
.antMatchers(HttpMethod.POST, "/register")
.permitAll()
.antMatchers(HttpMethod.GET, "/confirm")
.permitAll()
.antMatchers(HttpMethod.POST, "/login")
.permitAll()
.antMatchers(HttpMethod.GET, "/user")
.permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
;
//#formatter:on
}
#Bean(BeanIds.AUTHENTICATION_MANAGER)
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Edit: here's the JwtAuthenticationFilter. If you need the TOs as well, let me know.
JwtAuthenticationFilter:
package com.particles.authservice.jwt;
import java.io.IOException;
import java.util.Optional;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import com.particles.authservice.tos.UserJwt;
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final String AUTHORIZATION_HEADER_PREFIX = "Authorization";
private static final String AUTHORIZATION_HEADER_BEARER_PREFIX = "Bearer ";
private static final int AUTHORIZATION_HEADER_BEARER_PREFIX_LENGTH = AUTHORIZATION_HEADER_BEARER_PREFIX.length();
#Autowired
private JwtService jwtService;
#Override
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain)
throws ServletException, IOException {
if (request.getHeader(AUTHORIZATION_HEADER_PREFIX) != null) {
final Optional<String> optToken = extractTokenFromRequest(request);
if (optToken.isPresent() && StringUtils.hasText(optToken.get()) && jwtService.isTokenValid(optToken.get())) {
// if token exists and is valid, retrieve corresponding UserJwt-object
final UserJwt jwt = jwtService.getJwtFromToken(optToken.get());
final UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(jwt.getUser(), null,
jwt.getUser().getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
filterChain.doFilter(request, response);
}
/**
* This method extracts a JWT from a {#link HttpServletRequest}-object.
*
* #param request
* ({#link HttpServletRequest}) request, which supposedly contains a JWT
* #return (Optional<String>) JWT as String
*/
private Optional<String> extractTokenFromRequest(final HttpServletRequest request) {
final String bearerToken = request.getHeader(AUTHORIZATION_HEADER_PREFIX);
String bearerTokenContent = null;
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(AUTHORIZATION_HEADER_BEARER_PREFIX)) {
bearerTokenContent = bearerToken.substring(AUTHORIZATION_HEADER_BEARER_PREFIX_LENGTH, bearerToken.length());
}
return Optional.ofNullable(bearerTokenContent);
}
}
If you need to see any other classes, tell me and I will paste them here.
Add the below method to expose your public endpoints
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/public-api/**");
}
It does not seem possible to only apply the filter to specific endpoints using solely the WebSecurityConfigurerAdapter#configure method.
Instead I decided to separate the endpoints in private-API and all the other endpoints.
endpoints, which need the filter (in my case JwtAuthenticationFilter), are collected under /api/ and not defined individually, because chances are high someone forgets to add them to the configure-method
all the other endpoints have a different path than /api/
My configure-method goes like this:
#Override
protected void configure(final HttpSecurity http) throws Exception {
//#formatter:off
http
.cors()
.and()
.csrf()
.disable()
.headers()
.frameOptions()
.disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/",
"/favicon.ico",
"/**/*.png",
"/**/*.gif",
"/**/*.svg",
"/**/*.jpg",
"/**/*.html",
"/**/*.css",
"/**/*.js")
.permitAll()
.antMatchers("/h2-console/**").permitAll()
.antMatchers(HttpMethod.POST,
PUBLIC_API_PATH + "register",
PUBLIC_API_PATH + "login")
.permitAll()
.antMatchers(HttpMethod.GET,
PUBLIC_API_PATH + "confirm")
.permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.accessDeniedHandler(unauthorizedHandler)
.and()
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
;
//#formatter:on
In JwtAuthenticationFilter I check whether the request-path contains the private-API path /api/. I only apply the filtering if it does.
#Override
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain)
throws ServletException, IOException {
if (request.getRequestURI().contains(SecurityConfiguration.PRIVATE_API_PATH)) {
// perform Jwt-authentication since request-URI suggests a call to private-API
...
}
}
filterChain.doFilter(request, response);
}
I do not like this solution, especially because I have to keep SecurityConfiguration.PRIVATE_API_PATH a constant since #*Mapping(value) expects a constant. It gets the job done, though.
If you have a better suggestion, I am eager to try it out.
Edit
Apparently it is possible to use variables in #*Mapping(value) like so: #PostMapping(value = "${apipath}/user"). So I can make the path configurable after all - but the check in JwtAuthenticationFilter must remain nevertheless; I just do not have to use constants, but variables, which contain values from e.g. application.yaml.
I have set the root path as:-
server.contextPath=/myspringBootApp (in Application.propertes) file.
and changed the configuration file as:-
package com.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
public CustomAuthenticationEntryPoint unauthorizedHandler;
#Autowired
MyDaoAuthenticationProvider authProvider;
#Bean
public CustomAuthenticationTokenFilter authenticationTokenFilterBean() {
return new CustomAuthenticationTokenFilter();
}
#Autowired
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider.authProvider());
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.authorizeRequests()
// UI related urls
.antMatchers(
HttpMethod.GET,
"/",
"/myspringBootApp/login",
"/content/**",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/assets/**"
).permitAll()
//Back end - auth layer
.antMatchers("/auth/user").permitAll()
//Back end - actual rest layer
.antMatchers(HttpMethod.POST,"/auth/login").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler);
httpSecurity.addFilterBefore(authenticationTokenFilterBean(),UsernamePasswordAuthenticationFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
The above code is not working and loading the UI. I tried changing the UI URLs to /myspringBootApp/favicon.ico, but this also dint give desired result.
Can anyone help me to find a solution?
I think you can use the WebSecurity part of the WebSecurityConfigurerAdapter for this:
#Override
public void configure(final WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/")
.antMatchers("/favicon.ico")
.antMatchers("/**.css")
.antMatchers("/webjars/**")
...
Is there a way to set up basic authentication and form login for the same REST service? I'd like to let logged in user trigger this service both through web browser after loggin in and from command line running curl -u username:password hostname.com/api/process
Now I've seen this post: Basic and form based authentication with Spring security Javaconfig
but it's slightly different from what I'm trying to do.
Is there a way to set this up with spring?
What I have now is this:
package com.my.company.my.app.security;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import javax.sql.DataSource;
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/js/**", "/css/**")
.permitAll();
http
.authorizeRequests()
.antMatchers("/api/**")
.authenticated()
.and()
.httpBasic();
http
.authorizeRequests()
.antMatchers("/","/index")
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/j_spring_security_check")
.defaultSuccessUrl("/monitor")
.failureUrl("/login?error")
.usernameParameter("j_username")
.passwordParameter("j_password")
.permitAll()
.and()
.logout()
.logoutUrl("/j_spring_security_logout")
.logoutSuccessUrl("/login?logout")
.permitAll()
.and()
.csrf()
.disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.passwordEncoder(passwordEncoder())
.usersByUsernameQuery("SELECT username, password, enabled FROM users WHERE username=?")
.authoritiesByUsernameQuery("SELECT username, authority FROM authorities WHERE username=?");
}
#Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
}
The only problem is that it doesn't redirect to my login page when hostname.com/index or hostname.com/ is called instead window pop ups asking for basic authentication credentials.
You can achieve this easily by using multiple http configuration as below, this code only explains multiple http configuration. I am assuming that you are well aware of the other essential configurations related to spring security e.g authenticationManger etc.
#EnableWebSecurity
public class MultiHttpSecurityCustomConfig {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER").and().withUser("admin").password("password")
.roles("USER", "ADMIN");
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**").authorizeRequests().anyRequest().hasRole("ADMIN").and().httpBasic();
}
}
#Configuration
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().formLogin();
}
}
}
Please refer spring security official link: Multiple HttpSecurity
I will also reccomend you to check out Secure REST Services with Spring Security
Feel free to comment if you encounter any problem!
One might try with the only ConfigurationAdapter class rather than two, e.g.:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf()
.disable()
.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/login/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
;
}
Ref.: https://medium.com/#haytambenayed/basic-authentication-and-form-based-authentication-using-spring-security-ed79951dbb2e
I found out that the previous code snippet is not working in Spring Security 5 because of an issue in the CSRF filter in the Basic authentication filter chain. It is possible to make it work by disabling CSRF for Basic auth.
BTW the override of Basic auth by Form auth is because redirection to /error page which is caused by this CSRF filter issue.
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**")
.authorizeRequests()
.anyRequest()
.hasRole("ADMIN")
.and()
.httpBasic()
.csrf().disable();
}
}