I'm trying to create an oauth2-enabled auth server which is able to authenticate users with two authentication providers: the former is in-memory (for default user-passwords) the latter is an external LDAP server (by now i'm using the example from gs-authenticating-ldap-complete).
I'm able to successfully retrieve an access token for any user, but i'm only able to use the refresh token for retrieving a new token for any user that is registered in the LDAP server. While everything is fine if I try to refresh an in-memory user's token, with the LDAP ones I get: 401 Unauthorized { "error": "unauthorized", "error_description": "ben" } where "ben" is the user id.
As far as I know (after some debugging) the exception occurs in DefaultTokenServices.java:150.
In the following I report the configuration classes I'm using.
#Configuration
#EnableWebSecurity
#Order(6)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().csrf().disable();
}
#Configuration
protected static class DefaultUsersAuthConfiguration extends GlobalAuthenticationConfigurerAdapter {
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN").and().withUser("guest")
.password("guest").roles("USER");
}
}
#Configuration
protected static class LDAPAuthConfiguration extends GlobalAuthenticationConfigurerAdapter {
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication().userDnPatterns("uid={0},ou=people").groupSearchBase("ou=groups")
.userDetailsContextMapper(new MyLdapUserDetailsMapper()).contextSource()
.ldif("classpath:test-server.ldif");
}
}
protected static class MyLdapUserDetailsMapper extends LdapUserDetailsMapper {
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
Collection<? extends GrantedAuthority> authorities) {
final UserDetails originalUser = super.mapUserFromContext(ctx, username, authorities);
final Set<GrantedAuthority> newAuth = new HashSet<>(originalUser.getAuthorities());
newAuth.add(new SimpleGrantedAuthority("ROLE_EXTRA_ROLE"));
return new User(originalUser.getUsername(), originalUser.getPassword(), originalUser.isEnabled(),
originalUser.isAccountNonExpired(), originalUser.isCredentialsNonExpired(),
originalUser.isAccountNonLocked(), newAuth);
}
}
}
#Configuration
#EnableAuthorizationServer
public class OAuth2Config extends OAuth2AuthorizationServerConfiguration {
#Autowired
private TokenStore tokenStore;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("acme").secret("acmesecret")
.authorizedGrantTypes("password", "refresh_token", "client_credentials")
.scopes("read", "write", "openid").autoApprove(true);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore);
}
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Autowired
private TokenStore tokenStore;
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.authorizeRequests().antMatchers("/me").authenticated();
// #formatter:on
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(tokenStore);
}
}
}
I'm using spring-boot 1.3.2.RELEASE. What am I missing?
Related
Well, I am currently programming on a project with Spring Security where I'd like to register a user in the database.
The proble is, if I vistit the endpoint where the registration should happen I get redirected to the default Login page of Spring Boot which should not happen.
This is my SecurityConfiguration.java
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Bean
public BCryptPasswordEncoder encodePasswd(){
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/").permitAll();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/**");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
}
}
My RestController called UserController.java looks like this:
#RestController
#RequestMapping("/secure/rest")
public class UserController {
#Autowired
private UserRepository userRepository;
private BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
#PostMapping("user/register")
public String addUser(#RequestBody User user){
String passwd = user.getPasswordUser();
String encryptPasswd = passwordEncoder.encode(passwd);
user.setPasswordUser(encryptPasswd);
userRepository.save(user);
return "addedUser";
}
}
Try this.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().disable();
}
}
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsInfoService userDetailsService;
#Autowired
private CustomLoginAuthenticationProvider authenticationProvider;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
auth.userDetailsService(userDetailsService);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/api/user/authenticate/**")
.antMatchers("/api/user/**")
.antMatchers("/api/master/**")
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/* #EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
private static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}*/
}
This is my spring security configuration in the application.
It works fine most of the scenarios, but not able to track one issue.
Whenever I call a web service with a date format spring security returns 401 error.
http://localhost:9190/nazouki/api/master/create
with input
{"orgId":"11","birthDate":"2020-04-20T20:00:00.000Z"}
without date it works fine.
Controller code
#RequestMapping(value = "master/create", method = RequestMethod.POST)
public #ResponseBody ResultDecorator createMaster(#RequestBody TuMasterDto tuMasterDto){ handler.resolveResult(masterService.createMaster(tuMasterDto), OperationEnum.SAVE);
}
Exception has been thrown from the CustomAuthenticationEntryPoint in the spring security.
#Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final Logger log = LoggerFactory.getLogger(CustomAuthenticationEntryPoint.class);
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException ae) throws IOException, ServletException {
log.info("Pre-authenticated entry point called. Rejecting access");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Access Denied");
}
}
We can't say for sure unless we also have a look at the handler.resolveResult() and the masterService.createMaster() methods.
My initial assumption would be that you have a GlobalMethodSecurity configuration on one or both of these methods (or a code path that is used by them), and since you ignore the security for the path web.ignoring().antMatchers("/api/master/**"), there is no SecurityContext created and therefore you get that response.
As to why it behaves differently when you add the date than without, I'm not so sure myself. Maybe you have 2 implementations? something like this
public void handleRequest(Dto dto) {
if(dto.getDate() != null) {
doSomethingWithTheDate(dto.getDate());
}
// handle normally
}
#PreAuthorize("hasRole('ROLE_USER')")
public void doSomethingWithTheDate(Date date) {
...
}
I managed to configure OAuth2 and ldap authorization. Created custom LdapUser by implementing LdapUserDetails and CustomUserDetailsContextMapper by implementing UserDetailsContextMapper.
Eventually, I get access token when authorize by Active Directory username and password.
But the problem is, I cannot get my current logged user from SecurityContextHolder.getContext().getAuthentication() as it says
java.lang.String cannot be cast to LdapUser
Below my security configure:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(adAuthenticationProvider())
.ldapAuthentication()
.userSearchBase("ldap.searchbase").userSearchFilter("ldap.filter").groupSearchFilter("ldap.groupsearch")
.contextSource(contextSource())
.userDetailsContextMapper(userDetailsContextMapper())
.passwordCompare()
.passwordEncoder(new LdapShaPasswordEncoder())
.passwordAttribute("userPassword");
}
#Bean
public DefaultSpringSecurityContextSource contextSource() {
return new DefaultSpringSecurityContextSource(Arrays.asList("ldap.url"), "dc=smth,dc=com");
}
#Bean
public ActiveDirectoryLdapAuthenticationProvider adAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider("smth.com","ldap.url");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
provider.setUserDetailsContextMapper(userDetailsContextMapper());
return provider;
}
#Bean
public UserDetailsContextMapper userDetailsContextMapper() {
return new CustomUserDetailsContextMapper();
}
Custom LdapUser:
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.ldap.userdetails.LdapUserDetails;
import java.util.Collection;
public class LdapUser implements LdapUserDetails
{
private String commonName;
private LdapUserDetails ldapUserDetails;
public LdapUser(LdapUserDetails ldapUserDetails) {
this.ldapUserDetails = ldapUserDetails;
}
#Override
public String getDn() {
return ldapUserDetails.getDn();
}
#Override
public void eraseCredentials() {
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return ldapUserDetails.getAuthorities();
}
#Override
public String getPassword() {
return ldapUserDetails.getPassword();
}
#Override
public String getUsername() {
return ldapUserDetails.getUsername();
}
#Override
public boolean isAccountNonExpired() {
return ldapUserDetails.isAccountNonExpired();
}
#Override
public boolean isAccountNonLocked() {
return ldapUserDetails.isAccountNonLocked();
}
#Override
public boolean isCredentialsNonExpired() {
return ldapUserDetails.isCredentialsNonExpired();
}
#Override
public boolean isEnabled() {
return ldapUserDetails.isEnabled();
}
}
CustomUserDetailsContextMapper:
I can print out context attributes successfully and I see that this is my logged user
#Configuration
public class CustomUserDetailsContextMapper extends LdapUserDetailsMapper implements UserDetailsContextMapper {
private LdapUser ldapUser = null;
private String commonName;
private Boolean isCity;
#Override
public LdapUserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
Attributes attributes = ctx.getAttributes();
LdapUserDetails ldapUserDetails = (LdapUserDetails) super.mapUserFromContext(ctx,username,authorities);
return new LdapUser(ldapUserDetails);
}
#Override
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
}
}
And now this is how i want to get custom LdapUser:
public LdapUser getCurrentLdapUser() {
org.springframework.security.core.context.SecurityContext securityContext = SecurityContextHolder
.getContext();
Authentication authentication = securityContext.getAuthentication();
LdapUser user = null;
if (authentication != null) {
user = ((LdapUser) authentication.getPrincipal());
}
return user;
}
After this function is called I get casting error. When I try to get principal name it returns - anonymousUser. I have no idea why it is not returning me LdapUser
Okay, I got the answer. Missed the basic things.
Since I did not configure Resource Server (ResourceServerConfigurerAdapter) every logged Active Directory user considered as anonymous. That is why, Security Context was returning String user instead of my custom Ldap user.
Here is an example ResourceServerConfig in case somebody will need:
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "resource_id";
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources
.resourceId(RESOURCE_ID)
.stateless(false);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.anonymous().disable()
.authorizeRequests()
.antMatchers("/api/**").hasAnyAuthority("Authority_1","Authority_2")
.and().exceptionHandling().authenticationEntryPoint(new UnauthorizedHandler())
.and().exceptionHandling().accessDeniedHandler(accessDeniedHandler());
}
#Bean
public AccessDeniedHandler accessDeniedHandler() {
return new CustomAccessDeniedHandler();
}
}
Ok guys, I hope you can help me, this is my last attempt. I am quite new to this spring security world and I cant get this to work. I tried many things, followed many tutorials and nothing.
The problem is as you saw in the title, make a custom user details service to work. It just not logs in, It appears that the customuserdetailsservice is not being called, as the sysouts are not showing in the console...
It works as a charm with spring security in memory features. Below are my codes.
Spring Security Config:
#Configuration
#EnableWebSecurity
#ComponentScan(basePackageClasses = CustomUserDetailsService.class)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsService userDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
//auth.inMemoryAuthentication().withUser("ram").password("ram123").roles("ADMIN");
auth.userDetailsService(userDetailsService).passwordEncoder(passwordencoder());
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/bower_components/**", "/resources/**", "/img/**"); // #3
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().csrf().disable()
.authorizeRequests()
.antMatchers("/", "/home", "/call").permitAll() // #4
.antMatchers("/resource", "/video").hasRole("USER") // #6
.anyRequest().authenticated();
}
#Bean(name="passwordEncoder")
public PasswordEncoder passwordencoder(){
return new BCryptPasswordEncoder();
}
}
CustomUserDetailsService
#Service("customUserDetailsService")
public class CustomUserDetailsService implements UserDetailsService{
private UserService userService;
#Override
public UserDetails loadUserByUsername(String ssoId)
throws UsernameNotFoundException {
User user = userService.findByUsername(ssoId);
System.out.println("User : "+user);
if(user==null){
System.out.println("User not found");
throw new UsernameNotFoundException("Username not found");
}
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
user.isEnabled(), true, true, true, getGrantedAuthorities(user));
}
private List<GrantedAuthority> getGrantedAuthorities(User user){
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(user.getRole()));
System.out.print("authorities :"+authorities);
return authorities;
}
}
Initializer Class
#SpringBootApplication
#EnableWebSocket
public class One2OneCallApp implements WebSocketConfigurer {
#Bean
public CallHandler callHandler() {
return new CallHandler();
}
#Bean
public UserRegistry registry() {
return new UserRegistry();
}
#Bean
public KurentoClient kurentoClient() {
return KurentoClient.create();
}
#Bean
public UiApplication uiApplication(){
return new UiApplication();
}
#Bean
public CustomUserDetailsService customUserDetailsService(){
return new CustomUserDetailsService();
}
#Bean
public SecurityConfig securityConfig(){
return new SecurityConfig();
}
#Bean
public EncryptPassword encryptPassword(){
return new EncryptPassword();
}
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(callHandler(), "/call");
}
public static void main(String[] args) throws Exception {
System.out.println("Iniciando");
new SpringApplication(One2OneCallApp.class).run(args);
}
}
I've also tested the communication with the database and it works perfectly fine. I'm seeking any help. Sorry for bad English. Thank you all!
Edit: Answered my own question down below.
In SecurityConfig class:
#Autowired
CustomUserDetailsService customUserDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService);
}
Change
#Autowired
UserDetailsService userDetailsService;
to
#Autowired
CustomUserDetailsService userDetailsService;
Also, import the security config in you web/socket config and move the component scan there, not on the security
#ComponentScan(basePackageClasses = CustomUserDetailsService.class)
#Import(value = { SecurityConfig.class })
public class WebConfig extends WebMvcConfigurerAdapter { /*...*/ }
You are setting hasRole(" ") in security config and you are using authorities for authentication.
instead of using .antMatchers("/resource", "/video").hasRole("USER")
use .antMatchers("/resource", "/video").hasAuthority("USER")
I ended up staying with the built in memory anthentication just for the presentation I had to do. I think my problem had to do with something in spring boot and the initialization in my application.
I am trying to go over the following documentation: https://github.com/spring-projects/spring-security-oauth/blob/f25592e682303b0cf89e1d7555174bac18e174df/docs/oauth2.md#mapping-user-roles-to-scopes
In the documentation, it says in order to map user roles to scopes, along with setting the checkUserScopes=true in the DefaultOAuth2RequestFactory, we need to add the TokenEndpointAuthenticationFilter filter after the HTTP BasicAuthenticationFilter. I was wondering how that could be done.
Here is what my AuthorizationServer looks like:
#Configuration
#EnableAuthorizationServer
protected static class OAuth2Config extends
AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private OAuth2RequestFactory requestFactory;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.authenticationManager(authenticationManager);
endpoints.requestFactory(requestFactory);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.withClientDetails(clientDetailsService());
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
throws Exception {
oauthServer.checkTokenAccess("isAuthenticated()");
}
#Bean
public ClientDetailsService clientDetailsService() {
Map<String, ClientDetails> clientDetailsStore = new HashMap<String, ClientDetails>();
Collection<String> scope = new HashSet<String>();
scope.add("user");
scope.add("admin");
Collection<String> authorizedGrantTypes = new HashSet<String>();
authorizedGrantTypes.add("password");
authorizedGrantTypes.add("refresh_token");
BaseClientDetails clientDetails = new BaseClientDetails();
clientDetails.setClientId("client");
clientDetails.setClientSecret("secret");
clientDetails.setScope(scope);
clientDetails.setAuthorizedGrantTypes(authorizedGrantTypes);
clientDetailsStore.put("client", clientDetails);
InMemoryClientDetailsService clientDetailsService = new InMemoryClientDetailsService();
clientDetailsService.setClientDetailsStore(clientDetailsStore);
return clientDetailsService;
}
#Bean
public OAuth2RequestFactory requestFactory() {
DefaultOAuth2RequestFactory requestFactory =
new DefaultOAuth2RequestFactory(clientDetailsService());
requestFactory.setCheckUserScopes(true);
return requestFactory;
}
}
Also, it would be fantastic to provide a sample CURL on how we can test the grant-type password.
Appreciate any help!
Instead of using #EnableAuthorizationServer you should be able to extend AuthorizationServerSecurityConfiguration and include that in your Spring configuration. E.g.
#Configuration
public class OAuth2Config extends AuthorizationServerSecurityConfiguration {
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.addFilterAfter(myFilter(), BasicAuthenticationFilter.class);
}
}
You can add also add additional filters via the AuthorizationServerSecurityConfigurer, though they come before Basic auth, not after.
#Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.addTokenEndpointAuthenticationFilter(myFilter());
security.checkTokenAccess("isAuthenticated()");
}
Adds a new custom authentication filter for the TokenEndpoint. Filters will be set upstream of the default BasicAuthenticationFilter.