spring security之@Secured角色权限

前言

写完后台接口之后,剩下的是加入权限控制。最简单是关于spring security加入的@Secured注解,注解里加入容许访问的角色便可。spring

@Secured("ROLE_VIEWER")
public String getUsername() {
    SecurityContext securityContext = SecurityContextHolder.getContext();
    return securityContext.getAuthentication().getName();
}

可是他里边的角色是如何与我定义的用户角色对应的呢,又是对应的哪一个字段呢,我也没找到解释,想着写完之后去研究一下。先写了一个尝试一下,在获取clazz分页数据接口上加入只能管理员访问,而后登录一个学生用户,用url跳转到clazz模块,发现起做用了。
image.png
而后就将大部分接口都加入了@Secured注解。再启动项目就发现就不对劲了,他是起做用了,可是对全部角色用户都拦截了。一开始想到写法不太正确,可是又不知道里头填的角色名称跟什么相对应。这能去研究他的原理。数组

过程

去网上找了不少资料,对于这方面的描述都只停留在使用层面。都是说你只要加入@Secured("ADMIN")注解,就只有你角色是ADMIN的用户才能访问。可是怎么让用户角色是ADMIN,并无详细的介绍。
网上找不到资料,我问了学长关于原来项目的使用方法。学长告诉我是因为实现UserDetailsService接口的loadUserByUsername方法里去添加获取到的user的角色。this

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方法上打断点,发现看看是否执行,发现只有登陆的时候执行了,可是是获取到用户角色了的。image.png
我就想多是其余地方实现的传入角色。我去找哪还用了UserDetail。发如今咱们自定义的spring security过滤器中也运用了,url

if (userOptional.isPresent()) {
    // token有效,则设置登陆信息
    PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(
            new UserServiceImpl.UserDetail(userOptional.get(), new ArrayList<>()), null, new ArrayList<>());
    SecurityContextHolder.getContext().setAuthentication(authentication);
 }

我在PreAuthenticatedAuthenticationToken中加入角色authorities,发现居然能够了。spa

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);
}

那为何和老项目里实现的方法不同呢。我去老项目里搜了一下关于debug

SecurityContextHolder.getContext().setAuthentication()

的实现,发现
// 设置当前登陆用户,设置其状态为:已认证。其它同类过滤器获取到已认证的状态后将跳过其自身的认证过程code

UserDetails userDetails = this.authService.loadUserByUsername(authentication.getName());
        SecurityContextHolder.getContext().setAuthentication(
                new SuperPasswordAuthenticationToken(
                        userDetails,
                        authentication.getCredentials(),
                        userDetails.getAuthorities()
                ));

他是在实现里去传入了SuperPasswordAuthenticationToken ,在SuperPasswordAuthenticationToken里传入了userDetails.getAuthorities()
显然SuperPasswordAuthenticationTokenPreAuthenticatedAuthenticationToken
继承同一个父类。
image.png继承

image.png

最后起做用的是咱们token

SecurityContextHolder.getContext().setAuthentication(authentication);

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

总结

使用一个新东西不能照猫画猫。里头要传什么参数要理解其中的原理才行。

相关文章
相关标签/搜索