实现SpringSecurity授权操作

2022-09-05 SpringSecurity

上文我们完成了登录认证操作,但是并没有进行权限配置,接下来我们来完成授权操作,首先在配置类开启注解鉴权。

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
// ……
}

# LoginUser对象填充授权码

还记得LoginUser对象中的getAuthorities吗?这里存放的就是我们的权限码,我们来简单修改一下LoginUser对象

@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {


    private User user;

    //存储权限信息
    private List<String> permissions;

    //存储SpringSecurity所需要的权限信息的集合
    private List<GrantedAuthority> authorities;

    public LoginUser(User user,List<String> permissions) {
        this.user = user;
        this.permissions = permissions;
    }

    public LoginUser(User user) {
        this.user = user;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if(authorities!=null){
            return authorities;
        }
        //把permissions中字符串类型的权限信息转换成GrantedAuthority对象存入authorities中
        authorities = permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
        return authorities;
    }

    @Override
    public String getPassword() {return user.getPassword();}
    // 和之前一样,此处省略……
}

在UserDetailsServiceImpl中给LoginUser对象赋权限值

@Service
@Slf4j
public class UserDetailServiceImpl implements UserDetailsService {
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 查询用户信息
        User user = findUserByUsername(username);
        log.info("user:{}", user);
        log.info("username:{}", username);

        // 如果找不到用户则抛出异常
        if (user == null) {
            throw new RuntimeException("用户名或密码错误");
        }
        //TODO 根据用户查询权限信息 添加到LoginUser中
        List<String> authList = new ArrayList<>(Arrays.asList("user:add", "user:search"));
        return new LoginUser(user, authList);
    }
}

# 修改JWT工具包

jwt生成令牌和解析令牌都需要添加权限信息,生成令牌在登陆时调用,解析令牌在Token校验过滤器中调用

public class JwtUtils {
    // 密钥
    private static final byte[] TOKEN_KEY = "xk857.com".getBytes();
    // token过期时间一周
    private static final long EXPIRE = 60000 * 60 * 24 * 7;

    /**
     * 根据用户信息,生成令牌
     *
     * @param loginUser 用户对象
     * @return token
     */
    public static String geneJsonWebToken(LoginUser loginUser) {
        // 默认使用HS256加密算法
        return JWT.create()
                .setPayload("id", loginUser.getUser().getId())
                .setPayload("username", loginUser.getUser().getUsername())
                .setPayload("nickName", loginUser.getUser().getNickName())
                .setPayload("permissions", loginUser.getPermissions())
                .setIssuedAt(new Date())
                .setExpiresAt(new Date(System.currentTimeMillis() + EXPIRE))
                .setKey(TOKEN_KEY).sign();
    }

    /**
     * 解析Token
     */
    public static LoginUser checkJWT(String token) {
        // 验证Token
        boolean validate = JWT.of(token).setKey(TOKEN_KEY).validate(0);
        if (!validate) {
            // 企业开发建议使用自定义异常
            throw new RuntimeException("Token过期");
        }
        JWT jwt = JWT.of(token);
        User user = new User();
        user.setId((String) jwt.getPayload("id"));
        user.setUsername((String) jwt.getPayload("username"));
        user.setNickName((String) jwt.getPayload("nickName"));
        List<String> permissions = (List<String>) jwt.getPayload("permissions");
        return new LoginUser(user, permissions);
    }
}

# 登录接口存储权限信息

生成的Token需要存储权限信息,因为解析的时候需要使用。

@RestController
public class AuthController {

    @Autowired
    private AuthenticationManager authenticationManager;

    @PostMapping("/user/login")
    public Map<String,String> login(@RequestBody LoginParam param) {
        // 使用SpringSecurity登录认证,通用写法
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(param.getUsername(),param.getPassword());
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        if(Objects.isNull(authenticate)){
             throw new RuntimeException("账号或密码错误");
        }

        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
        // 生成token
        String token = JwtUtils.geneJsonWebToken(loginUser);
        Map<String,String> map = new HashMap<>();
        map.put("token",token);
        return map;
    }
}

# 修改Token校验过滤器

之前我们仅设置了用户的登录信息,SecurityContextHolder中并没有存储权限码

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //获取token
        String token = request.getHeader("token");
        if (!StringUtils.hasText(token)) {
            //放行
            filterChain.doFilter(request, response);
            return;
        }
        //解析token,获取User对象封装成LoginUser对象
        User user = JwtUtils.checkJWT(token);
        LoginUser loginUser = new LoginUser(user);
        //存入SecurityContextHolder
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); // 注意这一行,仅修改了这一行
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        //放行
        filterChain.doFilter(request, response);
    }
}

# 开发测试接口

@RestController
@RequestMapping("/user")
public class UserController {

    @PostMapping
    @PreAuthorize("hasAuthority('user:add')")
    public String add() {
        return "添加用户";
    }

    @PutMapping
    @PreAuthorize("hasAuthority('user:update')")
    public String update() {
        return "更新用户信息";
    }

    @DeleteMapping("/{id}")
    @PreAuthorize("hasAuthority('user:del')")
    public String add(@PathVariable String id) {
        return "删除用户:"+id;
    }

    @GetMapping("/test")
    @PreAuthorize("hasAuthority('user:add') and hasAuthority('user:update')")
    public String addAndUpdate() {
        return "同时添加和更新用户";
    }
}

# Postman接口测试

调用登录接口获取Token

image-20220905164720236

测试添加接口,接口正常返回数据

image-20220905164810060

测试删除接口提示权限不足

image-20220905164839449

上次更新: 5 个月前