英文:
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 ('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
答案1
得分: 1
我已经从这里下载了您的项目,最初,我只发现了列accsess_token_validity
和resource_id
中的一些拼写错误。
之后,我已经添加了您项目所需的表格,并包括了一些虚拟信息。因此,我非常确定您的问题与oauth_client_details
和user56
表格中的password
值相关,因为只有当存储的值与预期值不匹配时,我收到了401
错误。
请注意,您已经定义了两个不同的BCryptPasswordEncoder
实例:
-
对于包含在
user56
中的用户,provider.setPasswordEncoder(new BCryptPasswordEncoder())
。 -
对于包含在
oauth_clients_detail
中的用户,clients.jdbc(dataSource).passwordEncoder(new BCryptPasswordEncoder())
。
一旦我修复了它并存储了预期的值,我就实现了端点返回了预期的结果:
正如我在先前的评论中告诉您的那样,有几个类将帮助您找到错误的原因:
BasicAuthenticationFilter将从请求中获取提供的基本身份验证并执行身份验证过程。
BasicAuthenticationConverter将从请求中提取实际提供的基本身份验证。
JbdcClientDetailsService将从oauth_clients_detail
中获取与提供的Basic Auth中的client_id
相关的信息。
以下图片是验证密码是否匹配最重要的部分。该方法将调用两次:首先是client_id / client_pass
,其次是username / password
。
我在“调试区域”中包含了一些有用的信息。
英文:
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:
-
provider.setPasswordEncoder(new BCryptPasswordEncoder())
for the users included inuser56
. -
clients.jdbc(dataSource).passwordEncoder(new BCryptPasswordEncoder())
for the users included inoauth_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.
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.
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"
答案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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论