springboot结合shiro的demo

项目结构

在这里插入图片描述

如图所示,项目一共分为6层,分别是:

  • common公共层:主要是放置一些公共的模块
  • controller层:数据的表示层,俗称vo
  • dao层: 用于操作数据库,增删改查
  • Exception异常层:定义一些全局的异常,方便维护
  • model层: 数据库表的映射
  • shiro层:主要是配置shiro的授权和认证

认证过程

// LoginController.java
@PostMapping("/login")
    public Response login(@RequestBody User user) {
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(user.getUsername(),user.getPassword());
        Subject subject = SecurityUtils.getSubject();
        subject.login(usernamePasswordToken);
        if(!subject.isAuthenticated()) {
            throw new AuthenticationException("认证失败");
        }
        return new Response(200,"登录成功",user);
    }
    
    
// CustomRealm.java
 // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        log.info("**************authenticationToken->{}", JSONUtil.toJsonStr(authenticationToken));
        if(authenticationToken.getPrincipal() == null) {
            return null;
        }
        // 获得用户名
        String username = authenticationToken.getPrincipal().toString();
        User user = userDao.findByUsername(username);
        if(user == null) {
            throw new UnknownAccountException("用户名或密码错误");
        }

        // 认证信息
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(), ByteSource.Util.bytes("wuyilong"),getName());
        log.info("**************返回认证结果->{}", JSONUtil.toJsonStr(simpleAuthenticationInfo));
        return simpleAuthenticationInfo;
    }

用户发起登录请求,经过login控制器,接受用户名和密码初始化一个用户密码认证token,然后再用SecurityUtils(这个封装了SecurityManager)获得当前环境的主体Subject、,通过login的方法,接收token,实质上就是SecurityManager里面的login方法,这里进行跳转到我们自定义的CustomRealm,进行真正的认证,认证通过就会返回simpleAuthenticationInfo,实质上就是principa(代码上的user.getUsername())l用于授权。

授权过程

// UserController.java
 @RequiresPermissions("/user/page")
    @GetMapping("/page")
    public Response<User> getUserPage(@RequestParam(value = "pageNum",defaultValue = "1")  Integer pageNum,
                                      @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
        Sort sort = Sort.by(Sort.Direction.DESC, "id");
        Pageable pageable = PageRequest.of(pageNum-1, pageSize, sort);
        Page<User> userPage = userDao.findAll(pageable);
        return new Response(200,"操作成功",userPage);
    }
    
 // CustomRealm.java
 // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        log.info("**********principalCollection->{}",principalCollection);
        // 获得用户名 这个用户名是从认证来的
        String username = principalCollection.getPrimaryPrincipal().toString();
        User user = userDao.findByUsername(username);
        // 授权信息
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        // 添加角色
        Role role = roleDao.findById(user.getRoleId()).get();
        simpleAuthorizationInfo.addRole(role.getRoleName());
        // 添加权限
        List<RolePermission> rolePermissionListList = rolePermissionDao.findByRoleId(role.getId());
        rolePermissionListList.stream()
                .map(rolePermission -> permissionDao.findById(rolePermission.getPermissionId()).get())
                .map(Permission::getAction)
                .forEach(simpleAuthorizationInfo::addStringPermission);

        log.info("*********返回授权结果->{}",JSONUtil.toJsonStr(simpleAuthorizationInfo));
        return simpleAuthorizationInfo;

    }

用户首先访问带有权限控制的注解@RequiresPermissions,从而进入授权过程。查询数据库当前用户的角色和权限,分别添加到simpleAuthorizationInfo并返回.这个时候我们还不清楚到底是怎么验证授权的,只有debug了!

 public void assertAuthorized(Annotation a) throws AuthorizationException {
        if (!(a instanceof RequiresPermissions)) return;

        RequiresPermissions rpAnnotation = (RequiresPermissions) a;
        String[] perms = getAnnotationValue(a);
        Subject subject = getSubject();

        if (perms.length == 1) {
            subject.checkPermission(perms[0]);
            return;
        }
        if (Logical.AND.equals(rpAnnotation.logical())) {
            getSubject().checkPermissions(perms);
            return;
        }
        if (Logical.OR.equals(rpAnnotation.logical())) {
            // Avoid processing exceptions unnecessarily - "delay" throwing the exception by calling hasRole first
            boolean hasAtLeastOnePermission = false;
            for (String permission : perms) if (getSubject().isPermitted(permission)) hasAtLeastOnePermission = true;
            // Cause the exception if none of the role match, note that the exception message will be a bit misleading
            if (!hasAtLeastOnePermission) getSubject().checkPermission(perms[0]);
            
        }
    }
}

debug到里面你会看到如上的代码,可以看到,解析了注解上的权限标识,并检测当前环境下的主体用户是否拥有这个权限。

ShiroConfig

/**
 * @ClassName ShiroConfig shiro的配置文件
 * @Description
 * @Author yilongwu
 * @DATE 2020-03-11 15:34
 * @Version 1.0.0
 **/
@Configuration
@Slf4j
public class ShiroConfig {

    /**
     * 把自定义的CustomRealm注入到spring容器中
     * @return
     */
    @Bean
    public CustomRealm customRealm() {
        CustomRealm customRealm = new CustomRealm();
        customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return customRealm;

    }

    /**
     * 注入securityManager
     * @return
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(customRealm());
        return securityManager;
    }

    // 设置用于匹配密码的CredentialsMatcher
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName(Md5Hash.ALGORITHM_NAME);  // 散列算法,这里使用更安全的sha256算法
        credentialsMatcher.setStoredCredentialsHexEncoded(false);  // 数据库存储的密码字段使用HEX还是BASE64方式加密
        credentialsMatcher.setHashIterations(1);  // 散列迭代次数
        return credentialsMatcher;
    }


    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 设置securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 这个在做前后端分离时,如果你没有登录就访问其他的资源就会跳到这个设置的url
        shiroFilterFactoryBean.setLoginUrl("/notLogin");
        // 没有权限时进行跳转(ps:在没有使用注解的情况下能自动捕获异常,并跳转到该指定的路径)
        //shiroFilterFactoryBean.setUnauthorizedUrl("/unAuthorized");


        // 设置拦截器
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

        // 开放接口
        filterChainDefinitionMap.put("/login","anon");;
        // 其余的需要认证
        filterChainDefinitionMap.put("/**","authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;

    }


    //加入注解的使用,不加入这个注解不生效
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

这个配置文件有很多注释了,并不用多说,这里要说的是密码匹配器,其实还有很多密码匹配器的
查看源代码就知道,一共有6种。

在这里插入图片描述

那么如何生成呢?

 String wuyilong = new SimpleHash(Md5Hash.ALGORITHM_NAME, "123456", ByteSource.Util.bytes("wuyilong"), 1).toBase64();
        System.out.println(wuyilong);

shiro里面自带有生成的类,我们直接调用即可。

最后

其他的就不多说了,需要的盆友,请到我的github上下载。
项目地址:springboot-shiro

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值