Issue
I am trying to add spring security to my rest api and secure one of endpoints. I added UserEntity which implements UserDetails and RoleEntity to Rest Api. They exists in database, and user has roles:
public class UserEntity implements UserDetails {
@Id
@GeneratedValue(generator = "system-uuid")
@GenericGenerator(name = "system-uuid", strategy = "uuid")
@Column(name="id")
private String id;
private String name;
private String password;
@ManyToMany
@JoinTable(
name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id"))
private Set<RoleEntity> roles;
and overrided necessary methods from UserDetails and set up them on true.
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (RoleEntity role : getRoles()) {
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
@Override
public String getUsername() {
return name;
}
@Override
public String getPassword() {
return password;
}
In MyWebSecurityConfig which extends WebSecurityConfigurerAdapter added configuration:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/offences", "/offences/*", "/offences/**").hasRole("ADMIN")
.anyRequest().permitAll()
.and()
.httpBasic();
}
and It is working too. Endpoint is secured. But when I hardcoded some user (with "admin" username and password) in other app to send request:
public HashMap<Boolean, Object> find(FindOffenceForm findOffenceForm) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setBasicAuth("admin", "admin");
HttpEntity<String> request = new HttpEntity<>(httpHeaders);
try {
ResponseEntity<String> responseEntity = restTemplate.exchange(URL_OFFENCE + "/find/" + findOffenceForm.getName(),
HttpMethod.GET, request, String.class);
result.put(true, objectMapper.readValue(responseEntity.getBody(), new TypeReference<Set<OffenceDTO>>() {
}));
} catch (Exception ex) {
result.put(false, ex.getMessage());
}
I am getting 401 Unauthorized and in my api I can see message WARN 13660 o.s.s.c.bcrypt.BCryptPasswordEncoder : Encoded password does not look like BCrypt. While sending request in resttemaplete, should I code password for bcrypt?
EDIT: OK, so in my database I have not code password for bcrypt. I changed it and now I am getting 403 Forbidden. I added to config .csrf().and().cors().disabled but nothing changed.
Solution
I think you may see 403 status code because the method ExpressionUrlAuthorizationConfigurer.AuthorizedUrl.hasRole(String role)
automatically adds "ROLE_" prefix to a String argument (see documentation here), so filter chain expects "ROLE_ADMIN" role name instead of just "ADMIN", that you passed to the method.
This may happen, if your RoleEntity.getRoles()
returns role names like "ADMIN", "USER", etc. without any prefixes.
If that's the reason, and you don't want this default behavior, you have like 3 options:
- use method
.hasAuthority(String authority)
instead, as suggested in javadoc; - in your overridden
getAuthorities()
method inUserEntity
class rewriteGrantedAuthority
creation logic.
For example, like this:
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (RoleEntity role : getRoles()) {
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
}
return authorities;
}
- register a bean of type GrantedAuthorityDefaults without prefix, like this:
For example, like this.
@Bean
public GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("");
}
Answered By - AndrewThomas
Answer Checked By - Mildred Charles (JavaFixing Admin)