【问题标题】:OAuth2 With Spring Boot Unauthorized (401) Response带有 Spring Boot 未授权 (401) 响应的 OAuth2
【发布时间】:2020-11-22 12:58:27
【问题描述】:

我已经使用 oauth2 实现了一个 Spring Boot 应用程序。当我尝试通过提供 clientId 和 Secret 来访问令牌时,将返回未经授权的 (401) 响应。

oauth_client_detals 表是在 oracle 数据库中设计的,具有以下架构,秘密列值以 BCrypt 格式存储。

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();
    }
}

UserDetailsS​​erviceImpl.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;
    }
}

用户类

@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;

}

通过邮递员响应未经授权的(401)

更新

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");

    }
}

更新 2

当我尝试在调试模式下运行应用程序时会出现以下错误

FileNotFoundException@769

【问题讨论】:

  • 您在请求中发送的参数是什么?您在实现中使用的AuthEntryPoint 是什么?
  • @dotore 我正在通过表单数据发送授权类型、用户名和密码作为参数,我将更新 AuthEntry 点类
  • 将您的代码调整到我的项目中,调用请求没有问题(但是,我当前的配置完全不同,因此我无法确定您的情况的根本原因)。可能详细调试您的请求,您将获得有关该问题的更多信息。我将从您的UserDetailsServiceImpl 开始,以确保尝试查找的username 是参数username 中提供的,而不是web
  • 另一方面,不确定,因为您没有提及,但是如果您使用spring-security-oauth2工件来处理Spring + Oauth 2.获取访问令牌(或刷新一个)的端点是POST 而不是GET,正如您在附加图片中使用的那样。更多信息:docs.spring.io/spring-security/oauth/apidocs/org/…
  • @dotore 是的。这是发布请求。但是我在它工作的时候在内存中做了oauth2,当我传递客户端ID和秘密时,返回方法不允许的响应。当我使用 DAO 身份验证而不是 in-memory 时发生未经授权的错误。

标签: java spring spring-boot spring-security oauth-2.0


【解决方案1】:

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

编码密码看起来不像 BCrypt

所以我在 SecurityConfig.class 中替换了以下代码

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

  // replace to

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

它工作正常,但我仍然找不到 BCrytptPasswordEncoder 不起作用的原因,即使秘密值以 BCrypt 格式存储。无论如何,非常感谢@doctore 的回答

【讨论】:

    【解决方案2】:

    我从here 下载了你的项目,最初,我只在列中发现了一些错字:accsess_token_validityresource_id

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

    考虑到您定义了两个不同的BCryptPasswordEncoder 实例:

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

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

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

    正如我在之前的 cmets 中告诉你的,有几个类可以帮助你找到错误的原因:


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


    BasicAuthenticationConverter 将真正提取请求中提供的基本身份验证。


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


    以下图片是验证您的密码是否匹配的最重要的图片。该方法将调用两次:第一次为client_id / client_pass,第二次为username / password

    我在“调试区”中加入了一些有用的信息

    【讨论】:

    • 我尝试在调试模式下运行应用程序,然后出现错误。是在上面问题的update2下更新的
    猜你喜欢
    • 1970-01-01
    • 2021-05-25
    • 2017-12-21
    • 2019-08-09
    • 2021-04-07
    • 2021-01-24
    • 2017-05-25
    • 2022-08-04
    • 2022-01-21
    相关资源
    最近更新 更多