OAuth2与Spring Boot未经授权的(401)响应

huangapple go评论57阅读模式
英文:

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

英文:

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 (&#39;web&#39;,&#39;{bcrypt}$2y$12$FCIQkEmh7ai/6oP99yNOEuWnKt9OjrGEczCxnEnFGDRSOHumOChQO&#39;,
  &#39;&#39;,&#39;READ,WRITE&#39;,&#39;900&#39;,&#39;3600&#39;,&#39;&#39;,&#39;password,authorization_code,refresh_token,implicit&#39;,&#39;ROLE_ADMIN,ROLE_USER,ROLE_MANAGER&#39;,&#39;&#39;,&#39;&#39;);

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(&quot;isAuthenticated()&quot;).tokenKeyAccess(&quot;permitAll()&quot;);
    }

    @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(&quot;userDetailsService&quot;)
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserDAO userDAO;

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

        User user= userDAO.findByUserName(username)
                .orElseThrow(()-&gt;new UsernameNotFoundException(&quot;data not found with &quot;+username));

        return AuthUserDetails.builder(user);
    }

}

AuthUserDetails.class

public class AuthUserDetails implements UserDetails{

    private String userName;
    private String password;
    private List&lt;GrantedAuthority&gt; authorities;
    private boolean accNonExpired;
    private boolean accNonLocked;
    private boolean credentialNonExpired;
    private boolean active;

    public AuthUserDetails()
    {

    }

    public AuthUserDetails(boolean active, List&lt;GrantedAuthority&gt; 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&lt;GrantedAuthority&gt; grantedAuthorities=new ArrayList&lt;&gt;();

         user.getRoles().forEach(role-&gt; {

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

        			role.getPermissions().forEach(perm-&gt;{
        					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&lt;? extends GrantedAuthority&gt; 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=&quot;user56&quot;,schema = Schema.OAUTH2,uniqueConstraints = @UniqueConstraint(
        columnNames = &quot;username&quot;
))
@Getter
@Setter
public class User {

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

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

}

OAuthClient.class

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

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

}

response unauthorized(401) through postman

OAuth2与Spring Boot未经授权的(401)响应

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(&quot;Unauthorized error : {}&quot;,authException.getMessage());

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

    }
}

Updated 2

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

> FileNotFoundException@769

OAuth2与Spring Boot未经授权的(401)响应

答案1

得分: 1

我已经从这里下载了您的项目,最初,我只发现了列accsess_token_validityresource_id中的一些拼写错误。

之后,我已经添加了您项目所需的表格,并包括了一些虚拟信息。因此,我非常确定您的问题与oauth_client_detailsuser56表格中的password值相关,因为只有当存储的值与预期值不匹配时,我收到了401错误。

请注意,您已经定义了两个不同的BCryptPasswordEncoder实例:

  1. 对于包含在user56中的用户,provider.setPasswordEncoder(new BCryptPasswordEncoder())

  2. 对于包含在oauth_clients_detail中的用户,clients.jdbc(dataSource).passwordEncoder(new BCryptPasswordEncoder())

一旦我修复了它并存储了预期的值,我就实现了端点返回了预期的结果:

OAuth2与Spring Boot未经授权的(401)响应
OAuth2与Spring Boot未经授权的(401)响应

正如我在先前的评论中告诉您的那样,有几个类将帮助您找到错误的原因:


BasicAuthenticationFilter将从请求中获取提供的基本身份验证并执行身份验证过程。

OAuth2与Spring Boot未经授权的(401)响应


BasicAuthenticationConverter将从请求中提取实际提供的基本身份验证。

OAuth2与Spring Boot未经授权的(401)响应


JbdcClientDetailsService将从oauth_clients_detail中获取与提供的Basic Auth中的client_id相关的信息。

OAuth2与Spring Boot未经授权的(401)响应


以下图片是验证密码是否匹配最重要的部分。该方法将调用两次:首先是client_id / client_pass,其次是username / password

我在“调试区域”中包含了一些有用的信息。

OAuth2与Spring Boot未经授权的(401)响应
OAuth2与Spring Boot未经授权的(401)响应

英文:

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:

OAuth2与Spring Boot未经授权的(401)响应
OAuth2与Spring Boot未经授权的(401)响应

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.

OAuth2与Spring Boot未经授权的(401)响应


BasicAuthenticationConverter will extract Basic Auth provided from the request really.

OAuth2与Spring Boot未经授权的(401)响应


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

OAuth2与Spring Boot未经授权的(401)响应


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"

OAuth2与Spring Boot未经授权的(401)响应
OAuth2与Spring Boot未经授权的(401)响应

答案2

得分: 1

最终,我找到了问题发生的地方。当我向服务器发送请求时,会出现未授权的(401)错误,并显示以下消息:

编码的密码看起来不像是BCrypt

因此,我已经替换了SecurityConfig.class中的以下代码:

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

替换为:

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

这样可以正常运行,但我仍然找不到为什么BCryptPasswordEncoder不起作用,即使密钥值以BCrypt格式存储。无论如何,非常感谢@doctore提供的答案。

英文:

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

huangapple
  • 本文由 发表于 2020年8月2日 13:54:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/63212843.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定