Why following basic security configurations do not apply inMemoryAuthentication() clause?
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.httpBasic()
.and()
.authorizeRequests()
.anyRequest().authenticated();
super.configure(http);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("username").password("password");
super.configure(auth);
}
}
After the application initialization, there is still only default user generated by Spring itself, there is no such user like username.
Do not call super method from void configure(AuthenticationManagerBuilder auth). It sets disableLocalConfigureAuthenticationBldr flag to true that leads to your AuthenticationManagerBuilder being ignored. Finally your void configure(AuthenticationManagerBuilder auth) method should look like this:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("username").password("password").roles("USER");
}
In spring boot 2.x, you will have to implement your own UserDetailsService, as described here and here
Example:
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger log = LogManager.getLogger();
#Override
protected void configure(HttpSecurity http) throws Exception {
// Note:
// Use this to enable the tomcat basic authentication (tomcat popup rather than spring login page)
// Note that the CSRf token is disabled for all requests
log.info("Disabling CSRF, enabling basic authentication...");
http
.authorizeRequests()
.antMatchers("/**").authenticated() // These urls are allowed by any authenticated user
.and()
.httpBasic();
http.csrf().disable();
}
#Bean
public UserDetailsService userDetailsService() {
// Get the user credentials from the console (or any other source):
String username = ...
String password = ...
// Set the inMemoryAuthentication object with the given credentials:
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
String encodedPassword = passwordEncoder().encode(password);
manager.createUser(User.withUsername(username).password(encodedPassword).roles("USER").build());
return manager;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Related
I am new to Spring Security and Oauth2. In my spring boot application, I have implemented authentication with Oauth2 with following set of changes:
Custom Ouath2 User service is as follows:
#Component
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
private UserRepository userRepository;
#Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
...
}
}
Security Configuration is as follows:
#EnableWebSecurity
#Import(SecurityProblemSupport.class)
#ConditionalOnProperty(
value = "myapp.authentication.type",
havingValue = "oauth",
matchIfMissing = true
)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final CustomOAuth2UserService customOAuth2UserService;
public SecurityConfiguration(CustomOAuth2UserService customOAuth2UserService) {
this.customOAuth2UserService = customOAuth2UserService;
}
#Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**")
.antMatchers("/app/**/*.{js,html}")
.antMatchers("/bundle.js")
.antMatchers("/slds-icons/**")
.antMatchers("/assets/**")
.antMatchers("/i18n/**")
.antMatchers("/content/**")
.antMatchers("/swagger-ui/**")
.antMatchers("/swagger-resources")
.antMatchers("/v2/api-docs")
.antMatchers("/api/redirectToHome")
.antMatchers("/test/**");
}
public void configure(HttpSecurity http) throws Exception {
RequestMatcher csrfRequestMatcher = new RequestMatcher() {
private RegexRequestMatcher requestMatcher =
new RegexRequestMatcher("/api/", null);
#Override
public boolean matches(HttpServletRequest request) {
return requestMatcher.matches(request);
}
};
http.csrf()
.requireCsrfProtectionMatcher(csrfRequestMatcher)
.and()
.authorizeRequests()
.antMatchers("/login**").permitAll()
.antMatchers("/manage/**").permitAll()
.antMatchers("/api/auth-info").permitAll()
.antMatchers("/api/**").authenticated()
.antMatchers("/management/health").permitAll()
.antMatchers("/management/info").permitAll()
.antMatchers("/management/prometheus").permitAll()
.antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
.anyRequest().authenticated()//.and().oauth2ResourceServer().jwt()
.and()
.oauth2Login()
.redirectionEndpoint()
.baseUri("/oauth2**")
.and()
.failureUrl("/api/redirectToHome")
.userInfoEndpoint().userService(oauth2UserService())
;
http.cors().disable();
}
private OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
return customOAuth2UserService;
}
}
Content of application.properties is as follows:
spring.security.oauth2.client.registration.keycloak.client-id=abcd
spring.security.oauth2.client.registration.keycloak.client-name=Auth Server
spring.security.oauth2.client.registration.keycloak.scope=api
spring.security.oauth2.client.registration.keycloak.provider=keycloak
spring.security.oauth2.client.registration.keycloak.client-authentication-method=basic
spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code
myapp.oauth2.path=https://internal.authprovider.com/oauth2/
spring.security.oauth2.client.provider.keycloak.token-uri=${myapp.oauth2.path}token
spring.security.oauth2.client.provider.keycloak.authorization-uri=${myapp.oauth2.path}authorize
spring.security.oauth2.client.provider.keycloak.user-info-uri=${myapp.oauth2.path}userinfo
spring.security.oauth2.client.provider.keycloak.user-name-attribute=name
myapp.authentication.type=oauth
Now, with the existing authentication mechanism, I would like to add support for multiple authentication providers: LDAP, Form-Login, etc.
In this regard, I have gone through a few articles:
https://www.baeldung.com/spring-security-multiple-auth-providers
Custom Authentication provider with Spring Security and Java Config
But, I am not getting any concrete idea regarding what changes should I do in the existing code base in order to achieve this.
Could anyone please help here? Thanks.
I've created a simplified setup starting from your code with support for both OAuth2 and Basic Auth.
/tenant2/** will start a basic authentication.
/** (everything else) triggers an OAuth2 Authorization Code authentication.
The key to achieve this is to have one #Configuration class per authentication type.
Let's start with the controllers:
Tenant1HomeController
#Controller
public class Tenant1HomeController {
#GetMapping("/tenant1/home")
public String home() {
return "tenant1Home";
}
}
Tenant2HomeController
#Controller
public class Tenant2HomeController {
#GetMapping("/tenant2/home")
public String home() {
return "tenant2Home";
}
}
Now, the configuration classes:
Tenant1SecurityConfiguration
#Configuration
public class Tenant1SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/login**").permitAll()
.antMatchers("/manage/**").permitAll()
.antMatchers("/api/auth-info").permitAll()
.antMatchers("/api/**").authenticated()
.antMatchers("/management/health").permitAll()
.antMatchers("/management/info").permitAll()
.antMatchers("/management/prometheus").permitAll()
.antMatchers("/management/**").hasAuthority("ADMIN")
.antMatchers("/tenant1/**").authenticated()
.and()
.oauth2Login()
.and()
.cors()
.disable();
}
}
Tenant2SecurityConfiguration (Notice the #Order(90), that's important
#Order(90)
#Configuration
public class Tenant2SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatcher(new AntPathRequestMatcher("/tenant2/**"))
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/tenant2/**").hasAuthority("BASIC_USER")
.and()
.httpBasic();
http.cors().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user")
.password("{noop}password")
.roles("BASIC_USER");
}
}
Finally the configuration:
spring:
security:
oauth2:
client:
registration:
keycloak:
client-id: myclient
client-secret: c6dce03e-ea13-4b76-8aab-c876f5c2c1d9
provider:
keycloak:
issuer-uri: http://localhost:8180/auth/realms/myrealm
With this in place, if we hit http://localhost:8080/tenant2/home, will be prompted with the basic auth popup:
Trying with http://localhost:8080/tenant1/home sends you to Keycloak's login form:
UPDATE:
It's completely viable to configure a multitenant application with the configuration above.
The key would be that each authentication provider works with a different set of users (tenants), e.g.:
TENANT 1 (OAuth2 authentication):
#Configuration
public class Tenant1SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.
...
.and()
.oauth2Login()
.and()
...
This first subset of users is federated by the OAuth2 provider, Keycloak in this case.
TENANT 2 (Basic / form /xxx authentication):
#Order(90)
#Configuration
public class Tenant2SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
...
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(XXX)
For the second tenant, you can use a userDetailsService that points to a different repository of users (LDAP, database...).
I have seen a lot of posts on the same problem, but no solution worked for me.
I have an API secured with spring security as below.
#EnableWebSecurity
#Component
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired private UserService userService;
public SecurityConfiguration() {
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/actuator/shutdown", "/api/register");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and().httpBasic();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userService)
.passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
I need endpoints /api/register and /actuator/shutdown available without authentication. But, as it turned out, all the endpoints are returning the same 401 status code.
try with this.
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
//no authentication needed for these context paths
.authorizeRequests()
.antMatchers("/your Urls that dosen't need security/**").permitAll()
We implemented a similar approach as mentioned by Supun Above,
http
.authorizeRequests()
.antMatchers(AUTH_WHITELIST).permitAll()
.anyRequest().authenticated()
.and().httpBasic()
You can keep 'AUTH_WHITELIST' as below to keep adding multiple Endpoints
private static final String[] AUTH_WHITELIST = {
// -- swagger ui
"/api/register",
"/actuator/shutdown"
};
My Spring Boot Actuator healthCheck is blocked because of a (pre_authenticated) token is missing.
There are many answers available, BUT this is question has interference with pre-authenticated security. As far as I searched, this is NOT a duplicate.
How can I allow the health check in a pre-authenticated security environment?
My question is also, do I need more settings (in e.g. the application.properties)?
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
provider.setPreAuthenticatedUserDetailsService(new XyzPreAuthenticatedGrantedAuthoritiesUserDetailsService());
auth.authenticationProvider(provider);
}
// Try-1, see below
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(xyzTokenRequestHeaderAuthenticationFilter(), RequestHeaderAuthenticationFilter.class)
.csrf().disable()
.authorizeRequests()
.antMatchers("/actuator/**").permitAll()
.anyRequest().authenticated();
}
#Bean
public XyzTokenRequestHeaderAuthenticationFilter xyzTokenRequestHeaderAuthenticationFilter() throws Exception {
XyzTokenRequestHeaderAuthenticationFilter filter = new XyzTokenRequestHeaderAuthenticationFilter();
filter.setAuthenticationManager(authenticationManager());
return filter;
}
}
My second try was:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(xyzTokenRequestHeaderAuthenticationFilter(), RequestHeaderAuthenticationFilter.class)
.csrf().disable()
.authorizeRequests()
.antMatchers("/actuator/**").permitAll();
}
It looks like the xyz filter is not implemented in the 'perfect' way.
This way will help you get things workin':
1 - use the management port:
management.server.port=8081
management.security.enabled=false
management.server.address=127.0.0.1
management.server.ssl.enabled=false
management.endpoints.health.sensitive=false
management.endpoint.health.show-details=always
2 - configure both ways web and api. Use this beyond the standard parts:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(STATELESS);
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/actuator/**").permitAll()
.antMatchers("/**").authenticated();
http.addFilterBefore(xyzTokenRequestHeaderAuthenticationFilter(), AbstractPreAuthenticatedProcessingFilter.class);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/actuator/**");
}
3 - Inside the Docker container, use the 8081 port for the healthCheck.
Try to add in .ignoring() and add #EnableGlobalMethodSecurity(prePostEnabled = true), #Configuration at class
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter implements WebMvcConfigurer{
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/actuator/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/actuator/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(xyzTokenRequestHeaderAuthenticationFilter(), RequestHeaderAuthenticationFilter.class);
}
}
The problem seems to be with your XyzTokenRequestHeaderAuthenticationFilter implementation. If you wrote that by extending RequestHeaderAuthenticationFilter, then you must set the property exceptionIfHeaderMissing to false.
If you didn't extend that Spring Security pre auth core class then you need to show the implementation.
I am using jdbc template to authenticate the user and in memory to
authorize client for spring boot application and i want to connect
database and and store the in memory token into database and check
each and every time over there when check request on postman.
I don't want to use hibernate and and using jdbctemplate can we able
to store the token not client name and secret key.
note:authentication working fine.
#EnableResourceServer
#Configuration
public class ResourceServerConfig extends WebSecurityConfigurerAdapter{
#Autowired
private UserDetailsService customUserDetailsService;
#Autowired
private Master master;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers().antMatchers("/home/**")
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.csrf()
.disable()
.formLogin()
.permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
master.setJdbcTemplate();
auth.jdbcAuthentication().dataSource(master.jdbcTemplate.getDataSource())
.usersByUsernameQuery(
"Select a.UserName,a.password,a.enable from [Auth_User] a where username=?")
.authoritiesByUsernameQuery(
"select a.UserName,a.role from [Auth_User] a where username=?");
.passwordEncoder(new BCryptPasswordEncoder());
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
///////////////////////authorization i need to change the code here to store the generated token in database and validate against it//////////////////////////////////
#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("{noop}secret")
.authorizedGrantTypes("authorization_code","password","refresh_token")
.scopes("user_info")
.autoApprove(true)
.accessTokenValiditySeconds(1*60);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
}
}
instead of Jdbc i used Jpa and spring security either using JWT or using Oauth2 i used Oauth2 and this is the link i referred
https://github.com/TechPrimers/spring-security-oauth-mysql-example
I've been developing Spring Cloud (with Netflix OSS stack) microservices architecture for some time. As you would expect, I've separated authorization server as a stand alone microservice. My front end application uses "password" grant type for user login purposes. However, I'm using "client-credentials" grant type for the rest calls that I make from front-end service to other back-end services. Client-credentials grant type is being used among other back-end services as well. By doing so, I am not able to get who is the actual invoker (currently logged in user) of a request. Is there a way to inject authentication and authorization info of the principal to the token that is being issued in client-credentials grant?
My Authorization Server Config class
#Configuration
#EnableAuthorizationServer
#Order(Ordered.HIGHEST_PRECEDENCE)
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("testclient")
.secret("{noop}testsecret")
.authorizedGrantTypes("authorization_code","password","client_credentials")
.scopes("ui")
.autoApprove(true)
// .accessTokenValiditySeconds(3600)
.and()
.withClient("backend-service")
.secret("{noop}backendsecret")
.authorizedGrantTypes("client_credentials","refresh_token")
.scopes("server")
.autoApprove(true)
}
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
endpoints.tokenEnhancer(tokenEnhancer());
endpoints.tokenStore(tokenStore());
}
#Bean
public TokenStore tokenStore() {
//return new JdbcTokenStore(dataSource);
return new InMemoryTokenStore();
}
#Bean
#Primary
public AuthorizationServerTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenEnhancer(tokenEnhancer());
tokenServices.setTokenStore(tokenStore());
return tokenServices;
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
Security Config Class
#Configuration
#Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.antMatchers("/resources/**", "/src/main/webapp/**","/css/**","/images/**").permitAll()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll().and().httpBasic().disable();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/static/**","/resources/**", "/src/main/webapp/**","/css/**","/images/**");
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("admin").password("{noop}a1b2c3#").roles("User");
}
}
I've tried to implement a Token Enhancer class to propogate additional data in token. However, I don't think this is the correct and secure way for what I'm trying to achieve.
public class CustomTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
final Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("customInfo", "testdata");
((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(additionalInfo);
return oAuth2AccessToken;
}
}
Your assistance would be appreciated.
If you are using an oauth token generated using Client Credentials then you can not get user information. You can only get source of the request (client).
If want user information across micro services then you have to use password grant type to generate oauth token.