I am trying to achieve different authentications based on certain http requests, but it appears no matter what the requests are they use the wrong authentications. More specifically, for any manage endpoint, I want to use only authentication A and for any internal or api endpoints I want to use only authentication B.
For example, with the current code, if I send an invalid authentication/user for manage, it will first try to use Authenication A, then fails, and then tries to use authentication B (which is should not).
For manage:
#Configuration
#Order(1)
#EnableWebSecurity
public class SecurityConfigA extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers("/manage/**")
.authenticated().anyRequest().permitAll().and().httpBasic();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(new AuthenticationProvider() {
#Override
public boolean supports(Class<?> arg0) {
return true;
}
#Override
public Authentication authenticate(Authentication auth) {
//do authentication A
}
});
}
}
For api and internal:
#Order(2)
#EnableWebSecurity
public class SecurityConfigB extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers(
"/internal/**",
"/api/**")
.authenticated().anyRequest().permitAll().and().httpBasic();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(new AuthenticationProvider() {
#Override
public boolean supports(Class<?> arg0) {
return true;
}
#Override
public Authentication authenticate(Authentication auth) {
//do authentication B
}
});
}
}
Related
I need to configure the Spring Security so that users that have either Basic Auth or API Key can access the API in the absence of one other. The below code works for Basic Auth but when I switch to API Key in Postman, I can not access the API using the correct API key/value added to the Header in Postman in the Authorization tab and I am getting 401.
However, when I just use ApiKeyWebSecurityConfigurerAdapter by itself, I can access the API even with the wrong API key/value pair. Any help would be appreciated.
Note: I used this and https://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/html5/#multiple-httpsecurity as reference.
Security Config:
#EnableWebSecurity
public class MultiHttpSecurityConfig {
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.userDetailsService(userDetailsService())
.httpBasic(Customizer.withDefaults());
}
#Bean
public UserDetailsService userDetailsService() {
return new JpaUserDetailService();
}
#Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
#Configuration
public static class ApiKeyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Value("${myApp.http.auth-token-header-name}")
private String principalRequestHeader;
#Value("${myApp.http.auth-token}")
private String principalRequestValue;
#Override
protected void configure(HttpSecurity http) {
http
.csrf()
.disable();
APIKeyAuthFilter filter = new APIKeyAuthFilter(principalRequestHeader);
filter.setAuthenticationManager(new AuthenticationManager() {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String principal = (String) authentication.getPrincipal();
if (!principalRequestValue.equals(principal)) {
throw new BadCredentialsException("The API key was not found or not the expected value.");
}
authentication.setAuthenticated(true);
return authentication;
}
});
}
}
}
Filter:
public class APIKeyAuthFilter extends AbstractPreAuthenticatedProcessingFilter {
private String principalRequestHeader;
public APIKeyAuthFilter(String principalRequestHeader) {
this.principalRequestHeader = principalRequestHeader;
}
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
return request.getHeader(principalRequestHeader);
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return "N/A";
}
}
In WebSecurityConfigurerAdapter class we have protected void configure(HttpSecurity http) method where we add restrictions to request. What i want is:
1.Retrieve a user who wants to enter
2.Check if that user is present in db
3.Check if that user has a special propery, for example.: login.endsWith('_login')
All you need to implement Custom AuthenticationProvider. Do the authentication code by other server as you need.
#Component
public class CustomAuthenticationProvider
implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
if (shouldAuthenticateAgainstThirdPartySystem()) {
// use the credentials
// and authenticate against the third-party system
return new UsernamePasswordAuthenticationToken(
name, password, new ArrayList<>());
} else {
return null;
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(
UsernamePasswordAuthenticationToken.class);
}
}
And register your Authentication provider.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider authProvider;
#Override
protected void configure(
AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated()
.and()
.httpBasic();
}
}
Note : I am assuming you're using HTTP Basic Authentication, you can to
change it to FormLogin or as per your need.
How can I define a #RequestMapping method to explicit allow anonymous (unauthorized) access?
The following does not work, always getting 401 Unauthorized:
#RequestMapping("/test")
#Secured(value={"ROLE_ANONYMOUS"})
public String test() {
return "OK";
}
In general the whole application is secured as follows, using spring-boot:
security.basic.enabled=true.
#Configuration
public class AuthConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
}
You can override configure(HttpSecurity httpSecurity) method and define your rules there:
#Configuration
public class AuthConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception
{
httpSecurity.authorizeRequests()
.antMatchers("/test")
.permitAll();
super.configure(http);
}
}
#Configuration
public class AuthConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception
{
httpSecurity.authorizeRequests().regexMatcher("^((?!test).)*$").permitAll();
}
}
I am not sure about a slash before test but just try this negative look-around method.
I want to replace the default access denied page:
With my custom page and my approach was this:
#Configuration
#EnableWebSecurity
public class SecurityContextConfigurer extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().maximumSessions(1)
.sessionRegistry(sessionRegistry()).expiredUrl("/");
http.authorizeRequests().antMatchers("/").permitAll()
.antMatchers("/register").permitAll()
.antMatchers("/security/checkpoint/for/admin/**").hasRole("ADMIN")
.antMatchers("/rest/users/**").hasRole("ADMIN").anyRequest()
.authenticated().and().formLogin().loginPage("/")
.defaultSuccessUrl("/welcome").permitAll().and().logout()
.logoutUrl("/logout");
}
#Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
#Bean
public AuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
return daoAuthenticationProvider;
}
#Bean
public ProviderManager providerManager() {
List<AuthenticationProvider> arg0 = new CopyOnWriteArrayList<AuthenticationProvider>();
arg0.add(daoAuthenticationProvider());
return new ProviderManager(arg0);
}
#Bean(name = "myAuthenticationManagerBean")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return providerManager();
}
#Bean
public ExceptionTranslationFilter exceptionTranslationFilter() {
ExceptionTranslationFilter exceptionTranslationFilter =
new ExceptionTranslationFilter(new CustomAuthenticationEntryPoint());
exceptionTranslationFilter.setAccessDeniedHandler(accessDeniedHandler());
return exceptionTranslationFilter;
}
#Bean
public AccessDeniedHandlerImpl accessDeniedHandler() {
AccessDeniedHandlerImpl accessDeniedHandlerImpl = new
AccessDeniedHandlerImpl();
accessDeniedHandlerImpl.setErrorPage("/page_403.jsp");
System.out.println("ACCESS DENIED IS CALLED......");
return accessDeniedHandlerImpl;
}
private class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint{
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authenticationException) throws IOException,
ServletException {
response.sendError(HttpServletResponse.SC_FORBIDDEN,
"Access denied.");
}
}
}
But with this config above I'm still not getting the job done and seeing the same
Are there more bean which must be injected for this purpose?
Disclaimer : this is not only solution, but a working one.
In this case my approach would be as simple as possible which is add this method in your SecurityContext
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().maximumSessions(1)
.sessionRegistry(sessionRegistry()).expiredUrl("/");
http.authorizeRequests().antMatchers("/").permitAll()
.antMatchers("/register").permitAll()
.antMatchers("/security/checkpoint/for/admin/**").hasRole("ADMIN")
.antMatchers("/rest/users/**").hasRole("ADMIN").anyRequest()
.authenticated().and().formLogin().loginPage("/")
.defaultSuccessUrl("/welcome").permitAll().and().logout()
.logoutUrl("/logout").and()
.exceptionHandling().accessDeniedPage("/page_403");//this is what you have to do here to get job done.
}
Reference: Custom 403 Page in Spring Security.
As #M. Deinum pointed out, you should tell Spring Security how to incorporate these beans. Anyway, there is a much simpler way for what you're trying to achieve:
#Configuration
#EnableWebSecurity
public class SecurityContextConfigurer extends WebSecurityConfigurerAdapter {
// Rest omitted
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// The usual stuff
.exceptionHandling()
.accessDeniedPage("/page_403.jsp")
.authenticationEntryPoint((request, response, authException) -> {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
});
}
}
I created a Spring Boot - Security project based on a example app, however the Rest Controller works fine, means, I can access the rest resource but the security does not seem to fire up at all, so when I added a breakpoint as stated below it does not break there. Not sure why.
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// #formatter:off
auth.inMemoryAuthentication() // breakpoint here
.withUser("roy")
.password("spring")
.roles("ADMIN");
// #formatter:on
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
Here is the complete project hosted and editable with Codio: http://bit.ly/1uFI0t5
You have to tell the framework which are the urls that it have to secure.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**").access("hasRole('ROLE_USER')")
.and().formLogin();
}
}