前言
写完后台接口以后,剩下的是加入权限控制。最简单是关于spring security加入的@Secured注解,注解里加入允许访问的角色即可。
@Secured("ROLE_VIEWER")
public String getUsername() {
SecurityContext securityContext = SecurityContextHolder.getContext();
return securityContext.getAuthentication().getName();
}
但是他里边的角色是如何与我定义的用户角色对应的呢,又是对应的哪个字段呢,我也没找到解释,想着写完以后去研究一下。先写了一个尝试一下,在获取clazz分页数据接口上加入只能管理员访问,然后登陆一个学生用户,用url跳转到clazz模块,发现起作用了。
然后就将大部分接口都加入了@Secured
注解。再启动项目就发现就不对劲了,他是起作用了,但是对所有角色用户都拦截了。一开始想到写法不太正确,但是又不知道里头填的角色名称跟什么相对应。这能去研究他的原理。
过程
去网上找了很多资料,对于这方面的描述都只停留在使用层面。都是说你只要加入@Secured("ADMIN")
注解,就只有你角色是ADMIN
的用户才能访问。但是怎么让用户角色是ADMIN
,并没有详细的介绍。
网上找不到资料,我问了学长关于原来项目的使用方法。学长告诉我是由于实现UserDetailsService
接口的loadUserByUsername
方法里去添加获取到的user的角色。
public UserDetails loadUserByUsername(String username) throws
UsernameNotFoundException {
logger.debug("根据用户名查询用户");
User user = this.userRepository.findByUsername(username);
if (user == null) {
logger.error("用户名不存在");
throw new UsernameNotFoundException("用户名不存在");
}
logger.debug("获取用户授权菜单");
Set<Menu> menus = new HashSet<>();
for (Role role : user.getRoles()) {
menus.addAll(role.getMenus());
}
logger.debug("初始化授权列表");
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
logger.debug("根据菜单进行 API 授权");
for (Menu menu : menus) {
authorities.add(new SimpleGrantedAuthority(menu.getRoleName()));
}
logger.debug("构造用户");
return new YzUserDetail(user, username, user.getPassword(), authorities);
}
然后与@Secured注解里我们定义的角色名称相对应。
我一看我的项目中loadUserByUsername
方法向UserDetail方法传入的authorities
参数是一个new ArrayList<>()
空数组。我就试着变成传入角色数组,再去实验,还是将所有角色拦截了。并没有解决问题。
然后我在loadUserByUsername
方法上打断点,发现看看是否执行,发现只有登录的时候执行了,但是是获取到用户角色了的。
我就想可能是其他地方实现的传入角色。我去找哪还用了UserDetail。发现在我们自定义的spring security过滤器中也运用了,
if (userOptional.isPresent()) {
// token有效,则设置登录信息
PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(
new UserServiceImpl.UserDetail(userOptional.get(), new ArrayList<>()), null, new ArrayList<>());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
我在PreAuthenticatedAuthenticationToken中加入角色authorities
,发现竟然可以了。
if (userOptional.isPresent()) {
// token有效,则设置登录信息
// 设置用户角色
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (Role role: userOptional.get().getRoles()) {
authorities.add(new SimpleGrantedAuthority(role.getValue()));
}
PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(
new UserServiceImpl.UserDetail(userOptional.get(), new ArrayList<>()), null, authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
那为什么和老项目里实现的方法不一样呢。我去老项目里搜了一下关于
SecurityContextHolder.getContext().setAuthentication()
的实现,发现
// 设置当前登录用户,设置其状态为:已认证。其它同类过滤器获取到已认证的状态后将跳过其自身的认证过程
UserDetails userDetails = this.authService.loadUserByUsername(authentication.getName());
SecurityContextHolder.getContext().setAuthentication(
new SuperPasswordAuthenticationToken(
userDetails,
authentication.getCredentials(),
userDetails.getAuthorities()
));
他是在实现里去传入了SuperPasswordAuthenticationToken
,在SuperPasswordAuthenticationToken
里传入了userDetails.getAuthorities()
。
显然SuperPasswordAuthenticationToken
与PreAuthenticatedAuthenticationToken
继承同一个父类。
最后起作用的是我们
SecurityContextHolder.getContext().setAuthentication(authentication);
里传入的用户,其他任何实现都为我们传入的user相关信息服务。
总结
使用一个新东西不能照猫画猫。里头要传什么参数要理解其中的原理才行。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。