微人事——Spring Security权限管理(二)

权限管理(二)

权限功能

1、数据库

因为是权限管理所以需要根据用户的role来查找所对应的权限menu
menu表

简单来说分为两步: 第一步,用户先从前端发起一个http请求,拿到http请求地址之后,我先去分析地址和数据库中的menu表中的哪一个url是相匹配的。就先看一下用户的请求地址跟这里边的哪一个是吻合的。

第一步的核心目的是根据用户的请求地址分析出来它所需要的角色,就是当前的请求需要哪些角色才能访问
第二步是去判断当前用户是否具备它需要的角色

2、UrlFilterInvocationSecurityMetadataSource类

通过当前的请求地址,获取该地址需要的用户角色
它的主要责任就是当访问一个url时,返回这个url所需要的访问权限

这个方法需要调用FilterInvocationSecurityMetadataSource接口

第一个方法:从filterInvocation里面可以获取当前请求的地址,拿到地址后,就要拿这个地址去menu里面的每一个项去匹配, 看是符合哪一个模式,然后再去看这个模式需要哪些角色

@Slf4j
@Component
public class UrlFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

    @Autowired
    MenuRepository menuRepository;

    AntPathMatcher antPathMatcher = new AntPathMatcher();


    /**
     * 返回本次访问需要的权限,可以有多个权限
     * collection:当前请求需要的角色 o:实际上是一个filterInvocation对象
     * @return
     * @throws IllegalArgumentException
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        String requestUrl = ((FilterInvocation) o).getRequestUrl();
        //这个方法每次请求都会调用
        List<Menu> allMenu = menuRepository.findAll();
        //比较request跟这menus里面的url是否一致 遍历menus 借助AntPathMatcher工具进行
        for(Menu menu:allMenu){
            if (antPathMatcher.match(menu.getUrl(),requestUrl)
                    && menu.getRoles().size()>0){
                List<Role> roles = menu.getRoles();
                int size = roles.size();
                String[] values = new String[size];
                for(int i = 0;i<size;i++){
                    values[i] = roles.get(i).getName();
                }
                log.info("当前访问路径是{},这个url所需要的访问权限是{}",requestUrl,values);
                return SecurityConfig.createList(values);
            }
        }

        //没有匹配上的资源,都是登陆访问
        log.info("当前访问路径是{},这个url所需要的访问权限是{}","ROLE_LOGIN");
        return SecurityConfig.createList("ROLE_LOGIN");
    }

    /**
     * 此处方法如果做了实现,返回了定义的权限资源列表,
     * Spring Security会在启动时校验每个ConfigAttribute是否配置正确,
     * 如果不需要校验,这里实现方法,方法体直接返回null即可。
     * @return
     */
    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    /**
     * 方法返回类对象是否支持校验,
     * web项目一般使用FilterInvocation来判断,或者直接返回true
     * @param aClass
     * @return
     */
    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

3、MenuService

public interface MenuService {
    List<Menu> getMenusByHrId(Integer id);
}

==getMenusByHrId(Integer id)==这个方法是通过Hr表中的id查询到每个用户所对应的menu,也就是权限,这里我们需要实现这个方法

@Service
public class MenuServiceImpl implements MenuService {
    @Autowired
    private MenuRepository menuRepository;
    @Autowired
    private MenuroleRepository menuroleRepository;
    @Autowired
    private HrRoleRepository hrRoleRepository;

    @Override
    public List<Menu> getMenusByHrId(Integer hrid) {
        List<hrRole> list = hrRoleRepository.findAllByHrid(hrid);
        List<Integer> rids = new ArrayList<>();
        for (hrRole hrrole:list){
            rids.add(hrrole.getRid());
        }
        List<MenuRole> mids = menuroleRepository.findByRidIn(rids);
        List<Integer> menus = new ArrayList<>();
        for (MenuRole menuRole:mids){
           menus.add(menuRole.getMid());
        }
        return menuRepository.findAllByIdIn(menus);
    }
}
public interface MenuRepository extends JpaRepository<Menu,Integer> {
    public List<Menu> findAllById(Integer id);
	//根据数组中的每一个元素查询Menu类
    List<Menu> findAllByIdIn(List<Integer> id);

}
public interface MenuroleRepository extends JpaRepository<MenuRole,Integer> {
    public List<MenuRole> findAllById(Integer id);
    public List<MenuRole> findByRidIn(List<Integer> Rids);
}
public interface HrRepository extends JpaRepository<Hr,Integer> {
    public Hr findByUsername(String username);
    public List<Role> findAllRolesById(Integer id);
}

4、UrlAccessDecisionManager类

判断当前用户是否具备这些角色,需要调用AccessDecisionManager

@Component
public class UrlAccessDecisionManager implements AccessDecisionManager {
    //Collection<ConfigAttribute> collection是是UrlFilterInvocationSecurityMetadataSource中的getAttributes方法传来的,
    // 表示当前请求需要的角色(可能有多个)

    /**
     *
     * @param authentication:包含了当前的用户信息,包括拥有的权限,
     *                      即之前UserDetailsService登录时候存储的用户对象
     * @param o:就是FilterInvocation对象,可以得到request等web资源
     *         configAttributes 是本次访问需要的权限。
     *         即上一步的 UrlFilterInvocationSecurityMetadataSource 中查询核对得到的权限列表
     * @param collection
     * @throws AccessDeniedException
     * @throws InsufficientAuthenticationException
     */
    //用户的角色在authentication里面,需要的角色在configAttributes里面,再去比较他们俩集合里面有没有包含关系就行
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
        //遍历需要的角色
        for (ConfigAttribute configAttribute : collection) {
            //它需要的角色
            String needRole = configAttribute.getAttribute();
            //如果它需要的角色是"ROLE_LOGIN"
            if ("ROLE_LOGIN".equals(needRole)) {
                //如果当前用户是匿名用户的实例的话,就是没登录
                if (authentication instanceof AnonymousAuthenticationToken) {
                    throw new AccessDeniedException("尚未登陆,请登陆");
                } else {
                    return;
                }
            }

            //获取当前登陆用户的角色
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for (GrantedAuthority authority : authorities) {
                //如果这两个东西是相等的
                if (authority.getAuthority().equals(needRole))
                    return;
            }
        }

        throw new AccessDeniedException("权限不足,请联系管理员!");
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

5、修改HrService类

@Service
public class HrService implements UserDetailsService {

    @Autowired
    HrRepository hrRepository;
    @Autowired
    HrRoleRepository hrRoleRepository;
    @Autowired
    RoleRepository roleRepository;

    //通过Hr的id查询Role
    public List<Role> getHrRolesByHrid(Integer hrid){
        List<hrRole> list = hrRoleRepository.findAllByHrid(hrid);
        List<Role> roles = new ArrayList<>();
        for (hrRole hrrole:list){
            roles.add(roleRepository.findById(hrrole.getRid()).get());
        }
        return roles;
    }


    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        Hr hr = hrRepository.findByUsername(s);
        if (hr == null){
            throw new UsernameNotFoundException("用户名不对");
        }
        //给用户设置角色
        hr.setRoles(getHrRolesByHrid(hr.getId()));
        return hr;
    }

}

6、配置webSecurityConfig类

通过==.withObjectPostProcessor()==讲这两个类注入

@Configuration
public class webSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    HrService hrService;
    @Autowired
    UrlFilterInvocationSecurityMetadataSource urlFilterInvocationSecurityMetadataSource;
    @Autowired
    UrlAccessDecisionManager urlAccessDecisionManager;
    @Autowired
    AccessDeniedHandler accessDeniedHandler;




    //明文显示密码
    @Bean
    public CustomPasswordEncoder passwordEncoder() {
        return new CustomPasswordEncoder();
    }

    //定义认证规则
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       auth.userDetailsService(hrService)
              .passwordEncoder(new CustomPasswordEncoder());

    //    auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("user1").password(new BCryptPasswordEncoder().encode("123")).roles("USER");

    }


    /**
     * 通过withObjectPostProcessor将MyFilterInvocationSecurityMetadataSource和MyAccessDecisionManager注入进来
     * 此url先被UrlFilterInvocationSecurityMetadataSource处理,然后 丢给UrlAccessDecisionManager处理
     * 如果不匹配,返回 MyAccessDeniedHandler
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //剩下的其他请求都是登陆之后就能访问的
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                        o.setAccessDecisionManager(urlAccessDecisionManager);
                        o.setSecurityMetadataSource(urlFilterInvocationSecurityMetadataSource);
                        return o;
                    }
                })
                //单表登陆
                .and().formLogin()
                //修改默认登陆的username
                .usernameParameter("username")
                //修改默认登陆的password
                .passwordParameter("password")
                //处理单表登陆的url路径
                .loginProcessingUrl("/doLogin")
                //默认看到的登录页面
                .loginPage("/login")
                //登陆成功的处理
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
                        //如果登陆成功就返回一段json
                        resp.setContentType("application/json;charset=utf-8");
                        //这是往出写的
                        PrintWriter out = resp.getWriter();
                        //登陆成功的hr对象
                        Hr hr = (Hr) authentication.getPrincipal();
                        RespBean ok = RespBean.ok("登陆成功",hr);
                        //把hr写成字符串
                        String s = new ObjectMapper().writeValueAsString(ok);
                        //把字符串写出去
                        out.write(s);
                        out.flush();
                        out.close();
                    }
                })
                //登陆失败的处理
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
                        //如果登陆失败就返回一段json
                        resp.setContentType("application/json;charset=utf-8");
                        PrintWriter out = resp.getWriter();
                        RespBean respBean = RespBean.error("登陆失败!");
                        if (e instanceof LockedException){
                            respBean.setMsg("账户被锁定,请联系管理员!");
                        }else if (e instanceof CredentialsExpiredException){
                            respBean.setMsg("密码过期,请联系管理员!");
                        }else if (e instanceof AccountExpiredException){
                            respBean.setMsg("账户过期,请联系管理员!");
                        }else if (e instanceof DisabledException){
                            respBean.setMsg("账户被禁用,请联系管理员!");
                        }else if (e instanceof  BadCredentialsException){
                            respBean.setMsg("用户名或者密码输入错误,请联系管理员!");
                        }
                        out.write(new ObjectMapper().writeValueAsString(respBean));
                        out.flush();
                        out.close();
                    }
                })
                //跟登陆相关的接口就能直接访问
                .permitAll()
                .and()
                .logout()
                //注销成功后的回调
                .logoutSuccessHandler(new LogoutSuccessHandler() {
                    @Override
                    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                        httpServletResponse.setContentType("application/json;charset=utf-8");
                        PrintWriter out = httpServletResponse.getWriter();
                        out.write(new ObjectMapper().writeValueAsString(RespBean.ok("注销成功!")));
                        out.flush();
                        out.close();
                    }
                })
                .permitAll()
                .and()
                //关闭csrf攻击
                .csrf().disable();
    }
}

这就是完整的webconfig了

7、测试

编写一个controller 测试一下

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello(){
        return "欢迎进入登陆页面";
    }

    @GetMapping("/employee/basic/hello")
    public String hello2(){
        return "/emp/basic/hello";
    }

    @GetMapping("/employee/advanced/hello")
    public String hello3(){
        return "/emp/adv/hello";
    }
}

登陆
成功显示role
在这里插入图片描述
测试权限
成功
在这里插入图片描述
Spring Security在这里就全部完成了

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值