SpringBoot+SpringSecurity+Vue实现动态权限(二)

前言

在上一篇完成项目的构建后,完成了SpringSecurity获取用户信息和权限信息的逻辑。这一篇要实现SpringSecurity的配置,实现自己的登录认证和授权的逻辑。要完成自定义认证和授权的逻辑,就需要自己实现两个过滤器。
这两个过滤器是认证过滤器和接口访问过滤器。分别要继承UsernamePasswordAuthenticationFilter类和BasicAuthenticationFilter这两个类。在UsernamePasswordAuthenticationFilter的实现类中可以定义登录接口的路径以及登录成功和失败后对应的逻辑,其中获取用户信息的逻辑就是调用上篇写的loadUserByUsername方法。我准备在登录成功后生成一个token并存入redis,失败后就直接返回一段字符串。

认证过滤器

public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;
    private RedisTemplate redisTemplate;

    public TokenLoginFilter(AuthenticationManager authenticationManager, RedisTemplate redisTemplate) {
        this.authenticationManager = authenticationManager;
        this.redisTemplate = redisTemplate;
        this.setPostOnly(false);
        // 设置登录路径匹配/autoperm/user/login
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/autoperm/user/login","POST"));
    }

    /**
     * 身份验证
     * @param req
     * @param res
     * @return org.springframework.security.core.Authentication
     * @author 黎勇炫
     * @create 2022/6/10
     * @email 1677685900@qq.com
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
            throws AuthenticationException {
        User user;
        try {
         user = new ObjectMapper().readValue(req.getInputStream(), User.class);
        } catch (Exception e) {
            throw new UserException(UserCodeEnum.AUTHENTICATION_FAILED);
        }
        // 调用UserDetailsServiceImpl.loadUserByUsername
        return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>()));

    }

    /**
     * 登录成功
     * @param req
     * @param res
     * @param chain
     * @param auth
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
                                            Authentication auth) throws IOException {
        User user = (User) auth.getPrincipal();
        // 生成token
        String token = JwtUtils.getJwtToken(user.getId(),user.getUsername());
        // 将token信息存入redis缓存
        redisTemplate.opsForValue().set(user.getUsername(), user.getPermissions());

        ResponseUtils.write(res,R.ok().setData(token));
    }

    /**
     * 登录失败
     * @param request
     * @param response
     * @param e
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                              AuthenticationException e) throws IOException {

        response.setStatus(200);
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        response.getWriter().print(JSON.toJSONString(R.error(HttpStatus.UNAUTHENTICATE,"认证失败")));
    }
}

接口访问过滤器

接下来就编写接口访问过滤器,实现BasicAuthenticationFilter类。在这个类中要拿到认证过滤器中发的token,并根据token获取用户名。再依据用户名到redis中拿到用户的权限列表为用户授权。

public class TokenAuthenticationFilter extends BasicAuthenticationFilter {
    private RedisTemplate redisTemplate;

    public TokenAuthenticationFilter(AuthenticationManager authManager, RedisTemplate redisTemplate) {
        super(authManager);
        this.redisTemplate = redisTemplate;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
            throws IOException, ServletException {

        if(req.getRequestURI().indexOf("admin") != -1) {
            chain.doFilter(req, res);
            return;
        }

        UsernamePasswordAuthenticationToken authentication = null;
        try {
            authentication = getAuthentication(req);
        } catch (Exception e) {
            throw new UserException(UserCodeEnum.TOKEN_NOT_FOUND);
        }

        if (authentication != null) {
            SecurityContextHolder.getContext().setAuthentication(authentication);
        } else {
            throw new UserException(UserCodeEnum.AUTHENTICATION_FAILED);
        }
        chain.doFilter(req, res);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        // token置于header里
        String token = request.getHeader("token");
        if (token != null && !"".equals(token.trim())) {
            String userName = JwtUtils.getUsernameByJwtToken(request);

            Set<String> permissionValueList = (Set<String>) redisTemplate.opsForValue().get(userName);
            Collection<GrantedAuthority> authorities = new ArrayList<>();
            if(!CollectionUtils.isEmpty(permissionValueList)){
                for(String permissionValue : permissionValueList) {
                    if(StringUtils.isEmpty(permissionValue)) continue;
                    SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
                    authorities.add(authority);
                }

            }

            if (!StringUtils.isEmpty(userName)) {
                return new UsernamePasswordAuthenticationToken(userName, token, authorities);
            }
            return null;
        }
        return null;
    }
}

到这里就完成了用户的登录认证和接口访问授权了。这样用户只要一访问接口就自动实现了权限配置。

登出处理器

登出处理器LogoutSuccessHandler需要继承LogoutHandler类,逻辑很简单,只需要拿到token并从redis中删除对应的信息就可以了。

@Component
public class LogoutSuccessHandler implements LogoutHandler {
    private RedisTemplate redisTemplate;

    public LogoutSuccessHandler(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        String token = request.getHeader("token");
        if (token != null)
            //清空当前用户缓存中的权限数据
        {
            String userName = JwtUtils.getUsernameByJwtToken(request);
            redisTemplate.delete(userName);
        }
        PrintWriter writer = null;
        try {
            writer = response.getWriter();
        } catch (IOException e) {
            e.printStackTrace();
        }
        writer.write(JSON.toJSONString(R.ok()));
        writer.close();
    }
}

未授权(权限不足)处理器

在这个处理器中做出权限不足或未授权时的业务逻辑,我这简单的返回对应的信息。

/**
 * 未授权统一处理
 * @author 黎勇炫
 * @date 2022年06月10日 11:04
 */
public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.setStatus(200);
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        response.getWriter().print(JSON.toJSONString(R.error(HttpStatus.UNAUTHORIZED,"权限不足")));
    }
}

配置Security

将刚才编写的过滤器和处理器以及密码加密注册到Springsecurity中,并配置路径的访问权限,登录接口时可以匿名访问的。

/**
 * 安全框架配置类
 * @author 黎勇炫
 * @date 2022年06月10日 9:36
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserServiceImpl userService;
    @Autowired
    private LogoutSuccessHandler logoutSuccessHandler;
    @Autowired
    private RedisTemplate redisTemplate;


    /**
     * 密码加密
     * @return org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
     * @author 黎勇炫
     * @create 2022/6/10
     * @email 1677685900@qq.com
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

    /**
     * 设置用户登录校验逻辑和加密算法(强散列哈希)
     * @param auth
     * @return void
     * @author 黎勇炫
     * @create 2022/6/10
     * @email 1677685900@qq.com
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.exceptionHandling()
                 // 未授权处理
                .authenticationEntryPoint(new UnauthorizedEntryPoint())
                // 关闭csrf
                .and().csrf().disable()
                // 需要授权的请求
                .authorizeRequests()
                .antMatchers("/autoperm/user/login").anonymous()
                .anyRequest().authenticated()
                // 退出登录的请求路径和对应的处理器
                .and().logout().logoutUrl("/autoperm/user/logout")
                .addLogoutHandler(new LogoutSuccessHandler(redisTemplate)).and()
                // 登录过滤器
                .addFilter(new TokenLoginFilter(authenticationManager(), redisTemplate))
                // 接口认证过滤器
                .addFilter(new TokenAuthenticationFilter(authenticationManager(), redisTemplate)).httpBasic();
    }

}

到这里已经实现了基本的登录逻辑和接口访问逻辑。下一篇就整合前端实现登录已经动态菜单。

相关内容

源码地址
SpringBoot+SpringSecurity+Vue实现动态权限(一)

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SpringBoot+SpringSecurity+Vue实现动态路由的过程如下: 1. 在后端(SpringBoot)中,首先需要定义一个权限表,用于存储所有的权限信息,包括权限名称、权限标识等。 2. 在前端(Vue)中,需要定义一个路由表,用于存储所有的路由信息,包括路由路径、组件名称等。 3. 后端需要提供一个接口,用于获取当前用户的权限列表。该接口会根据用户的角色查询对应的权限,并返回给前端。 4. 前端在登录成功后,会调用后端接口获取当前用户的权限列表,并将权限列表存储到本地(如localStorage或vuex)中。 5. 前端在路由跳转时,会根据当前用户的权限列表动态生成路由。可以通过遍历权限列表,根据权限标识匹配路由表中的路由信息,将匹配到的路由添加到路由表中。 6. 前端在生成路由后,需要使用Vue Router的addRoutes方法将动态生成的路由添加到路由表中。 7. 前端在路由跳转时,会根据用户的权限判断是否有权限访问该路由。可以通过导航守卫的beforeEach方法,在路由跳转前进行权限判断。 8. 后端可以使用Spring Security的注解对接口进行权限控制。可以通过在接口上添加注解,指定需要的权限才能访问该接口。 9. 后端在接口调用时,可以通过从redis中获取当前用户的权限列表,并进行权限判断。 10. 前端和后端通过接口交互,实现动态路由的权限控制

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值