Is it possible to configure Spring security in a way that it reads configuration details from an external file and configures accordingly ?
(I am not talking about changing config at runtime, I am talking about reading from a file at the time of startup).
An example of my existing Spring security config :
#EnableWebSecurity
#Configuration
public class SecurityConfig {
#Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password("userPass").roles("USER").build());
manager.createUser(User.withUsername("admin").password("adminPass").roles("ADMIN").build());
return manager;
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("user").password("user").roles("USER");
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN");
}
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/v1/**")
.authorizeRequests()
.antMatchers("/api/v1/**").authenticated()
.and()
.httpBasic();
}
}
#Configuration
#Order(2)
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("user1").password("user").roles("USER");
auth.inMemoryAuthentication().withUser("admin1").password("admin").roles("ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/test/**")
.authorizeRequests()
.antMatchers("/api/test/**").authenticated()
.and()
.formLogin();
}
}
}
As you can see, I am using multiple configurations (have a look at Order() annotation). What I want to be able to do is decide at the time of startup, the number and types of configuration. For example a first client may want to have 2 configs (e.g.LdapConfig and SamlConfig), a second one may want LdapConfig and SqlConfig and a third one may want 4-5 configs. Is it possible to do that?
NOTE: I am not using Spring Boot
EDIT
Summary of why I want in this way :
By customer I mean the company that will be buying my product. And by users I mean the actual end users of the company that bought my product. So I shipped the product to 3 companies. First will configure it to have ldap auth flow and google-oauth2 auth flow. Users of this first company will be seeing a login page with these 2 options. Company 2 now might have a ldap auth flow and saml auth flow and users of that company will be seeing those 2 options. And the company is selecting the available options before startup.
You could load properties, e.g. DB credentials, before creating your WebApplicationContext. Look at the following example:
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
// Tell the EnvironmentManager to load the properties. The path to the config
// file is set by Tomcat's home variable. If you change the container you might
// need to change this, too.
EnvironmentParamManager.initialize(System.getProperty("catalina.home"));
// now create the Spring Context
AnnotationConfigWebApplicationContext rootContext =
new AnnotationConfigWebApplicationContext();
rootContext.register(RootConfig.class);
rootContext.setServletContext(servletContext);
SpringApplicationContextProvider.configure(rootContext);
// ... other config
}
The EnvironmentParamManager could look like this. I've decided to make it static so that the properties are accessible from everywhere even in non-Spring parts of the application.
public class EnvironmentParamManager {
private static Properties properties = new Properties();
public static void initialize(String pathToConfigFile) {
BufferedInputStream stream;
try {
stream = new BufferedInputStream(new FileInputStream(
pathToConfigFile + "myconfig.props"));
properties.load(stream);
stream.close();
} catch (Throwable e) {
throw new Error("Cannot read environment settings from file " + pathToConfigFile);
}
}
public static String getMongoDBHostname() {
return properties.getProperty("mongodb.username");
}
}
When using JavaConfig, you can access your config properties at the Bean creation phase easily like this
#Configuration
public class CoreConfig {
#Bean
public MongoDbFactory mongoDbFactory() throws Exception {
...
ServerAddress address = new
ServerAddress(EnvironmentParamManager.getMongoDBHost(),
EnvironmentParamManager.getMongoDBPort());
...
}
Of course, you are free to connect to any other services like LDAP etc. in just the same way as you load the local properties file before the Spring Context is bootstrapped. Hope that helps.
Selective loading of components can be achived with Springs #Conditional annotation.
The configs would look like this:
#Configuration(value = "some.security.config")
#Conditional(value = LoadSecurityConfigCondition.class)
public class SomeSecurityConfig {
// some code
}
#Configuration(value = "other.security.config")
#Conditional(value = LoadSecurityConfigCondition.class)
public class OtherSecurityConfig {
// other code
}
Then, the LoadSecurityConfigCondition.class decides if the components are loaded:
#Component
public class LoadSecurityConfigCondition implements Condition {
#Override
public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
boolean enabled = false;
if (metadata.isAnnotated(Configuration.class.getName())) {
final String name = (String) metadata.getAnnotationAttributes(Configuration.class.getName()).get("value");
if (StringUtils.isNotBlank(name)) {
/* Here you may load your config file and
* retrieve the information on wether to load
* the config identified by its name.
*/
enabled = ...;
}
}
return enabled;
}
}
In this example, the config entries can now be created with the #Configuration name, postfixed with .enabled to clarify its purpose:
some.security.config.enabled=true
other.security.config.enabled=false
Have you tried this:
#EnableWebSecurity
#Configuration
public class SecurityConfig {
#Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new MemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password("userPass").roles("USER").build());
manager.createUser(User.withUsername("admin").password("adminPass").roles("ADMIN").build());
return manager;
}
#Configuration
#Profile({"profile1", "profile2"})
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("user").password("user").roles("USER");
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN");
}
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/v1/**")
.authorizeRequests()
.antMatchers("/api/v1/**").authenticated()
.and()
.httpBasic();
}
}
#Configuration
#Profile("profile1")
#Order(2)
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("user1").password("user").roles("USER");
auth.inMemoryAuthentication().withUser("admin1").password("admin").roles("ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/test/**")
.authorizeRequests()
.antMatchers("/api/test/**").authenticated()
.and()
.formLogin();
}
}
}
So with spring.profiles.active=profile1, both configurations are loaded, with spring.profiles.active=profile2, only the first configuration is loaded. Of course, you can use more than 2 profiles, and you can also activate more than one profile at startup (also comma separated). You just need to divide your configurations and profiles in a way that fits your requirements.
Related
I want to use the HTTPSessionIdResolver for everything located under "/api**" and for everything else the standard CookieResolver.
How is this possible, so that the two configurations use different resolvers? With my current approach everything uses X-AUTH.
I tried to understand the implementation within Spring and I end up in the SessionRepositoryFilter, but of this filter only one instance is created, so der exists only one resolver.
#EnableWebSecurity
public class TestConfig {
#EnableSpringHttpSession
#Configuration
#Order(1)
public static class Abc extends WebSecurityConfigurerAdapter {
#Bean
#Primary
public HeaderHttpSessionIdResolver xAuth() {
return HeaderHttpSessionIdResolver.xAuthToken();
}
#Bean
#Primary
public MapSessionRepository mapSessionRepository(){
return new MapSessionRepository(new HashMap<>());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/service/json/**")
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.csrf()
.disable();
}
}
#EnableSpringHttpSession
#Configuration
#Order(2)
public static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#ConfigurationProperties(prefix = "spring.datasource")
#Bean
#Primary
public DataSource dataSource() {
return DataSourceBuilder
.create()
.build();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/css/**", "/user/registration", "/webfonts/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
#Bean
public BCryptPasswordEncoder bcrypt() {
return new BCryptPasswordEncoder();
}
#Bean
public JdbcUserDetailsManager userDetailsManager() {
JdbcUserDetailsManager manager = new UserDetailsManager(dataSource());
manager.setUsersByUsernameQuery("select username,password,enabled from users where username=?");
manager.setAuthoritiesByUsernameQuery("select username,authority from authorities where username = ?");
return manager;
}
#Autowired
public void initialize(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsManager()).passwordEncoder(bcrypt());
}
}
}
I could move the logic into one resolver which delegates the work to the existing resolvers, but this seems hacky?
public class SmartHttpSessionIdResolver implements HttpSessionIdResolver {
private static final String HEADER_X_AUTH_TOKEN = "X-Auth-Token";
private static final CookieHttpSessionIdResolver cookie = new CookieHttpSessionIdResolver();
private static final HeaderHttpSessionIdResolver xauth = HeaderHttpSessionIdResolver.xAuthToken();
#Override
public List<String> resolveSessionIds(HttpServletRequest request) {
if (isXAuth(request)) {
return xauth.resolveSessionIds(request);
}
return cookie.resolveSessionIds(request);
}
#Override
public void setSessionId(HttpServletRequest request, HttpServletResponse response, String sessionId) {
if (isXAuth(request)) {
xauth.setSessionId(request, response, sessionId);
} else {
cookie.setSessionId(request, response, sessionId);
}
}
#Override
public void expireSession(HttpServletRequest request, HttpServletResponse response) {
if (isXAuth(request)) {
xauth.expireSession(request, response);
} else {
cookie.expireSession(request, response);
}
}
private boolean isXAuth(HttpServletRequest request) {
return request.getHeader(HEADER_X_AUTH_TOKEN) != null;
}
}
As you mention above, based on the code of "SessionRepositoryFilter" class, it is clear that it supports only a single "HttpSessionIdResolver". As a result, if you use only one "SessionRepositoryFilter", your only option seems to be the one that you suggested. Although this would work, it does feel a bit hacky and also, if your requirement is indeed to use the "HTTPSessionIdResolver" for everything located under "/api**", then it doesn't ensure that.
Since "SessionRepositoryFilter" class is effectivelly a Filter, I suggest checking if you could create two Spring beans for the "SessionRepositoryFilter" class. One that will be used for all HTTP endpoints under the "/api*" pattern and another for all other paths. Then, you could use the "HttpSessionIdResolver" and "CookieSessionIdResolver" respectivelly. You can find an example for defining different filters based on URL patterns here.
After attempting the solution provided in the question (which works fine, to be honest), I also attempted to do this by providing two different filters. However, when #EnableSpringHttpSession is added, a SessionRepositoryFilter is added automatically and adding two more of those in the servlet filter chain seems odd. Therefore, I thought they would have to go in the security filter chain instead, which is good because then we can use the URL matching made there as well (instead of having to implement that elsewhere as well).
Since other security filters use the HttpSession, we have to manually place the SessionRepositoryFilter first in this chain. This is what I came up with (in Kotlin) which works well for me:
#EnableWebSecurity
class SecurityConfig() {
private val sessionStore = ConcurrentHashMap<String, Session>()
private val sessionRepo = MapSessionRepository(sessionStore)
#Configuration
#Order(1)
inner class XAuthConfig(): WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http
.requestMatchers()
.antMatchers("/api**")
.and()
.addFilterBefore(
SessionRepositoryFilter(sessionRepo).apply{
setHttpSessionIdResolver(
HeaderHttpSessionIdResolver.xAuthToken();
)
}, WebAsyncManagerIntegrationFilter::class.java)
}
}
#Configuration
#Order(2)
inner class DefaultConfig(): WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http
.addFilterBefore(
SessionRepositoryFilter(sessionRepo).apply{
setHttpSessionIdResolver(
CookieHttpSessionIdResolver()
)
}, WebAsyncManagerIntegrationFilter::class.java)
}
}
}
}
Note that the annotation #EnableSpringHttpSession has been removed. Instead, we add the SessionRepositoryFilters manually before the WebAsyncManagerIntegrationFilters (the first filter in the security filter chain). The function of the SessionRepositoryFilter is to replace the existing HttpSession with Spring's HttpSession which it will do no matter if we place it manually or if it's put in place automatically by means of autoconfiguration. As long as no other filter before the security filter chain makes use of the session, this should work out. Otherwise, some filter rearrangement might still do the trick.
I made it to work by creating a subclass of RedisHttpSessionConfiguration. Then you will have available a filter called springHeaderSessionRepositoryFilter which you can use to configure another filter with a different filter-mapping in your web app.
public class MyCustomRedisHttpSessionConfiguration extends RedisHttpSessionConfiguration {
#Bean
public <S extends Session> SessionRepositoryFilter<? extends Session> springHeaderSessionRepositoryFilter(SessionRepository<S> sessionRepository) {
SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<>(sessionRepository);
sessionRepositoryFilter.setHttpSessionIdResolver(HeaderHttpSessionIdResolver.xAuthToken());
return sessionRepositoryFilter;
}
}
I am implementing a REST API using Spring Boot (2.0.1) to work with MongoDB (3.6). I'm really stuck. I've also tried other tips from StackOverFlow but it didn't help for some reason.
I have configured the SecurityConfig.java to permit the access to certain areas and also created a User inMemoryAuthentication, to be able to login to HAL Browser (Spring) and etc. But the problem is, that whatever address I put in browser I get a Login form and the credentials used in the inMemoryAuthentication is always wrong for some reason. The only way I've found to access the API is by excluding SecurityAutoConfiguration in the main class. But this opens up every permission to access everything including HAL Browser without authentication.
Would someone show me what I am doing wrong? I want to permit only certain paths/addresses to everyone, permit everything else only to use with TokenAuthentication (have already a custom implementation of it) and have one user (username, password) to access HAL Browser.
Here is my SecurityConfig.java:
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Autowired
private final TokenAuthenticationService tokenAuthenticationService;
#Autowired
protected SecurityConfig(final TokenAuthenticationService tokenAuthenticationService) {
super();
this.tokenAuthenticationService = tokenAuthenticationService;
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.csrf().disable()
.addFilterBefore(new TokenAuthenticationFilter(tokenAuthenticationService), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/hello").permitAll()
.antMatchers("/test2").permitAll()
.antMatchers("/register").permitAll()
.antMatchers("/useraccount").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().permitAll()
.and()
.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).permitAll();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// auth
// .inMemoryAuthentication()
// .withUser("user1").password("password").roles("ADMIN");
auth
.inMemoryAuthentication()
.withUser(User.withDefaultPasswordEncoder().username("user").password("password").roles("USER"));
// auth
// .userDetailsService(userService);
}
// #Bean
// #Override
// public UserDetailsService userDetailsService() {
// UserDetails user =
// User.withDefaultPasswordEncoder()
// .username("user")
// .password("password")
// .roles("USER")
// .build();
//
// return new InMemoryUserDetailsManager(user);
// }
// #Bean
// public UserDetailsService userDetailsService() {
// InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
// manager.createUser(User.withUsername("user").password("pass").roles("USER", "ADMIN").build());
// return manager;
// }
}
I've tried different approaches as you see (commented blocks) but still no luck.
Even though I have permitAll() on /register, i still get the auto generated login form, which won't accept any credentials.
So as i've said earlier the only way to use my API is to exclude the SecurityAutoConfiguration (#EnableAutoConfiguration(exclude = SecurityAutoConfiguration.class) but it is not a secure option.
Is there any way to resolve this?
From what I can see, it's likely that your SecurityConfig class never gets called, as it doesn't have any annotation indicating to Spring Boot that it should look for beans to autowire in the class (#Autowired)
To give you an idea, the following will never be called:
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
System.out.println("This will never called");
}
}
Whereas, if we had #EnableWebSecurity:
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
System.out.println("This is called");
}
}
Keep in mind that Spring Boot will not detect annotations inside a class if the class itself is not annotated with #Component or with another annotation that inherits the #Component annotation (such as #Configuration, #Service, ...)
EDIT: I quickly put together a program to imitate your situation:
SecurityConfiguration.java:
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser(new User("root", "root", Arrays.asList(new SimpleGrantedAuthority("USER"))))
.passwordEncoder(fakePasswordEncoder());
}
#Bean
public PasswordEncoder fakePasswordEncoder() {
return new PasswordEncoder() {
#Override
public String encode(CharSequence charSequence) {
return null; // matches(...) will always return true anyways
}
#Override
public boolean matches(CharSequence charSequence, String s) {
return true;
}
};
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/hello").permitAll()
.antMatchers("/test2").permitAll()
.antMatchers("/register").permitAll()
.antMatchers("/useraccount").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().permitAll()
.and()
.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).permitAll();
}
}
Note that I just quickly made a password encoder that ignores the password because that would require more work
ExampleController.java:
#RestController
public class ExampleController {
#GetMapping("/")
public Object index() {
return getCurrentUser();
}
public Object getCurrentUser() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return ((UsernamePasswordAuthenticationToken)auth).getPrincipal();
}
}
And when I login with the username root and the any password (remember, the fake password encoder doesn't care about the password), it redirects me to / and displays the following:
{"password":null,"username":"root","authorities":[{"authority":"USER"}],"accountNonExpired":true,"accountNonLocked":true,"credentialsNonExpired":true,"enabled":true}
(which is normal because that's what I'm making it output)
I am trying to add Spring security to my project. I have custom login logic - advanced LDAP with custom encoding
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
//TODO
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
//TODO
}
private boolean login(String login, String pass) {
// custom login logic....
return loginHandler.login(login, pass);
}
}
Is there way, how to add login() method into configure method ?
It's just Spring Security configuration class. It shouldn't have a login() method in it, it should store the configuration only.
Probably you would like to have something like this: https://spring.io/guides/gs/authenticating-ldap/ - WebSecurityConfig should be good example.
Just like in title, I want that only users of spec. Here is my authentication code:
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication().userSearchFilter("(sAMAccountName={0})")
.contextSource(contextSource());
}
I found that there are functions like groupSearchFilter and groupSearchBase or groupRoleAttribute but I have no idea how to use them
I made some modifications on Megha's solution
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Configuration
protected static class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter {
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource("ldap://ip:port/DC=xxxx,DC=yyyy");
contextSource.setUserDn("user_service_account");
contextSource.setPassword("password_user_service_account");
contextSource.setReferral("follow");
contextSource.afterPropertiesSet();
LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder> ldapAuthenticationProviderConfigurer = auth.ldapAuthentication();
ldapAuthenticationProviderConfigurer
.userSearchBase("OU=Users,OU=Servers")
.userSearchFilter("(&(cn={0})(memberOf=CN=GROUP_NAME,OU=Groups,OU=Servers,DC=xxxx,DC=yyyy))")
.contextSource(contextSource);
}
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").authenticated().and()
.httpBasic();
}
}
"(sAMAccountName={0})"
should be replaced with following
"(&(objectCategory=Person)(sAMAccountName=*)(memberOf=cn=entergroup,ou=users,dc=company,dc=com))"
where cn, ou,dc are the specifications of the group in directory
It depends on how your group membership is set up. Something like the following might work, replacing your group dn and objectclasses as necessary:
groupSearchBase("cn=yourgroup,ou=groups")
groupSearchFilter("(uniqueMember={0})")
I am having some issues getting my application set up using method level annotation controlled by #EnableGlobalMethodSecurity I am using Servlet 3.0 style initialization using
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
public SecurityWebApplicationInitializer() {
super(MultiSecurityConfig.class);
}
}
I have attempted 2 different ways of initialising an AuthenticationManager both with their own issues. Please note that not using #EnableGlobalMethodSecurity results in a successful server start up and all of the form security executes as expected. My issues arise when I add #EnableGlobalMethodSecurity and #PreAuthorize("hasRole('ROLE_USER')") annotations on my controller.
I am attempting to set up form-based and api-based security independently. The method based annotations need only work for the api security.
One configuration was the following.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class MultiSecurityConfig {
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**").httpBasic();
}
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
}
#Configuration
public static class FormWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/static/**","/status");
}
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().hasRole("USER").and()
.formLogin().loginPage("/login").permitAll();
}
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
}
}
This is not ideal as I really want only a single registration of the authentication mechanism but the main issue is that it results in the following exception:
java.lang.IllegalArgumentException: Expecting to only find a single bean for type interface org.springframework.security.authentication.AuthenticationManager, but found []
As far as I am aware #EnableGlobalMethodSecurity sets up its own AuthenticationManager so I'm not sure what the problem is here.
The second configuration is as follows.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class MultiSecurityConfig {
#Bean
protected AuthenticationManager authenticationManager() throws Exception {
return new AuthenticationManagerBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR)
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN").and()
.and()
.build();
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**").httpBasic();
}
}
#Configuration
public static class FormWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/static/**","/status");
}
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().hasRole("USER").and()
.formLogin().loginPage("/login").permitAll();
}
}
}
This config actually starts successfully but with an exception
java.lang.IllegalArgumentException: A parent AuthenticationManager or a list of AuthenticationProviders is required
at org.springframework.security.authentication.ProviderManager.checkState(ProviderManager.java:117)
at org.springframework.security.authentication.ProviderManager.<init>(ProviderManager.java:106)
at org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder.performBuild(AuthenticationManagerBuilder.java:221)
and when I test I found that the security doesn't work.
I've been looking at this for a couple of days now and even after diving into spring security implementation code I can't seem to find what is wrong with my configuration.
I am using spring-security-3.2.0.RC1 and spring-framework-3.2.3.RELEASE.
When you use the protected registerAuthentication methods on WebSecurityConfigurerAdapter it is scoping the Authentication to that WebSecurityConfigurerAdapter so EnableGlobalMethodSecurity cannot find it. If you think about this...it makes sense since the method is protected.
The error you are seeing is actually a debug statement (note the level is DEBUG). The reason is that Spring Security will try a few different ways to automatically wire the Global Method Security. Specifically EnableGlobalMethodSecurity will try the following ways to try and get the AuthenticationManager:
If you extend GlobalMethodSecurityConfiguration and override the registerAuthentication it will use the AuthenticationManagerBuilder that was passed in. This allows for isolating the AuthenticationManager in the same way you can do so with WebSecurityConfigurerAdapter
Try to build from the global shared instance of AuthenticationManagerBuilder, if it fails it logs the error message you are seeing (Note the logs also state "This is ok for now, we will try using an AuthenticationManager directly")
Try to use an AuthenticationManager that is exposed as a bean.
For your code, you are going to be best off using something like the following:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class MultiSecurityConfig {
// Since MultiSecurityConfig does not extend GlobalMethodSecurityConfiguration and
// define an AuthenticationManager, it will try using the globally defined
// AuthenticationManagerBuilder to create one
// The #Enable*Security annotations create a global AuthenticationManagerBuilder
// that can optionally be used for creating an AuthenticationManager that is shared
// The key to using it is to use the #Autowired annotation
#Autowired
public void registerSharedAuthentication(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 {
// Since we didn't specify an AuthenticationManager for this class,
// the global instance is used
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
.httpBasic();
}
}
#Configuration
public static class FormWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
// Since we didn't specify an AuthenticationManager for this class,
// the global instance is used
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/static/**","/status");
}
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().hasRole("USER")
.and()
.formLogin()
.loginPage("/login")
.permitAll();
}
}
}
NOTE: More documentation around this will be getting added to the reference in the coming days.