I'm using sprig security 4.0.4.RELEASE in my Spring 4.2.5.RELEASE application.
I want to implement the role wise security on method level in my application using #Secured annotation
I'm tried to implement this by adding #EnableGlobalMethodSecurity(securedEnabled = true) but some error is occurring.
Here is my SecurityConfiguration Class
package com.application.security;
import com.application.security.LoginSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.builders.WebSecurity;
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;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("customUserDetailsService")
UserDetailsService userDetailsService;
#Autowired
LoginSuccessHandler loginSuccessHandler;
#Autowired
PersistentTokenRepository tokenRepository;
/*#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
auth.authenticationProvider(authenticationProvider());
}*/
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
auth.authenticationProvider(customDaoAuthenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().invalidSessionUrl("/logout");
http.authorizeRequests()
/*.antMatchers("/").access("hasRole('USER') or hasRole('ADMIN') or hasRole('DBA')")*/
.antMatchers("/registration").permitAll()
.antMatchers("/exclusion").permitAll()
.antMatchers("/landing").permitAll()
.antMatchers("/uploadSingle").permitAll()
.antMatchers("/uploadSingleNoFile").permitAll()
.antMatchers("/loadHtmlTableAjax").permitAll()
.antMatchers("/AllclaimDetails").permitAll()
.antMatchers("/deleteclaim").permitAll()
.antMatchers("/claimComplete").permitAll()
.antMatchers("/exclusionComplete").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").permitAll()
.loginProcessingUrl("/login").usernameParameter("username").passwordParameter("password")
.successHandler(loginSuccessHandler)
.failureUrl("/login?error=true")
.and()
.logout()
.logoutUrl("/logout")
.deleteCookies("JSESSIONID")
.permitAll()
.and()
.rememberMe().rememberMeParameter("remember-me").tokenRepository(tokenRepository).tokenValiditySeconds(86400)
.and()
.csrf()
.and()
.exceptionHandling().accessDeniedPage("/Access_Denied");
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**","/logoff");
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Bean
CustomDaoAuthenticationProvider customDaoAuthenticationProvider() {
CustomDaoAuthenticationProvider customAuthenticationProvider = new CustomDaoAuthenticationProvider();
customAuthenticationProvider.setUserDetailsService(userDetailsService);
customAuthenticationProvider.setPasswordEncoder(passwordEncoder());
return customAuthenticationProvider;
}
#Bean
public PersistentTokenBasedRememberMeServices getPersistentTokenBasedRememberMeServices() {
PersistentTokenBasedRememberMeServices tokenBasedservice = new PersistentTokenBasedRememberMeServices(
"remember-me", userDetailsService, tokenRepository);
return tokenBasedservice;
}
#Bean
public AuthenticationTrustResolver getAuthenticationTrustResolver() {
return new AuthenticationTrustResolverImpl();
}
}
When I'm giving #EnableGlobalMethodSecurity(securedEnabled = true) , the application is not getting started. I'm getting the below error.
The problem is from AuthenticationTrustResolver bean
AnnotationConfigWebApplicationContext:546 - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'appConfig': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'metaDataSourceAdvisor': Cannot resolve reference to bean 'methodSecurityMetadataSource' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration.setAuthenticationTrustResolver(org.springframework.security.authentication.AuthenticationTrustResolver); nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'securityConfiguration': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.setTrustResolver(org.springframework.security.authentication.AuthenticationTrustResolver); nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'getAuthenticationTrustResolver': Requested bean is currently in creation: Is there an unresolvable circular reference?
Can somebody tell me the reason?
Please create Initializer class by extending AbstractSecurityWebApplicationInitializer.
Please refer my answer in here.
No bean named 'springSecurityFilterChain' available
I solved this problem by updating my spring security dependency to latest in pom.xml
I changed the version from
<springsecurity.version>4.0.4.RELEASE</springsecurity.version>
to
<springsecurity.version>4.2.3.RELEASE</springsecurity.version>
Related
I got error in encoding my inMemory User password using BCryptPasswordEncoder
here is my springsecurityconfig file
SpringSecurityConfig class
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;
#Configuration
#EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password(passwordEncoder().encode("password"))
.roles("USER");
}
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
How can i properly encode the password without getting bean creation error
You can use a more component-based approach to do that since the WebSecurityConfigurerAdapter has been deprecated in 5.7.0-M2.
#Configuration
#EnableWebSecurity
public class SpringSecurityConfig {
#Bean
public SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.httpBasic();
return http.build()
}
#Bean
public UserDetailsService userDetailsService(PasswordEncoder encoder) {
User admin = User.withUsername("admin")
.password(encoder.encode("password"))
.roles("USER").build();
return new InMemoryUserDetailsManager(admin);
}
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
This way, you have in-memory authentication by providing a InMemoryUserDetailsManager as the implementation of UserDetailsService, and this bean is dependent on the PasswordEncoder.
You can find more detail on the reference docs.
I'm using in-memory auth to have my login working in spring
However, I want to change it now to persistent database
Please see code below:
JWTWebSecurityConfig
package com.sbc.cpex.security.jwt;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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.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.builders.WebSecurity;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class JWTWebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtUnAuthorizedResponseAuthenticationEntryPoint jwtUnAuthorizedResponseAuthenticationEntryPoint;
#Autowired
private UserDetailsService jwtInMemoryUserDetailsService;
#Autowired
private JwtTokenAuthorizationOncePerRequestFilter jwtAuthenticationTokenFilter;
#Value("${jwt.get.token.uri}")
private String authenticationPath;
#Autowired
DataSource dataSource;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// auth
// .userDetailsService(jwtInMemoryUserDetailsService)
// .passwordEncoder(passwordEncoderBean());
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("select username, password"
+ " from users where username=?")
.passwordEncoder(passwordEncoderBean());
}
#Bean
public PasswordEncoder passwordEncoderBean() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
// .csrf().and().cors().disable()
.exceptionHandling().authenticationEntryPoint(jwtUnAuthorizedResponseAuthenticationEntryPoint).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.anyRequest().authenticated();
httpSecurity
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
httpSecurity
.headers()
.frameOptions().sameOrigin() //H2 Console Needs this setting
.cacheControl(); //disable caching
}
#Override
public void configure(WebSecurity webSecurity) throws Exception {
webSecurity
.ignoring()
.antMatchers(
HttpMethod.POST,
authenticationPath
)
.antMatchers(HttpMethod.OPTIONS, "/**")
.and()
.ignoring()
.antMatchers(
HttpMethod.GET,
"/" //Other Stuff You want to Ignore
)
.and()
.ignoring()
.antMatchers("/h2-console/**/**");//Should not be in Production!
}
}
Steps I did:
1. Comment out these lines
auth
.userDetailsService(jwtInMemoryUserDetailsService)
.passwordEncoder(passwordEncoderBean());
Add the following lines for jdbcauth
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("select username, password"
+ " from users where username=?")
.passwordEncoder(passwordEncoderBean());
Create a class called DataSourceConfig
package com.sbc.cpex.security.jwt;
import javax.sql.DataSource;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class DataSourceConfig {
#Bean
public DataSource getDataSource() {
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.username("test");
dataSourceBuilder.password("pass");
return dataSourceBuilder.build();
}
}
But I'm getting this error
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'h2Console' defined in class path resource [org/springframework/boot/autoconfigure/h2/H2ConsoleAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.web.servlet.ServletRegistrationBean]: Factory method 'h2Console' threw exception; nested exception is java.lang.IllegalArgumentException: dataSource or dataSourceClassName or jdbcUrl is required.
Please enlighten me. TIA
The stack trace itself is self explanatory :
nested exception is java.lang.IllegalArgumentException: dataSource or dataSourceClassName or jdbcUrl is required.
You need to provide the Datasource url when creating a bean of Datasource.
From the stacktrace I could understand that you are using H2 . So, you could create a bean like :
#Bean
public DataSource getDataSource() {
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.driverClassName("org.h2.Driver");
dataSourceBuilder.url("jdbc:h2:mem:test");
dataSourceBuilder.username("username");
dataSourceBuilder.password("");
return dataSourceBuilder.build();
}
In your code, you only provided the username and password part, hence it is throwing the error.
#iamjpcbau Springboot is autoconfiguring H2 as the databse as it is found as a dependency during class path scanning. Since you have provided a Datasource bean , spring automatically takes it up for configuring H2 but the url is missing , which causes the exception that you are receiving.
Inorder to configure another database with your project, configure the database through application.properties or application.yml or manually create configuration beans so that the configuration for your corresponding database is taken up at startup instead of H2.Now, since there are no other database configured and since H2 is found on classpath , spring is configuring that by default.
I have searched the web for solutions but have not found any. This is my last resort.
2018-04-29 16:31:57.743 WARN 71317 --- [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'authorizationServerConfig': Unsatisfied dependency expressed through field 'authenticationManager'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'resourceServerConfig': Unsatisfied dependency expressed through field 'authenticationManager'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'org.springframework.security.authenticationManager': Requested bean is currently in creation: Is there an unresolvable circular reference?
CLASS:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import
org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends
AuthorizationServerConfigurerAdapter {
#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("secret")
.authorizedGrantTypes("authorization_code")
.scopes("user_info")
.autoApprove(true);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
}
}
CLASS:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.oauth2.config.annotation.web.configuration.EnableResourceServer;
#EnableResourceServer
#Configuration
public class ResourceServerConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private UserDetailsService customUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.parentAuthenticationManager(authenticationManager)
.userDetailsService(customUserDetailsService);
}
#Bean(name = BeanIds.AUTHENTICATION_MANAGER)
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
I have run into a strange problem. I created a project with Spring Boot 2.0.1 with redis, mongodb and elasticsearch . Before adding elasticsearch, everything runs smoothly, but after I add elasticsearch, Spring Boot starts complaining,but the error looks like not related to elasticsearch, it complains that it could not create userRepo 。 Please get noticed I used lombok's #RequiredArgsConstructor to generate constructor to make injection work, so it should not be #autowired issue, Anyone can help me out? thank in adavance
2018-05-02 16:12:58.687 INFO 74244 --- [ restartedMain] o.s.b.w.servlet.ServletRegistrationBean : Servlet dispatcherServlet mapped to [/]
2018-05-02 16:12:59.037 WARN 74244 --- [ restartedMain] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'securityConfig' defined in file [/Users/wangpeng/workspace/books/gtm/backend/api/out/production/classes/dev/local/gtm/api/config/SecurityConfig.class]: Unsatisfied dependency expressed through constructor parameter 1; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userDetailsServiceImpl' defined in file [/Users/wangpeng/workspace/books/gtm/backend/api/out/production/classes/dev/local/gtm/api/security/UserDetailsServiceImpl.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userRepo': Invocation of init method failed; nested exception is java.lang.IllegalStateException: No association found!
2018-05-02 16:12:59.037 DEBUG 74244 --- [ restartedMain] h.i.c.PoolingHttpClientConnectionManager : Connection manager is shutting down
2018-05-02 16:12:59.037 DEBUG 74244 --- [ restartedMain] h.i.c.PoolingHttpClientConnectionManager : Connection manager shut down
Process finished with exit code 1
my subproject's build.gradle is as follows:
apply plugin: 'org.springframework.boot'
configurations {
compile.exclude module: 'spring-boot-starter-tomcat'
}
bootRun {
systemProperties = System.properties as Map<String, ?>
}
test {
systemProperties['spring.profiles.active'] = 'test'
}
dependencies {
implementation("io.springfox:springfox-swagger2:${springFoxVersion}")
implementation("io.springfox:springfox-bean-validators:${springFoxVersion}")
implementation("io.springfox:springfox-swagger-ui:${springFoxVersion}")
implementation("org.springframework.boot:spring-boot-starter-undertow")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("io.jsonwebtoken:jjwt:0.9.0")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-aop")
implementation("org.zalando:problem-spring-web:0.20.1")
implementation("org.redisson:redisson:${redissonVersion}")
implementation("com.fasterxml.jackson.module:jackson-module-afterburner")
implementation("org.springframework.boot:spring-boot-starter-data-mongodb")
implementation("org.springframework.boot:spring-boot-starter-data-redis")
implementation("com.github.vanroy:spring-boot-starter-data-jest:3.1.2.RELEASE")
testImplementation("org.springframework.security:spring-security-test")
}
The UserRepo is as follows
package dev.local.gtm.api.repository;
import dev.local.gtm.api.domain.User;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
#Repository
public interface UserRepo extends MongoRepository<User, String> {
String USERS_BY_LOGIN_CACHE = "usersByLogin";
String USERS_BY_MOBILE_CACHE = "usersByMobile";
String USERS_BY_EMAIL_CACHE = "usersByEmail";
#Cacheable(cacheNames = USERS_BY_MOBILE_CACHE)
Optional<User> findOneByMobile(#Param("mobile") String mobile);
#Cacheable(cacheNames = USERS_BY_EMAIL_CACHE)
Optional<User> findOneByEmailIgnoreCase(#Param("email") String email);
#Cacheable(cacheNames = USERS_BY_LOGIN_CACHE)
Optional<User> findOneByLogin(#Param("login") String login);
Page<User> findAllByLoginNot(Pageable pageable, #Param("login") String login);
List<User> findAllByActivatedIsFalseAndCreatedDateBefore(Instant dateTime);
}
The Spring Security Configuration is as follows:
package dev.local.gtm.api.config;
import dev.local.gtm.api.security.AuthoritiesConstants;
import dev.local.gtm.api.security.jwt.JWTConfigurer;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
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.builders.WebSecurity;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.filter.CorsFilter;
import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport;
import javax.annotation.PostConstruct;
#RequiredArgsConstructor
#Configuration
#ComponentScan(basePackages = "dev.local.gtm.api")
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
#Import(SecurityProblemSupport.class)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final AuthenticationManagerBuilder authenticationManagerBuilder;
private final UserDetailsService userDetailsService;
private final CorsFilter corsFilter;
private final SecurityProblemSupport problemSupport;
private final JWTConfigurer jwtConfigurer;
#PostConstruct
public void init() {
try {
authenticationManagerBuilder
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
} catch (Exception e) {
throw new BeanInitializationException("安全配置失败", e);
}
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**")
.antMatchers("/app/**/*.{js,html}")
.antMatchers("/i18n/**")
.antMatchers("/content/**")
.antMatchers("/swagger-ui/index.html")
.antMatchers("/test/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling()
.authenticationEntryPoint(problemSupport)
.accessDeniedHandler(problemSupport)
.and()
.csrf()
.disable()
.headers()
.frameOptions()
.disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/**").authenticated()
.antMatchers("/websocket/tracker").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/websocket/**").permitAll()
.antMatchers("/management/health").permitAll()
.antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/v2/api-docs/**").permitAll()
.antMatchers("/swagger-resources/configuration/ui").permitAll()
.antMatchers("/swagger-ui/index.html").permitAll()
.and()
.apply(jwtConfigurer);
}
}
The UserDetailServiceImpl is as follows:
package dev.local.gtm.api.security;
import dev.local.gtm.api.config.Constants;
import dev.local.gtm.api.domain.User;
import dev.local.gtm.api.repository.UserRepo;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import lombok.val;
import org.hibernate.validator.internal.constraintvalidators.hv.EmailValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import java.util.Locale;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
#Log4j2
#RequiredArgsConstructor
#Component("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepo userRepo;
#Override
public UserDetails loadUserByUsername(final String login) {
log.debug("正在对用户名为 {} 的用户进行鉴权", login);
if (new EmailValidator().isValid(login, null)) {
val userByEmailFromDatabase = userRepo.findOneByEmailIgnoreCase(login);
return userByEmailFromDatabase.map(user -> createSpringSecurityUser(login, user))
.orElseThrow(() -> new UsernameNotFoundException("系统中不存在 email 为 " + login + " 的用户"));
}
if (Pattern.matches(Constants.MOBILE_REGEX, login)) {
val userByMobileFromDatabase = userRepo.findOneByMobile(login);
return userByMobileFromDatabase.map(user -> createSpringSecurityUser(login, user))
.orElseThrow(() -> new UsernameNotFoundException("系统中不存在手机号为 " + login + " 的用户"));
}
String lowercaseLogin = login.toLowerCase(Locale.ENGLISH);
val userByLoginFromDatabase = userRepo.findOneByLogin(lowercaseLogin);
return userByLoginFromDatabase.map(user -> createSpringSecurityUser(lowercaseLogin, user))
.orElseThrow(() -> new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the database"));
}
private org.springframework.security.core.userdetails.User createSpringSecurityUser(String lowercaseLogin, User user) {
if (!user.isActivated()) {
throw new UserNotActivatedException("用户 " + lowercaseLogin + " 没有激活");
}
val grantedAuthorities = user.getAuthorities().stream()
.map(authority -> new SimpleGrantedAuthority(authority.getName()))
.collect(Collectors.toList());
return new org.springframework.security.core.userdetails.User(user.getLogin(),
user.getPassword(),
grantedAuthorities);
}
}
[update] After I change spring-boot-jest to spring-boot-elasticsearch the error is more specific. it now says the elasticsearchTemplate bean is not defined, but in fact it is.
2018-05-02 17:04:59.776 WARN 76262 --- [ restartedMain] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'securityConfig' defined in file [/Users/wangpeng/workspace/books/gtm/backend/api/out/production/classes/dev/local/gtm/api/config/SecurityConfig.class]: Unsatisfied dependency expressed through constructor parameter 1; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userDetailsService' defined in file [/Users/wangpeng/workspace/books/gtm/backend/api/out/production/classes/dev/local/gtm/api/security/UserDetailsServiceImpl.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userRepo': Cannot resolve reference to bean 'elasticsearchTemplate' while setting bean property 'elasticsearchOperations'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'elasticsearchTemplate' available
2018-05-02 17:04:59.805 INFO 76262 --- [ restartedMain] o.apache.catalina.core.StandardService : Stopping service [Tomcat]
2018-05-02 17:04:59.822 INFO 76262 --- [ restartedMain] ConditionEvaluationReportLoggingListener :
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2018-05-02 17:04:59.925 ERROR 76262 --- [ restartedMain] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in dev.local.gtm.api.security.UserDetailsServiceImpl required a bean named 'elasticsearchTemplate' that could not be found.
Action:
Consider defining a bean named 'elasticsearchTemplate' in your configuration.
Process finished with exit code 1
The bean is defined in ElasticConfig as follows
package dev.local.gtm.api.config;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.elasticsearch.client.Client;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.EntityMapper;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import java.io.IOException;
#Configuration
#EnableConfigurationProperties(ElasticsearchProperties.class)
#ConditionalOnProperty("spring.data.elasticsearch.cluster-nodes")
public class ElasticConfig {
#Bean
public ElasticsearchTemplate elasticsearchTemplate(Client client, Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder) {
return new ElasticsearchTemplate(client, new CustomEntityMapper(jackson2ObjectMapperBuilder.createXmlMapper(false).build()));
}
public class CustomEntityMapper implements EntityMapper {
private ObjectMapper objectMapper;
public CustomEntityMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
}
#Override
public String mapToString(Object object) throws IOException {
return objectMapper.writeValueAsString(object);
}
#Override
public <T> T mapToObject(String source, Class<T> clazz) throws IOException {
return objectMapper.readValue(source, clazz);
}
}
}
It turns out I reuse same entities for both Elasticsearch and MongoDB, which raise the exception. So I managed to get it resolved by separating the entities
I want to use #Secured annotations for my controller actions. Since I have java based configuration I need to know how I can set the
<security:global-method-security secured-annotations="enabled" />
option without the xml file.
Upate 1:
I addeed #EnableGlobalMethodSecurity(securedEnabled = true) to my security config class:
#Configuration
#EnableWebMvcSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class LIRSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authenticationProvider(preAuthenticatedAuthenticationProvider())
.addFilter(cookiePreAuthenticationFilter())
.authorizeRequests()
.antMatchers("/**")
.hasAnyAuthority("ROLE_USER")
;
}
...
}
Up on start-up this causes this exception
Jul 21, 2014 3:32:54 PM org.apache.catalina.core.StandardContext listenerStart
SEVERE: Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'methodSecurityInterceptor' defined in class path resource [org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: An AuthenticationManager is required
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1512)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:521)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:458)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:296)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:293)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:633)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:932)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:479)
at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:410)
at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:306)
at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:112)
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4937)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5434)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1559)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1549)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
at java.util.concurrent.FutureTask.run(FutureTask.java:138)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
at java.lang.Thread.run(Thread.java:695)
Caused by: java.lang.IllegalArgumentException: An AuthenticationManager is required
at org.springframework.util.Assert.notNull(Assert.java:112)
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.afterPropertiesSet(AbstractSecurityInterceptor.java:121)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1571)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1509)
... 22 more
Jul 21, 2014 3:32:54 PM org.apache.catalina.core.StandardContext
Update 2:
After adding
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
I get an other exception:
Caused by: org.springframework.beans.FatalBeanException: A dependency cycle was detected when trying to resolve the AuthenticationManager. Please ensure you have configured authentication.
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$AuthenticationManagerDelegator.validateBeanCycle(WebSecurityConfigurerAdapter.java:462)
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$AuthenticationManagerDelegator.<init>(WebSecurityConfigurerAdapter.java:430)
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.authenticationManagerBean(WebSecurityConfigurerAdapter.java:220)
at com.galexis.lir.config.LIRSecurityConfig.authenticationManagerBean(LIRSecurityConfig.java:36)
at com.galexis.lir.config.LIRSecurityConfig$$EnhancerBySpringCGLIB$$88306f96.CGLIB$authenticationManagerBean$3(<generated>)
at com.galexis.lir.config.LIRSecurityConfig$$EnhancerBySpringCGLIB$$88306f96$$FastClassBySpringCGLIB$$a4d1ea33.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:293)
at com.galexis.lir.config.LIRSecurityConfig$$EnhancerBySpringCGLIB$$88306f96.authenticationManagerBean(<generated>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:160)
... 77 more
You need to use the
#EnableGlobalMethodSecurity(securedEnabled = true)
annotation, as defined in the docs.
You should add also a bean for Manager. Check this out:
#Configuration
#EnableWebMvcSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Resource(name = "authService")
private UserDetailsService userDetailsService;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
Md5PasswordEncoder encoder = new Md5PasswordEncoder();
auth.userDetailsService(userDetailsService).passwordEncoder(encoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/login")
.and()
.logout()
.logoutSuccessUrl("/");
}
}
Important thing is
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
Just for the others which will try to solve "A dependency cycle was detected when trying to resolve the AuthenticationManager. Please ensure you have configured authentication." problem.
The solution is to add the follwing method:
#Override
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
// do NOT call super.configure() !
...
}
So this did the work :
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().
withUser("user").password("user").roles("USER").and().
withUser("admin").password("admin").roles("USER", "ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
#Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
}
the important parts are
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().
withUser("user").password("user").roles("USER").and().
withUser("admin").password("admin").roles("USER", "ADMIN");
}
Stefan is right, adding
#EnableGlobalMethodSecurity(securedEnabled = true)
does the trick.
In my particular situation I had to add to get rid of the excptions.
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").authorities("ROLE_USER");
}
Follow the classes within the annotations
#Configuration
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfigProvider extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
#Configuration
#EnableOAuth2Client
#EnableWebSecurity
public class OAuth2SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
OAuth2ClientContext oauth2ClientContext;
#Override
protected void configure(HttpSecurity http) throws Exception {
//TODO
}
#Override
public void configure(WebSecurity web) throws Exception {
//TODO
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
#EnableGlobalMethodSecurity(prePostEnabled=true)