前后端分离整合shiro做权限控制

本文介绍了如何在Spring应用中添加Shiro依赖,并详细解读了一个自定义的UserRealm类,涉及用户认证与权限授权的过程。重点讲解了如何重写`getAuthenticationInfo`和`getAuthorizationInfo`方法,以及如何在Shiro配置中设置认证过滤器和角色权限拦截。

添加 shiro依赖

      <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.5.3</version>
        </dependency>

首先写 自定义Realm

public class UserRealm extends AuthorizingRealm {

    @Autowired(required = false)
    private SysUserService sysUserService;

    @Autowired(required = false)
    SysUserRoleMapper sysUserRoleMapper;

    // AuthorizationInfo代表了角色的权限信息集合 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        System.out.println("执行了=>授权doGetAuthorizationInfo");
        // 授权
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        Subject subject = SecurityUtils.getSubject();
        // 获取当前user
        SysUser user = (SysUser) subject.getPrincipal();

        //
        Map<String,Object> map = new HashMap<>();
        map.put("userId",user.getId());
        List<SysRole> userRoleList = sysUserRoleMapper.selRoleByuserId(map);
        Set<String> permissionCode = new HashSet<>();
        Set<String> roleName = new HashSet<>();

        for (SysRole userRole : userRoleList) {
            roleName.add(userRole.getRoleName());
        }

        // 将 角色名称级权限的名称放进 info中 以供 shiroConfig 拦截
        info.setRoles(roleName);
        return info;
    }


    /**
     * 用户信息的认证
     *
     * 该方法主要执行以下操作:
     *      1、检查提交的进行认证的令牌信息
     *      2、根据令牌信息从数据源(通常为数据库)中获取用户信息
     *      3、对用户信息进行匹配验证。
     *      4、验证通过将返回一个封装了用户信息的AuthenticationInfo实例。
     *      5、验证失败则抛出AuthenticationException异常信息。
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行了=>认证 doGetAuthorizationInfo");
        UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
        String username = userToken.getUsername();
        SysUser user = sysUserService.getUserByName(username);
        if (user == null){
            return null;
        }
        String paw1 = String.valueOf(userToken.getPassword());
        String paw2 = RSAUtil.privateKeyEncryption(paw1);
        String paw3 = DigestUtil.md5Hex(paw2);
        userToken.setPassword(paw3.toCharArray());
        // 密码认证
        return new SimpleAuthenticationInfo(user, user.getPassword(),this.getName());
    }
}

解读代码

继承 AuthorizingRealm 需要重写 两个方法,一个是 用户认证的方法,一个是 给用户 授权的方法
1、用户认证

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行了=>认证 doGetAuthorizationInfo");
        UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
        String username = userToken.getUsername();
        SysUser user = sysUserService.getUserByName(username);
        if (user == null){
            return null;
        }
        // 用户输入的密码
        String paw1 = String.valueOf(userToken.getPassword());
        
        // 因为数据库存的是 MD5,多以对密码进行加密
        String paw3 = DigestUtil.md5Hex(paw2);
        userToken.setPassword(paw3.toCharArray());
        // 密码认证
        return new SimpleAuthenticationInfo(user, user.getPassword(),this.getName());
    }

通过 userToken 中的用户名 查询用户信息 检验用户密码等 判断用户信息是否正确。

2、将用户的权限角色信息 写进info

    // AuthorizationInfo代表了角色的权限信息集合 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        System.out.println("执行了=>授权doGetAuthorizationInfo");
        // 授权
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        Subject subject = SecurityUtils.getSubject();
        // 获取当前user
        SysUser user = (SysUser) subject.getPrincipal();

        //
        Map<String,Object> map = new HashMap<>();
        map.put("userId",user.getId());
        List<SysRole> userRoleList = sysUserRoleMapper.selRoleByuserId(map);
        // 这个放 用户权限的CODE
        Set<String> permissionCode = new HashSet<>();
        // 这个放用户角色的名字
        Set<String> roleName = new HashSet<>();

        for (SysRole userRole : userRoleList) {
            roleName.add(userRole.getRoleName());
        }

        // 将 角色名称级权限的名称放进 info中 以供 shiroConfig 拦截
        info.setRoles(roleName);
        return info;
    }

3、在登陆的接口 需要加上这个,返回信息

  @GetMapping("/login")
    public String login(String username,String password,Model model){
        // 获取 一个用户
        Subject subject = SecurityUtils.getSubject();
        // 封装用户的登录数据
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        // 执行登录的方法 无异常就ok了
        try{
            subject.login(token);

        }catch (UnknownAccountException e){
            model.addAttribute("msg","用户名错误");
            return "login";
        }catch (IncorrectCredentialsException e) {//密码不存在
            model.addAttribute("msg","密码错误");
            return "login";
        }catch (LockedAccountException e){
            model.addAttribute("msg","账户被锁定");
            System.out.print("账户被锁定:"+e.getMessage());
            return "login";
        }
        // 当前用户信息被保存进Session
        return "index";
        // 登录成功 将当前用户保存在session中
    }

shiro 配置


@Configuration
public class ShiroConfig {
    // 1、创建realm对象 需要自定义类
    @Bean
    public UserRealm userRealm(){
        return new UserRealm();
    }

    /**
     * @Qualifier 我们可以消除需要注入哪个 bean 的问题
     * @param userRealm
     * @return
     */
    // 2、DefaultWebSecurityManager
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        // 关联userRealm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    /**
     认证过滤器:
     anon:无需认证即可访问,游客身份。
     authc:必须认证(登录)才能访问。
     authcBasic:需要通过 httpBasic 认证。
     user:不一定已通过认证,只要是曾经被 Shiro 记住过登录状态的用户就可以正常发起请求,比如 rememberMe。

     授权过滤器:
     perms:必须拥有对某个资源的访问权限(授权)才能访问。
     role:必须拥有某个角色权限才能访问。
     port:请求的端口必须为指定值才可以访问。
     rest:请求必须是 RESTful,method 为 post、get、delete、put。
     ssl:必须是安全的 URL 请求,协议为 HTTPS。

     Filter Chain 定义说明:

     1、一个URL可以配置多个 Filter,使用逗号分隔
     2、当设置多个过滤器时,全部验证通过,才视为通过
     3、部分过滤器可指定参数,如 perms,roles
     *
     * bean.setLoginUrl: 请求被拦截 跳转拦截成功页面
     * setSuccessUrl:授权成功 跳转 通过的页面
     * setUnauthorizedUrl:授权失败 跳转 失败的页面
     *
     * @param defaultWebSecurityManager
     * @return
     */
    // 3、shiroFilterFactoryBean 拦截
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        // 设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);
        // 拦截器
        Map<String,String> filterMap = new HashMap<>();
        filterMap.put("/api/project/deleteProject","roles[administrator]");

        bean.setFilterChainDefinitionMap(filterMap);
        return bean;
    }

}

解读代码
1、创建realm 对象

  @Bean
    public UserRealm userRealm(){
        return new UserRealm();
    }

2、

    // 2、DefaultWebSecurityManager
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        // 关联userRealm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

 /**
     认证过滤器:
     anon:无需认证即可访问,游客身份。
     authc:必须认证(登录)才能访问。
     authcBasic:需要通过 httpBasic 认证。
     user:不一定已通过认证,只要是曾经被 Shiro 记住过登录状态的用户就可以正常发起请求,比如 rememberMe。

     授权过滤器:
     perms:必须拥有对某个资源的访问权限(授权)才能访问。
     role:必须拥有某个角色权限才能访问。
     port:请求的端口必须为指定值才可以访问。
     rest:请求必须是 RESTful,method 为 post、get、delete、put。
     ssl:必须是安全的 URL 请求,协议为 HTTPS。

     Filter Chain 定义说明:

     1、一个URL可以配置多个 Filter,使用逗号分隔
     2、当设置多个过滤器时,全部验证通过,才视为通过
     3、部分过滤器可指定参数,如 perms,roles
     *
     * bean.setLoginUrl: 请求被拦截 跳转拦截成功页面
     * setSuccessUrl:授权成功 跳转 通过的页面
     * setUnauthorizedUrl:授权失败 跳转 失败的页面
     *
     * @param defaultWebSecurityManager
     * @return
     */
    // 3、shiroFilterFactoryBean 拦截
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        // 设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);
        // 拦截器
        Map<String,String> filterMap = new HashMap<>();
        filterMap.put("/api/project/deleteProject","roles[administrator]");

        bean.setFilterChainDefinitionMap(filterMap);
        return bean;
    }

此处配置的 拦截器 在 删除项目的接口 用户的角色需要是 administrator,否则会报401 无权限异常。用户角色在 Realm中 放进了 info 当然这个也可以 根据权限 来拦截请求

### Spring Boot 前后端分离 Shiro 整合实现用户认证和权限管理 #### 项目搭建与依赖引入 为了构建支持前后端分离并集成了Shiro安全机制的Spring Boot应用,首先需要创建一个新的Spring Boot项目,并添加必要的依赖项。除了常规的Web开发所需依赖外,还需加入Apache Shiro的相关库来负责安全控制。 ```xml <dependencies> <!-- 其他基础依赖 --> <!-- shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> </dependencies> ``` 此部分描述了如何设置项目的初始结构以及所需的Maven坐标以包含Shiro功能[^1]。 #### 配置Shiro过滤器链定义 在`application.properties`文件或者通过Java配置类中指定Shiro Filter Chain Definitions, 定义哪些URL模式应该被哪种类型的拦截器处理。特别需要注意的是路径匹配规则中的顺序问题;更具体的路径应当优先于通配符路径声明,以免影响预期的行为。 例如,在配置文件里可以这样写: ```properties # application.properties 中的部分内容 shiro.filterChainDefinitions= /css/**=anon,/js/**=anon,/images/**=anon,/login=anon, /**=authc ``` 这里指出了对于静态资源(如CSS、JavaScript 文件)允许匿名访问(`anon`),而其他所有请求则需经过身份验证(`authc`)才能继续[^3]。 #### 自定义SecurityManager Bean 和 Realm 实现 为了让Shiro能够理解应用程序特有的逻辑——比如从数据库加载用户的凭证信息或角色分配情况,则通常还需要自定义`Realm`组件并与之关联起来的一个`SecurityManager` bean实例。这一步骤涉及到编写相应的Java代码片段用于扩展AbstractAuthorizingRealm抽象基类,并重载其中的方法完成具体业务需求。 ```java @Configuration public class ShiroConfig { @Bean(name = "securityManager") public DefaultWebSecurityManager securityManager(MyCustomRealm myCustomRealm){ DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); manager.setRealm(myCustomRealm); return manager; } } ``` 上述代码展示了怎样注册一个名为`securityManager`的服务对象给IoC容器的同时注入了一个实现了定制化realm接口的对象作为参数传递进去。 #### 处理前端发送过来的身份验证请求 当采用RESTful API风格设计时,客户端会向服务器提交带有用户名密码的数据包来进行登录操作。此时可以在控制器层面上捕获这些POST调用并通过Shiro提供的工具方法尝试建立有效的Subject上下文环境。 ```java @PostMapping("/doLogin") @ResponseBody public String doLogin(@RequestParam String username,@RequestParam String password){ Subject currentUser = SecurityUtils.getSubject(); if (!currentUser.isAuthenticated()) { UsernamePasswordToken token = new UsernamePasswordToken(username,password); try{ currentUser.login(token); // 尝试登陆... return "success"; }catch (AuthenticationException e){ return "failure"; } } ... } ``` 这段程序说明了接收来自HTTP POST请求携带的信息之后利用Shiro框架内部封装好的API去校验传入账号的有效性[^2]。 #### 结论 综上所述,通过合理规划项目模块间的协作关系,适当调整中间件层面的各项设定再加上精心编写的后台服务端口就能顺利达成基于Spring Boot平台之上运用Shiro技术栈实施细粒度级别的访问控制目标。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值