I am not very familiar with spring, so added everything related to spring.
I have a spring 4 struts 1.x application.
Spring context is loaded using struts plugin which points to spring-context.xml
<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
<set-property property="contextConfigLocation"
value="/WEB-INF/spring-context.xml"/>
</plug-in>
In spring-context.xml I have configured Jpa's EntityManager like so:
<context:component-scan base-package="com.epam.testsystem" />
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
<property name="persistenceUnitName" value="primary"/>
</bean>
<!-- Transactions -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
I also have Spring Security integrated using
public class SecurityWebApplicationInitializer
extends AbstractSecurityWebApplicationInitializer {
public SecurityWebApplicationInitializer() {
super(SecurityConfig.class);
}
}
and
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
#ImportResource("/WEB-INF/spring-context.xml")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/welcome.do").hasRole("ADMIN")
.and()
.formLogin()
.loginPage("/login.do")
.loginProcessingUrl("/login")
.failureUrl("/login?error=1")
.permitAll()
.and()
.csrf();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
AND IT WORKS WITHOUT ERRORS.
Now I decided to move the configuration of EntityManager to java Config. So I deleted bean definitions from spring-context.xml added those beans to SecurityConfig
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setPersistenceUnitName("primary");
return em;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
But now I get this Exception:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'entityManagerFactory': Requested bean is currently in creation: Is there an unresolvable circular reference?"
I think that happened because I use #PersistenceContext annotation in UserDaoImpl, before I imported it into Java Config.
So what is the most clean way to handle this?
Thanks
One of the proposed solution is
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setPersistenceUnitName("primary");
return em;
}
#Bean
public PlatformTransactionManager transactionManager(){
JpaTransactionManager transactionManager = new JpaTransactionManager();
// SOlution below
transactionManager.setEntityManagerFactory(entityManagerFactory());
return transactionManager;
}
Here we pass the dependent bean via entityManagerFactory()
Kindly let me know if this fulfills the solution
I made it work by creating a separate PersistenceConfig
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages="com.epam.testsystem.repository")
public class PersistenceConfig {
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setPersistenceUnitName("primary");
return em;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
}
and import Java Config to xml config (spring-context.xml):
<bean class="com.epam.testsystem.config.PersistenceConfig" />
Related
I have a simple-as-possible OAuth2 JWT Spring Boot 2.7.3 project that works perfectly. It's easy to setup and run as described in the README from Github repository.
Besides Spring Security, the project uses spring-security-oauth2:
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>
Then defines some beans:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
#Value("${jwt.secret}")
private String jwtSecret;
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();
tokenConverter.setSigningKey(jwtSecret);
return tokenConverter;
}
#Bean
public JwtTokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
And an Authorization Server:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Value("${security.oauth2.client.client-id}")
private String clientId;
#Value("${security.oauth2.client.client-secret}")
private String clientSecret;
#Value("${jwt.duration}")
private Integer jwtDuration;
#Autowired
private BCryptPasswordEncoder passwordEncoder;
#Autowired
private JwtAccessTokenConverter accessTokenConverter;
#Autowired
private JwtTokenStore tokenStore;
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(clientId)
.secret(passwordEncoder.encode(clientSecret))
.scopes("read", "write")
.authorizedGrantTypes("password")
.accessTokenValiditySeconds(jwtDuration);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.tokenStore(tokenStore)
.accessTokenConverter(accessTokenConverter);
}
}
And a Resource Server:
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Value("${cors.origins}")
private String corsOrigins;
#Autowired
private Environment env;
#Autowired
private JwtTokenStore tokenStore;
private static final String[] PUBLIC = { "/oauth/token", "/h2-console/**" };
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(tokenStore);
}
#Override
public void configure(HttpSecurity http) throws Exception {
// H2
if (Arrays.asList(env.getActiveProfiles()).contains("test")) {
http.headers().frameOptions().disable();
}
http.authorizeRequests()
.antMatchers(PUBLIC).permitAll()
.anyRequest().authenticated();
http.cors().configurationSource(corsConfigurationSource());
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
String[] origins = corsOrigins.split(",");
CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.setAllowedOriginPatterns(Arrays.asList(origins));
corsConfig.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "DELETE", "PATCH"));
corsConfig.setAllowCredentials(true);
corsConfig.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfig);
return source;
}
#Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
FilterRegistrationBean<CorsFilter> bean
= new FilterRegistrationBean<>(new CorsFilter(corsConfigurationSource()));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
}
Now I'm trying to upgrade it to Spring Boot 3.0.0-SNAPSHOT, but the project is throwing BeanCreationException: Error creating bean with name 'authorizationEndpoint'. The stack trace shows it's caused by ClassNotFoundException: javax.servlet.ServletException.
How to fix that?
The problem is that you are using an incompatible version of spring cloud (Hoxton.SR8). It is only compatible with spring boot 2.3.x and 2.2.x (see https://spring.io/blog/2020/08/28/spring-cloud-hoxton-sr8-has-been-released). You need to upgrade spring cloud as well.
(Spring boot 3 uses the new Jakarta APIs so jakarta.servlet.ServletException is only available and downloaded by maven after you upgraded Spring boot. Your incompatible spring cloud version expects the old javax.servlet.ServletException class, but is not there anymore. Therefore this ClassNotFoundException.)
The dependency:
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>
Has reached its End of Life some time ago. Therefore, it won't be compatible with Spring Boot 3. The project has been replaced by the OAuth2 support provided by Spring Security (client and resource server) and Spring Authorization Server.
When I first ran the application(converted to gradle), I got the exception java.lang.ClassNotFoundException: javax.servlet.Filter. It went away once I added the javax-servlet-api dependency. Can you also try adding the below dependency?
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
I am integrating Spring Batch in one of the existing applications where other beans are successfully using DataSourceTransactionManager. However, I am getting the following error when I introduce the #EnableBatchProcessing annotation :
Field transactionManager in AuthBeans required a bean of type 'org.springframework.jdbc.datasource.DataSourceTransactionManager' that could not be found.
I've tried to configure BatchConfigurer but it has not resolved the issue. Please see BatchConfigurer code below.
#Configuration
public class CustomBatchConfigurer implements BatchConfigurer {
#Autowired
private DataSource dataSource;
#Override
public JobRepository getJobRepository() throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(dataSource);
factory.setTransactionManager(getTransactionManager());
factory.afterPropertiesSet();
return (JobRepository) factory.getObject();
}
#Override
public PlatformTransactionManager getTransactionManager() throws Exception {
return new DataSourceTransactionManager(dataSource);
}
#Override
public JobLauncher getJobLauncher() throws Exception {
SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
jobLauncher.setJobRepository(getJobRepository());
jobLauncher.afterPropertiesSet();
return jobLauncher;
}
#Override
public JobExplorer getJobExplorer() throws Exception {
JobExplorerFactoryBean factory = new JobExplorerFactoryBean();
factory.setDataSource(dataSource);
factory.afterPropertiesSet();
return (JobExplorer) factory.getObject();
}
I am wondering am I missing something? Your help regarding this will be highly appreciated.
In which class , you kept #EnableBatchProcessing annotation.
#EnableBatchProcessing should be at the highest level.
#EnableBatchProcessing will create the DataSourceTransactionManager by default.
Go through this BatchConfigure and follow the example.
I hope this will resolve your issue.
My spring boot (2.2.4) application is using siteminder as authentication provider. Upon successful authentication siteminder sends user details (Roles) in Http header. Inside the application I need to use spring security for role based authorization. Here is reference documentation I am following -
https://docs.spring.io/spring-security/site/docs/5.0.x/reference/html/preauth.html
My spring-security.xml will look this.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
https://www.springframework.org/schema/security/spring-security.xsd">
<security:http>
<!-- Additional http configuration omitted -->
<security:csrf disabled="true"/>
<security:intercept-url pattern="/**" access="hasRole('ROLE_ABC')"/>
<security:custom-filter position="PRE_AUTH_FILTER" ref="siteminderFilter" />
</security:http>
<bean id="siteminderFilter" class="org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter">
<property name="principalRequestHeader" value="SM_USER"/>
<property name="authenticationManager" ref="authenticationManager" />
</bean>
<bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<property name="preAuthenticatedUserDetailsService">
<bean id="userDetailsServiceWrapper"
class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
<property name="userDetailsService" ref="userDetailsService"/>
</bean>
</property>
</bean>
<bean id="userDetailsService" class="com.mycode.service.SCPMUserDetailsService"/>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="preauthAuthProvider" />
</security:authentication-manager>
</beans>
How do I create a Java config file which will incorporate above security for Spring boot application.
I started something like this and need to inject all the pieces in above xml file.
#EnableWebSecurity
#Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
#Autowired
private MyUserDetailsService myUserDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()// Disable for GraphIQL to work locally
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/healthcheck/**").permitAll()
.antMatchers("/scpm/**").permitAll()
.antMatchers("/approvers/**").hasRole("APPROVER")
.antMatchers("/departments/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and().httpBasic();
}
}
Spring Security reference document does not provide Java Code for SiteMinder integration. It just provides xml configuration for non-spring boot applications. Here is the Java code which can be used in any Spring Boot Application.
#EnableWebSecurity
#Configuration
#Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
#Autowired
private MyUserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
auth.authenticationProvider(preAuthenticatedAuthenticationProvider());
}
#Bean
public RequestHeaderAuthenticationFilter siteminderFilter() throws Exception {
RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
filter.setPrincipalRequestHeader("SM_USER");
filter.setAuthenticationManager(this.authenticationManager());
return filter;
}
#Bean
public PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider() {
PreAuthenticatedAuthenticationProvider preauthAuthProvider = new PreAuthenticatedAuthenticationProvider();
preauthAuthProvider.setPreAuthenticatedUserDetailsService(userDetailsByNameServiceWrapper());
return preauthAuthProvider;
}
#Bean
public UserDetailsByNameServiceWrapper userDetailsByNameServiceWrapper() {
UserDetailsByNameServiceWrapper userDetailsServiceWrapper = new UserDetailsByNameServiceWrapper();
userDetailsServiceWrapper.setUserDetailsService(userDetailsService);
return userDetailsServiceWrapper;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilter(siteminderFilter())
.csrf().disable()// Disable for GraphIQL to work locally
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/healthcheck/**").permitAll()
.antMatchers("/approvers/**").hasRole("APPROVER")
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and().httpBasic();
}
}
Here is custom UserDetailsService.
#Service
public class MyUserDetailsService implements UserDetailsService {
private Logger logger = LoggerFactory.getLogger(SCPMUserDetailsService.class);
#Autowired
private HttpServletRequest request;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
HttpServletRequest request =
((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
String csgroups = "";
Collection<GrantedAuthority> auth = new ArrayList<>();
try {
username = request.getHeader("SM_USER");
} catch (Exception ex){
logger.trace("SM_USER not found in Request Http Headers");
ex.printStackTrace();
}
try {
csgroups = request.getHeader("ROLES");;
} catch (Exception ex){
logger.trace("CSGROUPS not found in Request Http Headers");
ex.printStackTrace();
}
//Roles
if(csgroups != null) {
StringTokenizer st = new StringTokenizer(csgroups, "^");
while (st.hasMoreElements()) {
GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + st.nextToken());
}
}
return new User(username, "", true, true, true, true,auth);
}
#Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
I have two DispatcherServlets and I want to have different contexts for them. First servlet should be secured with spring security and second servlet should not use security at all.
I register servlets as follows:
#SpringBootApplication(exclude = {DispatcherServletAutoConfiguration.class})
public class Application{
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public ServletRegistrationBean FirstServletRegistration() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(FirstWebConfig.class, SecurityConfig.class);
dispatcherServlet.setApplicationContext(applicationContext);
ServletRegistrationBean registrationBean = new ServletRegistrationBean(
dispatcherServlet, "/api/*"
);
registrationBean.setName("firstServlet");
return registrationBean;
}
#Bean
public ServletRegistrationBean SecondServletRegistration() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(SecondWebConfig.class);
dispatcherServlet.setApplicationContext(applicationContext);
ServletRegistrationBean registrationBean = new ServletRegistrationBean(
dispatcherServlet, "/*"
);
registrationBean.setName("secondServlet");
return registrationBean;
}
}
For each of the servlets, I set the context:
#Configuration
#EnableWebMvc
#ComponentScan("com.example.app.controllers.first")
public class FirstWebConfig extends WebMvcConfigurerAdapter {
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
#Configuration
#EnableWebMvc
#ComponentScan("com.example.app.controllers.second")
public class SecondWebConfig extends WebMvcConfigurerAdapter {
}
And I want to use SecurityConfig only for "firstServlet":
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final BCryptPasswordEncoder passwordEncoder;
private final DataSource dataSource;
#Autowired
public SecurityConfig(BCryptPasswordEncoder passwordEncoder, DataSource dataSource) {
this.passwordEncoder = passwordEncoder;
this.dataSource = dataSource;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication()
.usersByUsernameQuery(USERS_BY_USERNAME_QUERY)
.authoritiesByUsernameQuery(AUTHORITIES_BY_USERNAME_QUERY)
.dataSource(dataSource)
.passwordEncoder(passwordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated().and()
.formLogin()
.loginPage("/api/login")
.usernameParameter("username")
.passwordParameter("password")
.and()
.httpBasic().and()
.csrf().disable();
}
}
So how can I exclude use of security for "secondServlet"?
I would have one question regarding the configuration of spring-security-oauth2 2.0.7 please.
I am doing the Authentication using LDAP via a GlobalAuthenticationConfigurerAdapter:
#SpringBootApplication
#Controller
#SessionAttributes("authorizationRequest")
public class AuthorizationServer extends WebMvcConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(AuthorizationServer.class, args);
}
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
registry.addViewController("/oauth/confirm_access").setViewName("authorize");
}
#Configuration
public static class JwtConfiguration {
#Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyPair keyPair = new KeyStoreKeyFactory(
new ClassPathResource("keystore.jks"), "foobar".toCharArray())
.getKeyPair("test");
converter.setKeyPair(keyPair);
return converter;
}
#Bean
public JwtTokenStore jwtTokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter());
}
}
#Configuration
#EnableAuthorizationServer
public static class OAuth2Config extends AuthorizationServerConfigurerAdapter implements EnvironmentAware {
private static final String ENV_OAUTH = "authentication.oauth.";
private static final String PROP_CLIENTID = "clientid";
private static final String PROP_SECRET = "secret";
private static final String PROP_TOKEN_VALIDITY_SECONDS = "tokenValidityInSeconds";
private RelaxedPropertyResolver propertyResolver;
#Inject
private AuthenticationManager authenticationManager;
#Inject
private JwtAccessTokenConverter jwtAccessTokenConverter;
#Inject
private JwtTokenStore jwtTokenStore;
#Inject
private UserDetailsService userDetailsService;
#Override
public void setEnvironment(Environment environment) {
this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_OAUTH);
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(jwtTokenStore);
tokenServices.setAuthenticationManager(authenticationManager);
return tokenServices;
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager).tokenStore(jwtTokenStore).accessTokenConverter(
jwtAccessTokenConverter).userDetailsService(userDetailsService);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess(
"isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(propertyResolver.getProperty(PROP_CLIENTID))
.scopes("read", "write")
.authorities(AuthoritiesConstants.ADMIN, AuthoritiesConstants.USER)
.authorizedGrantTypes("authorization_code", "refresh_token", "password")
.secret(propertyResolver.getProperty(PROP_SECRET))
.accessTokenValiditySeconds(propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer.class, 1800));
}
}
#Configuration
#Order(-10)
protected static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin().loginPage("/login").permitAll()
.and()
.requestMatchers().antMatchers("/login", "/oauth/authorize", "/oauth/confirm_access")
.and()
.authorizeRequests().anyRequest().authenticated();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
#Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}
}
#Configuration
protected static class AuthenticationConfiguration extends
GlobalAuthenticationConfigurerAdapter {
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userDnPatterns("uid={0},ou=people")
.groupSearchBase("ou=groups")
.contextSource().ldif("classpath:test-server.ldif");
}
}
}
While the refresh token works fine with the release 2.0.6 of spring-security-oauth2, it does not work anymore with the version 2.0.7.
As read here, one should set the AuthenticationManager to be used when trying to get a new access token during the refresh.
As far as I understand, this has something to do with the following change of spring-security-oauth2.
I unfortunately did not manage to set it up properly.
org.springframework.security.oauth2.provider.token.DefaultTokenServices#setAuthenticationManager
is called and gets an AuthenticationManager injected. I an not sure I understand how the LdapUserDetailsService is then going to be injected. The only thing I see is that the PreAuthenticatedAuthenticationProvider is going to be called while trying to re-authenticate the user during the token refresh call.
Can anyone advise me on how to do it please?
ps: The exception I am getting is the following:
p.PreAuthenticatedAuthenticationProvider : PreAuthenticated authentication request: org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken#5775: Principal: org.springframework.security.authentication.UsernamePasswordAuthenticationToken#441d5545: Principal: bob; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER
o.s.s.o.provider.endpoint.TokenEndpoint : Handling error: IllegalStateException, UserDetailsService is required.
I had a similar issue when I was implementing a a OAuth2 server with JWT tokens with a custom AuthenticationProvider instead of a UserDetailsService implementation to solve login authentications.
But lately I found that the error Spring raises is correct if you want the refresh_token working correctly. For an AuthenticationProvider implementation is impossible to refresh a token with a refresh_token, because in that kind of implementation you have to resolve if the password is correct, but the refresh token doesn't have that information. However, UserDetailsService is agnostic of the password.
The version 2.0.6 of spring-security-oauth2 works because never checks the user grants, just checks if the refresh token is valid (signed with the private key), but, if the user was deleted from the system after a first login, with a refresh token the deleted user will have infinite time access to your system, that is a big security issue.
Take a look to the issue I reported with this: https://github.com/spring-projects/spring-security-oauth/issues/813
What you need for the OAuth piece is to create an LdapUserDetailsService with the same query as you authenticator and inject it into the AuthorizationServerEndpointsConfigurer. I don't think there's any support for creating a UserDetailService in #Configuration style (might be worth opening a ticket for that in JIRA), but it looks like you can do it in XML.
As advised by Dave Syer, I created a custom LdapUserDetailsService.
The working solution can be found under the following tag.
Application Context
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<context:property-placeholder location="application.yml"/>
<bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
<constructor-arg value="${authentication.ldap.url}" />
</bean>
<bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
<constructor-arg index="0" value="${authentication.ldap.userSearchBase}" />
<constructor-arg index="1" value="uid={0}" />
<constructor-arg index="2" ref="contextSource"/>
</bean>
<bean id="ldapAuthoritiesPopulator" class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
<constructor-arg index="0" ref="contextSource"/>
<constructor-arg index="1" value="${authentication.ldap.groupSearchBase}"/>
<property name="groupSearchFilter" value="${authentication.ldap.groupSearchFilter}"/>
</bean>
<bean id="myUserDetailsService"
class="org.springframework.security.ldap.userdetails.LdapUserDetailsService">
<constructor-arg index="0" ref="userSearch"/>
<constructor-arg index="1" ref="ldapAuthoritiesPopulator"/>
</bean>
</beans>
Properties
authentication:
ldap:
url: ldap://127.0.0.1:33389/dc=springframework,dc=org
userSearchBase:
userDnPatterns: uid={0},ou=people
groupSearchBase: ou=groups
groupSearchFilter: (uniqueMember={0})