Spring EnableAuthorizationServer with custom AuthenticationManager - spring-security

I am setting up an OAuth2 + OpenID connect server using Spring security. I have been trying to use the automatic /oauth/token & /oauth/authorize endpoints that are defined when you use the #EnableAuthorizationServer annotation on a class.
#Configuration
#EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter{
In the same class, I have autowired an AuthenticationManager to use in configuring the AuthorizationServerEndpointsConfigurer. I have debugged & confirmed that the correct bean is being autowired.
#Autowired
private AuthenticationManager authMan;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception{
endpoints
.tokenStore(tokenStore())
.userApprovalHandler(userApprovalHandler())
.authenticationManager(authMan);
}
The problem is, there are two WebSecurityConfigurers being created, the one I defined and what appears to be the default WebSecurityConfigurer. Here is part of the one I defined:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
private UserDetailsSrvc detailsSrvc;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider
= new DaoAuthenticationProvider();
authProvider.setUserDetailsService(detailsSrvc);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
#Autowired
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Override
#Bean(name="myAuthenticationManager")
public AuthenticationManager authenticationManagerBean() throws Exception {
return authenticationManager();
}
Unfortunately, the default is being called when I navigate to localhost:8080/outh/token with my browser. I can tell because my custom UserDetailsService is not being used during the authentication, and because I put a breakpoint on the getWebSecurityConfigurers method in org.springframework.security.config.annotation.web.configuration.AutowiredWebSecurityConfigurersIgnoreParents:
#SuppressWarnings({ "rawtypes", "unchecked" })
public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() {
List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new ArrayList<SecurityConfigurer<Filter, WebSecurity>>();
Map<String, WebSecurityConfigurer> beansOfType = beanFactory
.getBeansOfType(WebSecurityConfigurer.class);
for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) {
webSecurityConfigurers.add(entry.getValue());
}
return webSecurityConfigurers;
}
The beansOfType map has two entries, but only if I have a class with the #EnableAuthorizationServer annotation. (Only 1 if I comment out annotation)
How do I get my AuthorizationServerConfigurerAdapter (or whatever is actually processing the requests to /oauth/token) to use the WebSecurityConfigurer defined in my WebSecurityConfigurerAdapter? I believe I can get around this issue by defining my own endpoints, and maybe that's the only solution, but I was hoping to utilize the default endpoints.

Related

Securing Spring Boot with vaadin

Instead of using InMemory Auth I am trying to authenticate with BD user, so a created class user and roles, and added this code to this class
#EnableWebSecurity
#Configuration
public class SecurityConfig extends VaadinWebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetails customUserDetails;
#Autowired
CustomAuthenticationProvider customAuthenticationProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
// Set default security policy that permits Vaadin internal requests and
// denies all other
super.configure(http);
setLoginView(http, LoginView.class, "/logout");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//auth.inMemoryAuthentication().withUser("user").password("{noop}userpass").roles("USER");
auth.userDetailsService(customUserDetails).passwordEncoder(passwordEncoder());
}
#Bean
public DaoAuthenticationProvider createDaoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(customUserDetails);
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
}
But when i try to authenticate it does not accept
There is more than one way to do this, but usually it goes roughly like this. The relevant pieces, the complete solution is too lengthy for SO. There should not be anything Vaadin specific, but just follow Spring documentation on the matter.
#EnableWebSecurity
#Configuration
public class SecurityConfig extends VaadinWebSecurityConfigurerAdapter {
...
private final UserDetailsService userDetailsService;
#Autowired
public SecurityConfig (UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
...
}
And then implement UserDetailsService according to Spring spec.
#Service
#Primary
public class UserDetailsServiceImpl implements UserDetailsService {
...
#Autowired
private UserRepository repository; // or what ever you have named it ...
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// implement this according to your actual implementation of user database
}
}

How to add an additional AuthenticationProvider without using WebSecurityConfigurerAdapter

Prior to Spring Security 5.7 it was possible to add additional AuthenticationProviders to the global AuthenticationManager this way:
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
...
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider);
}
}
With Spring Security 5.7 the WebSecurityConfigurerAdapter was deprecated.
Question: ho should i migrate this code to solve the deprecation?
When i try to register the additional AuthenticationProvider as #Bean, the autocreated authentication provider for username/password based authentication gets replaced, leading to
No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken
I read the blog post https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter but found no hints about adding additional authentication providers to the global AuthenticationManager.
If you have a single AuthenticationProvider you can register it as a bean and it will be picked up by Spring Security:
#Bean
public CustomAuthenticationProvider customAuthenticationProvider() {
return new CustomAuthenticationProvider();
}
Alternatively, you can add additional AuthenticationProviders in the HttpSecurity configuration:
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.authenticationProvider(new CustomAuthenticationProvider());
return http.build();
}
You can annotate your configuration class with #EnableGlobalAuthentication and will be able to configure a global instance of AuthenticationManagerBuilder:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(customAuthenticationProvider);
}
Please see the related documentation: https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/config/annotation/authentication/configuration/EnableGlobalAuthentication.html
I had a same problem when I want to add an custom AuthenticationProvider using Spring Security without WebSecurityConfigurerAdapter.
Here is what I did.
Code with WebSecurityConfigurerAdapter
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
...
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider);
}
}
Code without WebSecurityConfigurerAdapter
#EnableWebSecurity
#EnableGlobalAuthentication
public class SecurityConfiguration {
...
#Autowired
CustomAuthenticationProvider customAuthenticationProvider;
#Autowired
void registerProvider(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(customAuthenticationProvider);
}
}
Note: #EnableGlobalAuthentication and registerProvider().
Hope this will help.
I had a similar problem. I have a custom user details service and I also use an additional custom authentication provider. One is for actual users and the custom provider is for automated devices.
This is my code with the WebSecurityConfigurerAdapter:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
...
#Autowired
private MyCustomAuthenticationProvider customAuthenticationProvider;
#Autowired
private UserDetailsService userDetailsService;
...
#Bean
PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
var encoder = passwordEncoder();
customAuthenticationProvider.encoder(encoder);
auth.userDetailsService(userDetailsService).passwordEncoder(encoder);
auth.authenticationProvider(customAuthenticationProvider);
}
...
}
This is my code without the WebSecurityConfigurerAdapter:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {
...
#Autowired
private MyCustomAuthenticationProvider customAuthenticationProvider;
#Autowired
private UserDetailsService userDetailsService;
...
#Bean
PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
#Bean
AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
var encoder = passwordEncoder();
customAuthenticationProvider.encoder(encoder);
auth.userDetailsService(userDetailsService).passwordEncoder(encoder);
auth.authenticationProvider(customAuthenticationProvider);
}
...
}
note: you might need to set spring.main.allow-circular-references to true in your properties file for this to work.

authenticationFilter is not getting invoked in spring security

I having requirement of passing extra parameter along with username and password in spring security authentication process. After reading several threads I added custom authentication filter in spring security chain
below are my files
Filter class
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String role = request.getParameter("role");
request.getSession().setAttribute("role", role);
return super.attemptAuthentication(request, response);
}
SecurityConfig class
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
RestAuthenticationSuccessHandler customizeAuthenticationSuccessHandler;
#Autowired
RestAuthenticationFailureHandler restAuthenticationFailureHandler;
#Autowired
UserDetailsService userDetailsService;
#Autowired
PasswordEncoder passwordEncoder;
#Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
#Autowired
private AccessDeniedHandler restAccessDeniedHandler;
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests().antMatchers("/api/common/**").permitAll()
.antMatchers("/api/student/**").access("hasRole('ROLE_STUDENT')")
.antMatchers("/api/staff/**").access("hasRole('ROLE_STAFF')").antMatchers("/sysAdmin/**").access("hasRole('ROLE_ADMIN')").and().formLogin()
.loginProcessingUrl("/api/common/login")
.successHandler(customizeAuthenticationSuccessHandler)
.failureHandler(restAuthenticationFailureHandler)
.usernameParameter("userName")
.passwordParameter("password")
.and().exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint).accessDeniedHandler(restAccessDeniedHandler)
.and().csrf().disable();
}
#Bean
public UsernamePasswordAuthenticationFilter authenticationFilter() throws Exception {
AuthenticationFilter authFilter = new AuthenticationFilter();
authFilter.setUsernameParameter("username");
authFilter.setPasswordParameter("password");
authFilter.setAuthenticationManager(authenticationManagerBean());
return authFilter;
}
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider());
}
public AuthenticationProvider authProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder);
return provider;
}
}
Problem:
When I tried to authenticate user attemptAuthentication method of my custom filter class is not getting invoked and call directly go to UserDetails service class
Added FilterProcessingUrl to authenticateFilter object resolved the issue.
Updated authenticationFilter() method :
#Bean
public AuthenticationFilter authenticationFilter() throws Exception {
AuthenticationFilter authFilter = new AuthenticationFilter();
authFilter.setUsernameParameter("userName");
authFilter.setPasswordParameter("password");
authFilter.setFilterProcessesUrl("/api/common/login");
authFilter.setAuthenticationSuccessHandler(customizeAuthenticationSuccessHandler);
authFilter.setAuthenticationFailureHandler(restAuthenticationFailureHandler);
authFilter.setAuthenticationManager(authenticationManagerBean());
return authFilter;
}

spring-security redirect 404 error

I'm using spring boot security as ACL for my restful services.
The security adapter as below
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableRedisHttpSession
#Order(2)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private MyUserDetailsService userDetailsService;
#Bean
public HttpSessionStrategy httpSessionStrategy() {
return new HeaderHttpSessionStrategy();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and().csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and().userDetailsService(userDetailsService);
}
}
The snap of userdetailservice
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Yuangong yuangong = yuangongService.getYuangongByNo(username).getData();
List<SimpleGrantedAuthority> grantedAuthorities = new ArrayList<SimpleGrantedAuthority>();
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_ALL"));
return new User(yuangong.getNo(), yuangong.getPassword(), grantedAuthorities);
}
The endpoint annotated by #RestController, and the method in endpoint like
#RestController
#RequestMapping(path = "/bumen")
public class BumenEndpoint {
// #PermitAll
#PreAuthorize("hasRole('ROLE_ALL')")
#RequestMapping(path = "/getBumenTreeList", method = RequestMethod.GET )
public HttpResult<List<Map<String, Object>>> getBumenTreeData(Principal principal) {
System.out.println(principal.getName());
return new HttpResult(bumenService.getBumenTreeList());
}
If I use #permitAll, it worked find and return the right JSON response. If using #PreAuthorize("hasRole('ROLE_ALL')"), it can pass the auth and can debug into this method, but the response will be redirected to "/bumen/bumen/getBumenTreeList" (double '/bumen') with 404 error.
if I don't implements the BumenEndpoint, there will not being redirected and return the right response.
I'm not sure which part cause the redirecting.
The issue was caused by the annotation. I have fixed it as per this Spring-MVC Problem using #Controller on controller implementing an interface

How can I get a custom Principal object with Spring using OAuth2?

I have a Spring Boot application utilizing spring-security-jwt and spring-security-oauth2. I've got a custom User object extending UserDetails and a Custom UserDetailsService returning this object from the loadUserByUsername method.
But when I utilize the getPrincipal method of the Authentication object and try to Cast to my custom user object, it fails as the principal is returning a string vs my custom user object.
My actual goal is to eliminate the trip to the persistence layer on every method call that requires the custom object detail which is most.
You can do this by setting an AccessTokenConverter (which indirectly holds your UserDetailsService) to JwtAccessTokenConverter. See accessTokenConverter() method.
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
// Other configurations omitted
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter())
.tokenEnhancer(accessTokenConverter())
.authenticationManager(authenticationManager);
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
DefaultUserAuthenticationConverter duac = new DefaultUserAuthenticationConverter();
duac.setUserDetailsService(userDetailsService);
DefaultAccessTokenConverter datc = new DefaultAccessTokenConverter();
datc.setUserTokenConverter(duac);
JwtAccessTokenConverter jatc = new JwtAccessTokenConverter();
jatc.setAccessTokenConverter(datc); // IMPORTANT
jatc.setSigningKey("your-signing-key");
return jatc;
}
#Bean
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(tokenStore());
tokenServices.setSupportRefreshToken(true);
return tokenServices;
}
}

Resources