前言
用POSTMAN或者在页面前端登录访问后端API时,始终返回401.返回401有很多原因造成的,主要分为两个方面来看:
- 配置上的问题。确实没有权限。可以去检查一下数据库,看看相关的用户,权限有没有配上。
- 代码上的问题。配置上已经配置了权限,任然无法访问。
这里主要讲代码上的问题。
一、 UserDetails实现类里的getAuthorities重写方法,返回null.
public class UserDto extends DeepflowAbstractDto implements UserDetails {
private String name;
private String password;
private Integer age;
private String phoneNumber;
private List<RoleDto> authorities;
@Override
public Collection<RoleDto> getAuthorities() {
return null;
}
...
}
getAuthorities这个方法是返回当前登录用户具有哪些角色,如果返回null的话,即使数据库里给用户配置了角色,框架也认为这个用户没有任何角色可以访问这个api,自然也就报401异常。这个问题一般是编写代码是粗心导致的,耗费了大量的时间而且不容易排查。所以写代码一定要细心呀。
代码改成下面应该就可以了:
public class UserDto extends DeepflowAbstractDto implements UserDetails {
private String name;
private String password;
private Integer age;
private String phoneNumber;
private List<RoleDto> authorities;
@Override
public Collection<RoleDto> getAuthorities() {
return authorities;
}
...
}
二、 UserDetailsService实现类没有往登录用户里塞进角色信息
@Component
public class DeepflowUserDetailsService implements UserDetailsService {
@Inject
private UserService userService;
@Inject
private UserRoleService userRoleService;
@Inject
private RoleService roleService;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
UserDto userDto = userService.findUserByName(userName);
if (null != userDto) {
List<Long> roleIds = userRoleService.findByUserId(userDto.getId())//
.stream()//
.map(UserRoleDto::getRoleId)//
.collect(Collectors.toList());
List<RoleDto> roleDtos = roleService.findByIds(roleIds);
// userDto.setAuthorities(roleDtos);
}
return userDto;
}
}
这里如果再给框架返回的用户信息里,没有用户的角色的话,在上一步return authorities;任然会返回一个null.所以这里把注释去掉应该就可以了。一般刚接触的可能会踩到这个坑。
三、访问@PreAuthorize修饰的方法报401
当访问某些被@PreAuthorize(“hasRole(‘ADMIN’)”)注解修饰的方法时,登录用户已经配置了ADMIN角色,可还是报401。一个可能的原因是,数据库中角色的名字要存为ROLE_ADMIN。
原因是源码org.springframework.security.access.vote.RoleVoter类中定义了一个前缀private String rolePrefix = “ROLE_”;,类中的supports方法会拿权限参数和rolePrefix进行匹配,查看是否是以ROLE_开头。
public class RoleVoter implements AccessDecisionVoter<Object> {
// ~ Instance fields
// ================================================================================================
private String rolePrefix = "ROLE_";
// ~ Methods
// ========================================================================================================
public String getRolePrefix() {
return rolePrefix;
}
/**
* Allows the default role prefix of <code>ROLE_</code> to be overridden. May be set
* to an empty value, although this is usually not desirable.
*
* @param rolePrefix the new prefix
*/
public void setRolePrefix(String rolePrefix) {
this.rolePrefix = rolePrefix;
}
public boolean supports(ConfigAttribute attribute) {
if ((attribute.getAttribute() != null)
&& attribute.getAttribute().startsWith(getRolePrefix())) {
return true;
}
else {
return false;
}
}
}
在supports方法中,会判断角色是否以ROLE_开头,如果不是的话,就会返回false.
解决办法:数据库中的角色名字以ROLE_为前缀存储