I'm using io.github.lognet:grpc-spring-boot-starter:3.5.3 to add Grpc support. And I have a org.springframework.boot:spring-boot-starter-security dependency. I don't want to add org.springframework.boot:spring-boot-starter-web dependency, cause my application need to use Grpc Netty server without servlets and tomcat server.
Having two implementations of AuthentificationProvider I configured a AuthentificationManager:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final ClientAuthenticationProvider clientAuthenticationProvider;
private final ServiceAuthenticationProvider serviceAuthenticationProvider;
public WebSecurityConfig(ClientAuthenticationProvider clientAuthenticationProvider,
ServiceAuthenticationProvider serviceAuthenticationProvider) {
this.clientAuthenticationProvider = clientAuthenticationProvider;
this.serviceAuthenticationProvider = serviceAuthenticationProvider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(clientAuthenticationProvider)
.authenticationProvider(serviceAuthenticationProvider);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
Build fails with message:
Error:(18, 8) java: cannot access javax.servlet.Filter class file
for javax.servlet.Filter not found
This because WebSecurityConfigurerAdapter needs javax.servlet.Filter.
I'm tried to add javax.servlet:javax.servlet-api:4.0.1 dependency, but application failes at runtime when calling authenticationManager.authenticate(...):
public class MytAuthService {
private final AuthenticationManager authenticationManager;
public AbstractAuthService(
#Qualifier("authenticationManagerBean") AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
public TokenPair authenticate(AuthRequest request) throws AuthenticationException {
...
authenticationManager.authenticate(createAuthToken(username, password));
...
}
with stacktrace:
java.lang.IllegalStateException: This object has not been built at
org.springframework.security.config.annotation.AbstractSecurityBuilder.getObject(AbstractSecurityBuilder.java:55)
~[spring-security-config-5.2.1.RELEASE.jar:5.2.1.RELEASE] at
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$AuthenticationManagerDelegator.authenticate(WebSecurityConfigurerAdapter.java:506)
~[spring-security-config-5.2.1.RELEASE.jar:5.2.1.RELEASE] at
org.my.service.auth.MyAuthService.authenticate(MyAuthService.java:75)
~[classes/:?]
When I adding org.springframework.boot:spring-boot-starter-web all works well, but I don't want add Tomcat and servlets to my application.
Can I configure AuthentificationManager to set custom AuthenticationProvider's without extending WebSecurityConfigurerAdapter class or any way? Or may be you can show a good sample/tutorial where Grpc and Spring Security uses only?
Answer myself, I should remove extending WebSecurityConfigurerAdapter and create a AuthenticationManager bean using ProviderManager class.
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {
private final ClientAuthenticationProvider clientAuthenticationProvider;
private final ServiceAuthenticationProvider serviceAuthenticationProvider;
public WebSecurityConfig(ClientAuthenticationProvider clientAuthenticationProvider,
ServiceAuthenticationProvider serviceAuthenticationProvider) {
this.clientAuthenticationProvider = clientAuthenticationProvider;
this.serviceAuthenticationProvider = serviceAuthenticationProvider;
}
#Bean
public AuthenticationManager authenticationManagerBean() {
return new ProviderManager(Arrays.asList(clientAuthenticationProvider, serviceAuthenticationProvider));
}
}
Related
In my Spring boot app, I have the following two classes:
#EnableWebSecurity
public class AppSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// TODO re-enable csrf after dev is done
.csrf()
.disable()
// we must specify ordering for our custom filter, otherwise it
// doesn't work
.addFilterAfter(jwtAuthenticationFilter,
UsernamePasswordAuthenticationFilter.class)
// we don't need Session, as we are using jwt instead. Sessions
// are harder to scale and manage
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
and:
#Component
public class JwtAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
/*
* we must set authentication manager for our custom filter, otherwise it
* errors out
*/
#Override
#Autowired
public void setAuthenticationManager(
AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
}
JwtAuthenticationFilter depends on an AuthenticationManager bean through its setAuthenticationManager method, but that bean gets created in AppSecurityConfig which has JwtAuthenticationFilter autowired in. This whole thing creates a circular dependency.
How should I resolve this issue?
I fixed this issue by following what was suggested here:
Cannot pass AuthenticationManager to custom filter by #Autowired
I removed #Component from JwtAuthenticationFilter and instead of autowiring JwtAuthenticationFilter to WebSecurityConfig class, I defined the bean there:
#Bean
public JwtAuthenticationFilter JwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
I'm currently developing a React WebApp with a Spring Boot REST API in the backend. I want to use OAuth2 to secure the API (and to improve my knowledge). I have however some questions regarding the correct authentication flow to use.
Since the frontend is using JavaScript I should not use flows that require a client secret.
The only flow that does not require a client secret in Spring is the Implicit Flow. With this flow however, Spring does not support refresh tokens. That means that after some time the user would get automatically logged out and needs to authorize the WebApp again.
Another option I saw was creating a client without a secret and then use the Authorization Code flow. But I have some doubts if this is the right way to go.
So my question basically: Which is the best OAuth2 flow to use with a Javascript frontend, when I don't want the user to be logged out after some time?
WebSecurityConfig
#Configuration
#EnableWebSecurity
#Import(Encoders.class)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsServiceImpl;
#Autowired
private PasswordEncoder userPasswordEncoder;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServiceImpl).passwordEncoder(userPasswordEncoder);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.formLogin().permitAll()
.and()
.authorizeRequests().antMatchers("/login", "/error**").permitAll()
.anyRequest().authenticated();
}
}
ResourceServerConfig
#Configuration
#EnableResourceServer
#EnableGlobalMethodSecurity
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "resource-server-rest-api";
private static final String SECURED_READ_SCOPE = "#oauth2.hasScope('read')";
private static final String SECURED_WRITE_SCOPE = "#oauth2.hasScope('write')";
private static final String SECURED_PATTERN = "/api/**";
#Autowired
private DefaultTokenServices tokenServices;
#Autowired
private TokenStore tokenStore;
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID)
.tokenServices(tokenServices)
.tokenStore(tokenStore);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.antMatcher(SECURED_PATTERN).authorizeRequests().anyRequest().authenticated();
}
}
OAuth2Config
#Configuration
#PropertySource({"classpath:persistence.properties"})
#EnableAuthorizationServer
#Import(WebSecurityConfig.class)
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("dataSource")
private DataSource dataSource;
#Autowired
private UserDetailsService userDetailsServiceImpl;
#Autowired
private PasswordEncoder oauthClientPasswordEncoder;
#Autowired
private AuthenticationManager authenticationManager;
#Bean
public OAuth2AccessDeniedHandler oauthAccessDeniedHandler() {
return new OAuth2AccessDeniedHandler();
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(
new ClassPathResource("mykeys.jks"),
"mypass".toCharArray());
converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mykeys"));
return converter;
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setTokenEnhancer(accessTokenConverter());
return defaultTokenServices;
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients().tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.passwordEncoder(oauthClientPasswordEncoder);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
final TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter()));
endpoints
.tokenStore(tokenStore())
.tokenEnhancer(tokenEnhancerChain)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsServiceImpl);
}
}
The Implicit flow is meant for JavaScript implementations according to the OAuth2 spec
Refresh tokens are not supported with the Implicit flow. The spring implementation is following the Oauth2 spec.
In case of a Javascript client implementations, tokens are stored on the client. When using refresh tokens, the refresh token needs to be persisted on the client in order to obtain a new access token in the future. If this is the case, you might as wel issue a long(er) lasting access token instead of a refresh token. When working client side, there is no advantage in using refresh token.
I'm using Spring Security 4.0.1 and want to use multiple authentication providers for authentication using Java-based configuration. How do I specify the provider order?
I was hoping to use AuthenticationManagerBuilder, since that's what WebSecurityConfigurerAdapter.configureGlobal() exposes, but I don't see any way to specify the order. Do I need to create a ProviderManager manually?
Update: Here's a problem clarification based on Arun's answer. The specific providers I want to use are ActiveDirectoryLdapAuthenticationProvider and DaoAuthenticationProvider for a custom UserService.
Ultimately I'd like to authenticate against the DaoAuthenticationProvider first and the ActiveDirectoryLdapAuthenticationProvider second.
The AD provider involves a call to AuthenticationManagerBuilder.authenticationProvider() but the DAO provider involves calling AuthenticationManagerBuilder.userService(), which creates a DaoAuthenticationProvider around the user service behind the scenes. Looking at the source code, it doesn't directly place a provider in the provider list (it creates a configurer), so Arun's answer isn't working for me here.
I tried creating the DaoAuthenticationProvider manually and passing it to authenticationProvider(). It didn't impact the order.
I tried an objectPostProcessor inside the configure method and it worked. Not sure if this is what you want:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.passwordEncoder(new BCryptPasswordEncoder());
auth.authenticationProvider(new CustomAuthenticationProvider(this.dataSource));
auth.objectPostProcessor(new ObjectPostProcessor<Object>() {
#Override
public <O> O postProcess(O object) {
ProviderManager providerManager = (ProviderManager) object;
Collections.swap(providerManager.getProviders(), 0, 1);
return object;
}
});
}
This is the configure method that goes on your WebSecurityConfigurerAdapter inherited class.
The reason for the object post processor is that we need to wait the AuthenticationManagerBuilder to actually build the object before we can access and change the order of the providers list.
Hope it helps.. let me know if you have any questions.
There is no explicit ordering provision. The order of invocation will be the order in which you have provided your AuthenticationProviderto AuthenticationManagerBuilder.authenticationProvider(). Refer here for xml configuration. The same should apply for java config as well.
For eg
auth.authenticationProvider(getAuthenticationProvider2());
auth.authenticationProvider(getAuthenticationProvider1());
will result in the following order of invocation AuthenticationProvider2,AuthenticationProvider1
and
auth.authenticationProvider(getAuthenticationProvider1());
auth.authenticationProvider(getAuthenticationProvider2());
will result in the following order of invocation AuthenticationProvider1,AuthenticationProvider2
I had exactly the same problem in my Spring 5 application, but in my case creating DaoAuthenticationProvider helped. Here's my code (I omitted a lot of it and pasted the most important one).
...
import javax.annotation.PostConstruct;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
#Import(SecurityProblemSupport.class)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class);
private final AuthenticationManagerBuilder authenticationManagerBuilder;
private final UserDetailsService userDetailsService;
private final TokenProvider tokenProvider;
private final CorsFilter corsFilter;
private final SecurityProblemSupport problemSupport;
public SecurityConfiguration(AuthenticationManagerBuilder authenticationManagerBuilder, UserDetailsService userDetailsService, TokenProvider tokenProvider, CorsFilter corsFilter, SecurityProblemSupport problemSupport) {
this.authenticationManagerBuilder = authenticationManagerBuilder;
this.userDetailsService = userDetailsService;
this.tokenProvider = tokenProvider;
this.corsFilter = corsFilter;
this.problemSupport = problemSupport;
}
#PostConstruct
public void init() {
try {
authenticationManagerBuilder.authenticationProvider(userDetailsAuthenticationProvider());
if (isSsoEnabled()) {
authenticationManagerBuilder
.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
}
if (isKerberosEnabled()) {
authenticationManagerBuilder
.authenticationProvider(kerberosServiceAuthenticationProvider());
}
} catch (Exception e) {
throw new BeanInitializationException("Security configuration failed", e);
}
}
#Bean
public DaoAuthenticationProvider userDetailsAuthenticationProvider() {
DaoAuthenticationProvider authProvider
= new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new CustomPasswordEncoder();
}
#Override
public void configure(WebSecurity web) throws Exception {
...
}
#Override
protected void configure(HttpSecurity http) throws Exception {
...
}
private JWTConfigurer securityConfigurerAdapter() {
return new JWTConfigurer(tokenProvider);
}
private ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider =
new ActiveDirectoryLdapAuthenticationProvider(getAdDomain(), getAdServer());
provider.setUserDetailsContextMapper((UserDetailsContextMapper) userDetailsService);
return provider;
}
private boolean isSsoEnabled() {
return Boolean.parseBoolean(ConfigurationFileUtils.getConfigurationProperty("security.use-sso"));
}
private boolean isKerberosEnabled() {
return isSsoEnabled() && Boolean.parseBoolean(ConfigurationFileUtils.getConfigurationProperty("security.use-kerberos"));
}
}
I have set up the boilerplate spring security Configurer:
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource datasource;
#Override
protected void configure(HttpSecurity http) throws Exception {
// ...setting up security for routes, etc.
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// here I have access to the AuthenticationManagerBuilder
// I can associate it with my datasource, set the password encoder, etc.
JdbcUserDetailsManager userDetailsService = new JdbcUserDetailsManager();
userDetailsService.setDataSource(datasource);
PasswordEncoder encoder = new BCryptPasswordEncoder();
auth.userDetailsService(userDetailsService).passwordEncoder(encoder);
auth.jdbcAuthentication().dataSource(datasource);
}
But what I want is to be able to access that AuthenticationManagerBuilder from another bean like this:
#Service
public class MyUserService {
#Autowired
AuthenticationManagerBuilder builder;
public void createUser(...) {
//use builder here...
JdbcUserDetailsManager userDetailsService = new JdbcUserDetailsManager();
userDetailsService.setDataSource(datasource);
PasswordEncoder encoder = new BCryptPasswordEncoder();
builder.userDetailsService(userDetailsService)
.passwordEncoder(encoder);
builder.jdbcAuthentication().dataSource(datasource);
userDetailsService.createUser(new User(...));
}
Is there any way to access from other beans the same AuthenticationManagerBuilder instance that is automatically passed to the configure() method?
The AuthenticationManagerBuilder is really only meant for building your authentication objects (i.e. UserDetails, AuthenticationProvider, AuthenticationManager). It is not intended to be used within the application itself.
Instead, I would recommend using the UserDetailsManager API. You can create a UserDetailsManager Bean, provide the UserDetailsManager to the AuthenticationManagerBuilder for creating the AuthenticationProvider & AuthenticationManager, then you can use the UserDetailsManager directly within your code.
Something like this:
#EnableWebMvcSecurity
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
...
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth, UserDetailsService uds) throws Exception {
auth
.userDetailsService(uds)
.passwordEncoder(new BCryptPasswordEncoder());
}
#Bean
public UserDetailsManager udm(DataSource dataSource) {
JdbcUserDetailsManager udm = new JdbcUserDetailsManager();
udm.setDataSource(dataSource);
return udm;
}
}
#Service
public class MyUserService {
#Autowired
UserDetailsManager udm;
public void createUser(...) {
//use builder here...
udm.createUser(new User(...));
}
}
One thing to note is that we leverage the global instance of AuthenticationManagerBuilder. In order to ensure the configureGlobal method is invoked before building the AuthenticationProvider and AuthenticationManager you need to have EnableGlobalAuthentication, EnableWebMvcSecurity or EnableWebSecurity annotations on the Configuration class (our example already does this).
I'm having the weirdest issue at hand. Having an application secured by spring-seucrity-oauth(2) the authentication suddenly stopped working and we can't find what's going wrong.
Well actually we can. It's the authenticationManager that is injected too late. When configuring the AuthorizationServerEndpointsConfigurer the authenticationManager is not yet injected. For that reason the AuthorizationServerEndpointsConfigurer creates a list of tokenGranters without the ResourceOwnerPasswordTokenGranter.
We have no idea on what could cause this behaviour or how we could solve or work around it.
Any advise or help is appreciated!
AuthorizationServerConfiguration.class:
#EnableAuthorizationServer
#Configuration
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Inject
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore)
.prefix("/api")
.authenticationManager(authenticationManager); // when this method is called authenticationManager is null
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// when I check authentionManager here, it is injected
}
This is how the authenticationManager bean is exposed and created.
SecurityConfigurer.class:
#Configuration
#EnableWebSecurity
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}