美文网首页
spring security实现权限功能

spring security实现权限功能

作者: sunpy | 来源:发表于2022-09-29 20:48 被阅读0次

    登录校验流程


    spring security认证流程


    1. 浏览器用户提交用户名和密码。
    2. 将请求信息封装成Authentication,实现类为UsernamePasswordAuthenticationToken。
    3. authenticate()方法认证。
    4. AuthenticationManager委托认证authenticate()。
    5. DaoAuthenticationProvider通过loadUserByUsername方法获取用户信息。
    6. 返回UserDetails。
    7. 通过PasswordEncoder对比UserDetails中的密码与Authentication中密码是否一致。
    8. 填充Authentication对象,内容为权限信息。
    9. 返回给 UsernamePasswordAuthenticationFilter一个Authentication对象。
    10. 将Authentication对象放到SecurityContextHolder中。

    部分代码需要重写实现业务


    红色需要自己实现的逻辑
    • 需要查我们自己的数据库校验用户名密码,那么我们需要实现UserDetailsService接口。
    • 在service层,我们编写自己的逻辑方法,需要调用AuthenticationManager类实现认证。
    • 继承spring security的WebSecurityConfigurerAdapter类来实现自己的配置类,配置spring security过滤哪些url等。WebSecurityConfigurerAdapter类在tomcat启动后,托管给spring加载。

    动手实现


    1. 实现自己数据库校验,重写UserDetailsService
    @Service
    public class LoginUserDetailsImpl implements UserDetailsService {
    
        @Autowired
        private UserMapper userMapper;
    
        /**
         * 重写loadUserByUsername方法
         * 查询自己数据库中用户信息
         * @param username
         * @return
         * @throws UsernameNotFoundException
         */
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            if (StrUtil.isBlank(username)) {
                throw new CommonException("用户" + username + "不存在");
            }
    
            /**
             * 查询用户信息,认证
             */
            QueryWrapper queryWrapper = new QueryWrapper();
            queryWrapper.eq("user_name", username);
            User user = userMapper.selectOne(queryWrapper);
    
            if (Objects.isNull(user)) {
                throw new CommonException("该用户不存在");
            }
    
            UserBO userBO = new UserBO();
            BeanUtil.copyProperties(user, userBO);
    
            CustomUserDetailBO customUserDetailBO = new CustomUserDetailBO(userBO);
    
            return customUserDetailBO;
        }
    }
    
    1. 实现自己登录服务接口
    @RestController
    @RequestMapping("/user")
    public class UserController {
    
        @Autowired
        private IUserService userService;
    
        @PostMapping("/login")
        public ResultModel<String> login(@RequestBody UserBO userBO) throws CommonException {
            return userService.login(userBO);
        }
    }
    
    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    
        @Autowired
        private UserMapper userMapper;
    
        @Autowired
        private AuthenticationManager authenticationManager;
    
        @Autowired
        private RedisUtil redisUtil;
    
        @Override
        public ResultModel<String> login(UserBO userBO) throws CommonException {
            /**
             * 1. 获取授权信息
             */
            UsernamePasswordAuthenticationToken token =
                    new UsernamePasswordAuthenticationToken(userBO.getUserName(), userBO.getPassword());
            Authentication authentication = authenticationManager.authenticate(token);
    
            if (Objects.isNull(authentication)) {
                throw new CommonException("登录失败");
            }
    
            /**
             * 获取授权信息中的用户信息
             */
            CustomUserDetailBO customUserDetailBO = (CustomUserDetailBO) authentication.getPrincipal();
            String userIdStr = customUserDetailBO.getUserBO().getUserId().toString();
    
            /**
             * 用户登录,认证成功,那么生成JWT,下次直接使用JWT访问
             */
            String jwt = JwtUtil.createJWT(userIdStr);
    
            /**
             * 将用户信息存储到redis
             */
            redisUtil.setCacheObject("token:"+ userIdStr, customUserDetailBO);
    
            ResultModel<String> resultModel = new ResultModel<>();
            resultModel.setMsg("登录成功");
            resultModel.setRes(jwt);
            return resultModel;
        }
    }
    
    1. 配置spring security,过滤不需要的url
    @EnableWebSecurity
    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        /**
         * 配置过滤的路径等
         * @param http
         * @throws Exception
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable()
                    .sessionManagement().sessionCreationPolicy(
                            SessionCreationPolicy.STATELESS
                    )
                    .and()
                    .authorizeRequests()//csrf关闭
                    .antMatchers("/user/**").permitAll()//放行
                    .anyRequest().authenticated()//其他路径拦截
                    .and()
                    .formLogin().permitAll()//表单提交放行
            ;
        }
    
        /**
         * 注册解码器
         * @return
         */
        @Bean
        public PasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
    
        /**
         * 暴露AuthenticationManager这个Bean
         * @return
         * @throws Exception
         */
        @Bean
        public AuthenticationManager getAuthenticationManager() throws Exception {
            return super.authenticationManagerBean();
        }
    }
    
    1. 测试

    编写配置过滤器,拦截token


    思路:过滤器在每一个请求到达Controller前,都会先经过Filter过滤,这时我们在过滤器里面获取其是否传递了token。
    情况1:如果发现token正确,我们直接使用HttpServletResponse返回。
    情况2:如果token不存在,那么我们交由后面的数据库查询检查。
    情况3:如果redis中不存在token,那么说明redis中token过期或者logout登出删除了redis中的token,那么直接HttpServletResponse返回,提示其重新登录。

    自定义过滤器:

    @Component
    public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    
        @Autowired
        private RedisCache redisCache;
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, CommonException {
            // 获取token
            String token = request.getHeader("token");
    
            if (StrUtil.isBlank(token)) {
                filterChain.doFilter(request, response);
                return;
            }
    
            // 解析token
            String userId = (String) JwtUtil.parseJWT(token).get("userId");
            // 从redis获取用户信息
            CustomUserDetailBO customUserDetailBO = redisCache.getCacheObject("token:" + userId);
            ResultModel<String> resultModel = new ResultModel<>();
            resultModel.setTime(TimeUtil.getNowTime());
    
            if (Objects.isNull(customUserDetailBO)) {
                response.setStatus(500);
                resultModel.setCode(500);
                resultModel.setSuccess(false);
                resultModel.setMsg("用户未登陆");
                HttpUtil.setRespBody(response, resultModel);
                return;
            }
    
            // 存入SecurityContextHolder
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                    customUserDetailBO, null, customUserDetailBO.getAuthorities()
            );
    
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            /*response.setStatus(200);
            resultModel.setMsg("用户登录成功");
            HttpUtil.setRespBody(response,resultModel);*/
            filterChain.doFilter(request, response);
        }
    }
    

    配置:

    测试:

    授权实现


    UserDetailService查询数据库权限:

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            if (StrUtil.isBlank(username)) {
                throw new CommonException("用户" + username + "不存在");
            }
    
            /**
             * TODO 查询用户信息,认证
             */
            QueryWrapper queryWrapper = new QueryWrapper();
            queryWrapper.eq("user_name", username);
            User user = userMapper.selectOne(queryWrapper);
    
            if (Objects.isNull(user)) {
                throw new CommonException("该用户不存在");
            }
    
            UserBO userBO = new UserBO();
            BeanUtil.copyProperties(user, userBO);
    
            /**
             * TODO 查询用户的权限信息,授权
             */
            List<Role> roleList = roleMapper.selectRolesByUserId(user.getUserId());
    
            List<RoleBO> roleBOList = roleList.stream().map(role -> {
                RoleBO roleBO = new RoleBO();
                BeanUtils.copyProperties(role, roleBO);
                List<Menu> menuList= menuMapper.selectMenuByRoleId(role.getRoleId());
                List<MenuBO> menuBOList = menuList.stream().map(menu -> {
                    MenuBO menuBO = new MenuBO();
                    BeanUtils.copyProperties(menu, menuBO);
                    log.info("menuBO => {}", menuBO.getMenuCode());
                    return menuBO;
                }).collect(Collectors.toList());
                roleBO.setMenuBOList(menuBOList);
                return roleBO;
            }).collect(Collectors.toList());
    
            return new CustomUserDetailBO(userBO, roleBOList);
        }
    

    为url分配权限:

    @EnableWebSecurity
    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
            http.sessionManagement().sessionCreationPolicy(
                            SessionCreationPolicy.STATELESS
                    )
                    .and()
                    .authorizeRequests()//csrf关闭
                    .antMatchers("/user/r/r1").hasAuthority("01")
                    .antMatchers("/user/r/r2").hasAuthority("02")
                    .antMatchers("/user/r/r3").denyAll()
                    .antMatchers("/user/**").permitAll()//放行
                    .anyRequest().authenticated()
                    .and()
                    .formLogin().permitAll()//表单提交放行
                    .and()
                    .csrf().disable()
            ;
        }
    
        /**
         * 注册解码器
         * @return
         */
        @Bean
        public PasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
    
        /**
         * 暴露AuthenticationManager这个Bean
         * @return
         * @throws Exception
         */
        @Bean
        public AuthenticationManager getAuthenticationManager() throws Exception {
            return super.authenticationManagerBean();
        }
    }
    

    编写CustomUserDetailBO类:

    public Collection<? extends GrantedAuthority> getAuthorities() {
            if(authorities != null){
                return authorities;
            }
    
            authorities = new ArrayList<>();
            //把permission中String类型的权限信息封装成SimpleGrantedAuthority对象
            roleBOList.stream().forEach(roleBO -> {
                roleBO.getMenuBOList().forEach(menuBO -> {
                    SimpleGrantedAuthority authority = new SimpleGrantedAuthority(menuBO.getMenuCode());
                    authorities.add(authority);
                });
            });
    
            return authorities;
        }
    

    过滤器修改:当用户登录成功,为其设置应该提供的权限:

    // 存入SecurityContextHolder
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                    customUserDetailBO, null, customUserDetailBO.getAuthorities()
            );
    
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
    

    登出功能


    思路:通过SecurityContextHolder类获取Authentication(里面封装了我们的登录信息),然后通过Authentication里面存储的User对象的用户id,然后拼接成redis的key获取redis中的对象,将其删除,这样下次拿着token来查询redis对象,发现没有了,就会提示其重新登录。

    @Slf4j
    @RestController
    @RequestMapping("/user")
    public class UserController {
    
        @Autowired
        private ILogoutService logoutService;
    
        @GetMapping("/logout")
        public ResultModel<String> logout() throws CommonException {
            return logoutService.logout();
        }
    }
    
    @Service
    public class LogoutServiceImpl implements ILogoutService {
    
        @Autowired
        private RedisUtil redisUtil;
    
        @Override
        public ResultModel<String> logout() throws CommonException {
            UsernamePasswordAuthenticationToken authentication
                    = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
    
            CustomUserDetailBO customUserDetailBO = (CustomUserDetailBO) authentication.getPrincipal();
            Long userId = customUserDetailBO.getUserBO().getUserId();
            redisUtil.deleteObject("token:" + userId);
    
            ResultModel<String> resultModel = new ResultModel<>();
            resultModel.setTime(TimeUtil.getNowTime());
            resultModel.setMsg("用户注销成功");
            resultModel.setRes(String.valueOf(userId));
            return resultModel;
        }
    }
    

    相关文章

      网友评论

          本文标题:spring security实现权限功能

          本文链接:https://www.haomeiwen.com/subject/omyuortx.html