Reputation: 9205
How can I define my custom UserDetailsService
bean in a way that enables my spring mvc web app to use my underlying MySQL database to check authentication for users and passwords?
Here are the specifics:
I am adding security to the spring petclinic
sample as a way of learning about spring security. I am using Java configuration, and have set up a SecurityConfig.java
file which extends WebSecurityConfigurerAdapter
. I am trying to set up JdbcAuthentication
in a way that utilizes a MySQL database managed by the ClinicService
tools that are built into the petclinic sample. I therefore created a CustomUserDetailsService
class which extends UserDetailsService
, and which is intented to link the SecurityConfig.java
with ClinicService.java
. I created a User
class and a Role
class to model the users
and roles
tables in the MySQL database, respectively.
I then added the following line to business-config.xml
to define the CustomUserDetailService
:
<bean class="org.springframework.samples.petclinic.service.CustomUserDetailsService"></bean>
But yet I am still getting the following error stating that the bean for CustomUserDetailService
has not been defined:
Caused by: java.lang.IllegalArgumentException: Can not set
org.springframework.samples.petclinic.service.CustomUserDetailsService field
org.springframework.security.samples.petclinic.config.SecurityConfig.myCustomUserDetailsService
to $Proxy61
To keep this posting concise, I have loaded the relevant backup materials to a file sharing site. You can read all the source code and the complete stack trace by clicking on the following links:
You can read SecurityConfig.java
by clicking on this link.
The code for business-config.xml
is at this link.
The code for CustomUserDetailService.java
is at this link.
The code for the User
entity is at this link.
The code for the Role
entity is at this link.
The complete stack trace can be read at this link.
All of the other code for the application can be found at the github page for the Spring petclinic sample, which you can read by clicking on this link.
Here is a link to the code for login.jsp.
Here is a link to my revised business-config.xml code.
Upvotes: 0
Views: 4996
Reputation: 5512
To summarize comments, here's the answer.
There are several things wrong here:
1) When mixing XML configuration and Java configuration in Spring in a way that java config is imported inside xml config file, <context:annotation-config/>
needs to be present in xml file and java config class needs to be declared as a bean. <context:annotation-config/>
will enable annotation processing of the declared bean and @Configuration annotation will then be processed. Read more at the documentation: http://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/beans.html#beans-java-combining
To fix te problem insert <context:annotation-config/>
in business-config.xml. <context:annotation-config/>
is needed for <bean class="org.springframework.security.samples.petclinic.config.SecurityConfig"></bean>
to work so they need to be declared in the same bean profile.
2) You are autowiring a concrete class (CustomUserDetailsSerivce) in SpringConfig instead of an interface (UserDetailsService). While it's possible to use Spring to autowire concrete classes usually it's better to autowire to interfaces (Spring will autowire concreate CustomUserDetailsSerivce implementation to @Autowired UserDetailsService field). Spring creates proxies around wired classes to enable certain features (such as declarative transactions) and such proxies may easily implement an interface when autowiring but may fail if attempted to be autowired to a concrete class. It is possible though to achieve it - more information here: Spring Autowiring class vs. interface? In this case, it's definitely better to autowire to UserDetailsService interface since that is what our security config actually depends on.
To fix this issue specify field type as UserDetailsService in SpringConfig:
//Use UseDetailsService interface as field type instead of concrete class CustomUserDao
@Autowired
private UserDetailsService myCustomUserDetailsService;
3) You seem to be setting up both jdbc authentication and authentication using a custom user details service. Spring JDBC authentication is usually used if you want Spring Security to use jdbc to query a database and find about existing users and their roles etc... Custom UserDetailsSerivce is used if you want implement querying for users/roles etc... your self. In your example (since you are providing your custom UserDetailsService which will query the backend using ClinicService) you don't need JDBC authentication.
Here's an example of working spring security configuration that uses custom UserDetailsService (implemented elsewhere and autowired by spring) via java config:
@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/petclinic/")
.usernameParameter("j_username") // default is username
.passwordParameter("j_password") // default is password
.loginProcessingUrl("/j_spring_security_check") // default is /login with an HTTP post
.failureUrl("/login")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/index.jsp")
.and()
.authorizeRequests()
.antMatchers("/**").hasRole("ROLE_ADMIN")
.antMatchers("/j_spring_security_check").permitAll()
.and()
.userDetailsService(userDetailsService);
}
}
I suggest reading the actual documentation as it describes what specific configuration builder methods do and provide the examples: http://docs.spring.io/spring-security/site/docs/3.2.0.RC2/apidocs/org/springframework/security/config/annotation/web/builders/HttpSecurity.html#formLogin()
EDIT 1 - added login form configuration and link to documentation
EDIT 2 - added more explanations for problem 1)
EDIT 3 - changed role name form "ADMIN" to "ROLE_ADMiN" to match role names in UserDetailsService
Upvotes: 4