乐趣区

关于spring-security:spring-security之Secured角色权限

前言

写完后盾接口当前,剩下的是退出权限管制。最简略是对于 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()
显然 SuperPasswordAuthenticationTokenPreAuthenticatedAuthenticationToken
继承同一个父类。

最初起作用的是咱们

SecurityContextHolder.getContext().setAuthentication(authentication);

里传入的用户, 其余任何实现都为咱们传入的 user 相干信息服务。

总结

应用一个新货色不能照猫画猫。外头要传什么参数要了解其中的原理才行。

退出移动版