简体   繁体   中英

OAuth2 With Spring Boot Unauthorized (401) Response

I have implemented a spring boot application with oauth2. when I am trying to access token by providing clientId and Secret then unauthorized(401) response is returned.

oauth_client_detals table is designed in the oracle database with the following schema and secret column value is stored in BCrypt format.

insert into oauth_client_details(client_id,client_secret,web_server_redirect_uri,
scope,accsess_token_validity,refresh_token_validity,resource_id,authorized_grant_types,authorities,
  additional_information,autoapprove) values ('web','{bcrypt}$2y$12$FCIQkEmh7ai/6oP99yNOEuWnKt9OjrGEczCxnEnFGDRSOHumOChQO',
  '','READ,WRITE','900','3600','','password,authorization_code,refresh_token,implicit','ROLE_ADMIN,ROLE_USER,ROLE_MANAGER','','');

AuthorizationConfig.class

@Configuration
@EnableAuthorizationServer
public class AuthorizationServer  extends AuthorizationServerConfigurerAdapter {


    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private DataSource dataSource;


    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
               security.checkTokenAccess("isAuthenticated()").tokenKeyAccess("permitAll()");
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager);
    }
}

SecurityConfig.class

@Configuration
@EnableWebSecurity
public class SecurityConfig  extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private AuthEntryPoint authEntryPoint;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider1());
    }

    private AuthenticationProvider authenticationProvider1()
    {
        DaoAuthenticationProvider provider=new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(new BCryptPasswordEncoder());
        return provider;
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().exceptionHandling().authenticationEntryPoint(authEntryPoint)
                .and().authorizeRequests().anyRequest().authenticated().and().httpBasic();
    }
}

UserDetailsServiceImpl.class

@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserDAO userDAO;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        User user= userDAO.findByUserName(username)
                .orElseThrow(()->new UsernameNotFoundException("data not found with "+username));

        return AuthUserDetails.builder(user);
    }

}

AuthUserDetails.class

public class AuthUserDetails implements UserDetails{

    private String userName;
    private String password;
    private List<GrantedAuthority> authorities;
    private boolean accNonExpired;
    private boolean accNonLocked;
    private boolean credentialNonExpired;
    private boolean active;

    public AuthUserDetails()
    {

    }

    public AuthUserDetails(boolean active, List<GrantedAuthority> authorities, String userName, String password,
            boolean accNonExpired, boolean credentialNonExpired, boolean accNonLocked) {

        this.active = active;
        this.authorities = authorities;
        this.userName = userName;
        this.password = password;
        this.accNonExpired = accNonExpired;
        this.credentialNonExpired = credentialNonExpired;
        this.accNonLocked = accNonLocked;
    }

    public static UserDetails builder(User user)
    {

        List<GrantedAuthority> grantedAuthorities=new ArrayList<>();

         user.getRoles().forEach(role-> {

                    grantedAuthorities.add(new SimpleGrantedAuthority(role.getName().name()));

                    role.getPermissions().forEach(perm->{
                            grantedAuthorities.add(new SimpleGrantedAuthority(perm.getName().name()));
                    });

                });

         return new AuthUserDetails((user.getActive()==1),grantedAuthorities,user.getUserName(),user.getPassword(),
                 (user.getAccNonExpired()==1), (user.getCredentialNonExpired()==1),(user.getAccNonLocked()==1));

      
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return userName;
    }

    @Override
    public boolean isAccountNonExpired() {
        return accNonExpired;
    }

    @Override
    public boolean isAccountNonLocked() {
        return accNonLocked;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return credentialNonExpired;
    }

    @Override
    public boolean isEnabled() {
        return active;
    }
}

User.class

@Entity
@Table(name="user56",schema = Schema.OAUTH2,uniqueConstraints = @UniqueConstraint(
        columnNames = "username"
))
@Getter
@Setter
public class User {

    @Id
    @SequenceGenerator(name="user_id_gen",sequenceName = Schema.OAUTH2+".user_id_seq",initialValue = 1003,allocationSize = 1)
    @GeneratedValue(generator = "user_id_gen",strategy = GenerationType.SEQUENCE)
    @Column(name = "user_id")
    private int userId;
    @Column(name = "username")
    private String userName;
    @Column(name = "password")
    private String password;
    @Column(name = "email")
    private String email;
    @Column(name = "active")
    private int active;
    @Column(name = "acc_non_expired")
    private int accNonExpired;
    @Column(name = "credential_non_expired")
    private int credentialNonExpired;
    @Column(name = "acc_non_locked")
    private int accNonLocked;

    @ManyToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
    @JoinTable(name = "role_user",
    joinColumns = {@JoinColumn(name = "user_id",referencedColumnName = "user_id")},
    inverseJoinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "id")})
    private Set<Role> roles;

}
             

OAuthClient.class

@Entity
@Table(name = "oauth_client_details",schema = Schema.OAUTH2)
@Getter
@Setter
public class OAuthClient {

    @Id
    @Column(name = "client_id")
    private String clientId;
    @Column(name="client_secret")
    private String clientSecret;
    @Column(name = "web_server_redirect_uri")
    private String webServerRedirectUri;
    @Column(name = "scope")
    private String scope;
    @Column(name = "accsess_token_validity")
    private String accessTokenValidity;
    @Column(name = "refresh_token_validity")
    private String refreshTokenValidity;
    @Column(name = "resource_id")
    private String resourceId;
    @Column(name="authorized_grant_types")
    private String authorizedGrantType;
    @Column(name = "authorities")
    private String authorities;
    @Column(name = "additional_information")
    private String additionalInformation;
    @Column(name = "autoapprove")
    private String autoApprove;

}

response unauthorized(401) through postman

在此处输入图像描述

Updated

AuthEntryPoint.class

@Component
public class AuthEntryPoint implements AuthenticationEntryPoint {


        Logger ERROR_LOGGER= LoggerFactory.getLogger(AuthEntryPoint.class);

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {

        ERROR_LOGGER.error("Unauthorized error : {}",authException.getMessage());

        response.sendError(HttpServletResponse.SC_UNAUTHORIZED,"Error : Unauthorized");

    }
}

Updated 2

when I tried to run the application under the debugging mode then the following error will occur

FileNotFoundException@769

在此处输入图像描述

I have downloaded you project from here and, initially, I only have found some typo in the columns: accsess_token_validity and resource_id

After that I have added the required tables of your project and include some dummy information. For that reason, I'm pretty sure your problem is related with the password values in your oauth_client_details and user56 tables, because only when the stored value did not match with the expected one, I received 401 .

Take into account you have defined two different BCryptPasswordEncoder instances:

  1. provider.setPasswordEncoder(new BCryptPasswordEncoder()) for the users included in user56 .

  2. clients.jdbc(dataSource).passwordEncoder(new BCryptPasswordEncoder()) for the users included in oauth_clients_detail

Once I fixed it and store the expected ones, I achieved the endpoint returned the expected result:

基本认证 所需参数和结果

As I told you in previous comments, there several classes will help you to find the cause of the error:


BasicAuthenticationFilter will get from the request the Basic Auth provided and executes the authentication process.

BasicFilterAuthentication


BasicAuthenticationConverter will extract Basic Auth provided from the request really.

基本认证转换器


JbdcClientDetailsService will get from oauth_clients_detail the information related with provided client_id in Basic Auth.

JbdcClientDetailsService


The following pictures are the most important to verify if your passwords match. That method will invoke twice: first for client_id / client_pass and second for username / password .

I have included some useful information in the "debug area"

DaoAuthenticationProviderClientId DaoAuthenticationProvider用户名

Finally, I have found the place where the problem is occurring, When I send the request to the server then unauthorized(401) error occurs with the following message,

Encoded password does not look like BCrypt

So I have replaced the following code in SecurityConfig.class

    @Bean
    public PasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

  // replace to

  @Bean
  public PasswordEncoder getPasswordEncoder() {
    return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}

It works fine but still, I couldn't find why BCrytptPasswordEncoder is not worked even secret values are stored in BCrypt format. Anyway thank you very much @doctore for your answer

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM