springboot+shiro+layuimini实现后台管理系统的权限控制(二)利用shiro实现对登录用户身份的认证

一、原理

所谓身份认证就是在应用中谁能证明他就是他本人。一般提供如他们的身份 ID 一些标识信息来表明他就是他本人,如提供身份证,用户名 / 密码来证明。

在 Shiro 中,用户需要提供 principals (身份)和 credentials(证明)给 shiro,从而应用能验证用户身份:

principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个 principals,但只有一个 Primary principals,一般是用户名 / 密码 / 手机号。

credentials:证明 / 凭证,即只有主体知道的安全值,如密码 / 数字证书等。

在正式前,除了上面说到的principals和 credentials,还需要对几个概念进行说明:

Subject:主体,可以理解为用户。

UsernamePasswordToken:顾名思义,就是基于用户名和密码所生成的令牌,Realm也需要这个令牌来认证用户的信息。

Realm:Shiro通过Realm的doGetAuthenticationInfo()和doGetAuthorizationInfo()来实现对用户身份的认证和授权。在登录的这个场景下,我们需要将用户提交的身份信息与数据库中该用户的身份信息在Realm中进行对比,因此我们也可以将Realm看作是Shiro的安全数据源。

SecurityManager:安全管理器,这是整个shiro中最重要的一个组件,我们需要将刚刚提到的Realm注入到安全管理器中,才能实现认证与授权等工作。而且所有的会话、缓存也都是通过注入到安全管理器中去实现的。

ShiroFilterFactoryBean:顾名思义就是拦截器,我们可以在这里设置拦截器来拦截需要特定权限或角色的资源(url)。shiro也为我们提供了丰富的拦截器以及更细粒度的权限分级来帮助我们更好的进行权限的管理与配置。同时,SecurityManager也需要注入到其中

整个认证流程大致是这样的:

前端将用户名密码通过接口传到后端,生成一个UsernamePasswordToken,然后调用当前Subjectlogin(token)方法,将token通过SecurityManager传递给Realm的doGetAuthenticationInfo(),在这个方法中实现认证。

二、具体实现

1、配置shiro:

1.1、自定义Realm:

public class ShiroRealm extends AuthorizingRealm {

    @Autowired
    SysUserService sysUserService;

    @Autowired
    SysResourceService sysResourceService;

    /**
     * 授权
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }


    /**
     *  认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String userName = (String) token.getPrincipal();
        SysUser sysUser = sysUserService.findUserByName(userName);
        //用户不存在
        if (sysUser == null) {
            return null;
        }
        //验证密码,使用加盐md5进行加密
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(sysUser, sysUser.getPassword(), getName());
        authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(userName));
        return authenticationInfo;
    }
}

说明:本文暂时先只实现了登录的认证操作,因此暂时对授权这块不做处理。相关service层的代码包括数据库的表结构等之后我会打包上传,都是基础代码,也不是本文的重点,这里就不再做过多的说明了。

可以注意到,我这里SimpleAuthenticationInfo里传入的第一个参数是一个user对象,而在w3cSchool以及其他的一些例子中,传入的是userName,其实都是可以的,因为SimpleAuthenticationInfo只认证密码的有效性,用户名的有效性在之前就已经认证过了,如果用户不存在就直接返回null了。这里传入user对象,主要是方便在授权时使用,这个以后再说。

密码这块,这里做了一个以用户名作为“盐”做了一个加盐的加密处理。由于这里使用了加盐散列算法,因此我们需要对Realm(ShiroConfig.java中)设置对应规则的CredentialMatchers(用于匹配用户输入的token的凭证(未加密)与系统提供的凭证(已加密))。因此我们在“注册”的时候,也需要把加了盐的密码存到数据库中,加盐方法:

 public static String addSalt(String password, String salt){
        //散列次数
        Integer hashIterations = 2;
        //利用SimpleHash来设置md5(上面三种都可以通过这个来设置,这里举例加盐加散列次数的)
        //第一个参数是算法名称,这里指定md5,第二个是要加密的密码,第三个参数是加盐,第四个是散列次数
        SimpleHash hash = new SimpleHash("md5", password, salt, hashIterations);
        System.out.println(hash.toString());
        return hash.toString();
    }

1.2、shiro配置类

与SpringSecurity类似,如果想要让认证与授权流程生效,就需要在项目启动时,将Realm、SecurityManager、ShiroFilterFactoryBean加载到项目中,所以我们需要新建一个配置类ShiroConfig:

/**
 * shiro配置
 */
@Configuration
public class ShiroConfig {

    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //设置过滤拦截器
        Map<String, String> filterMap = new LinkedHashMap<>();
        /*
            anon:  无需认证就能访问
            authc: 必须认证才能访问
            user:  必须拥有 记住我 功能才能用
            perms: 拥有对某个资源的权限才能访问;
            role:拥有某个角色权限才能访问;
        */
       //静态资源不拦截
        filterMap.put("/static/**","anon");
        filterMap.put("/css/**","anon");
        filterMap.put("/images/**","anon");
        filterMap.put("/js/**","anon");
        filterMap.put("/lib/**","anon");

        //登录相关资源不拦截
        filterMap.put("/jumpToLogin","anon");
        filterMap.put("/page/login-3","anon");
        filterMap.put("/api/login","anon");

        //其他资源需要进行认证
        filterMap.put("/**", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);

        //设置登录页
        shiroFilterFactoryBean.setLoginUrl("/jumpToLogin");
        //设置未授权页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/unAuthorized");
//        shiroFilterFactoryBean.setSuccessUrl("/");
        return shiroFilterFactoryBean;
    }

    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultSecurityManager(@Qualifier("shiroRealm") ShiroRealm shiroRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(shiroRealm);
        return securityManager;
    }


    @Bean(name = "shiroRealm")
    public ShiroRealm shiroRealm(){
        ShiroRealm shiroRealm = new ShiroRealm();
        //设置CredentialMatchers
        shiroRealm.setCredentialsMatcher(credentialsMatcher());
        return shiroRealm;
    }

    //加密规则设置,与之前的addSalt相对应
    public HashedCredentialsMatcher credentialsMatcher() {
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        //加密方式
        credentialsMatcher.setHashAlgorithmName("md5");
        //加密次数
        credentialsMatcher.setHashIterations(2);
        // true 密码加密用hex编码; false
        credentialsMatcher.setStoredCredentialsHexEncoded(true);
        return credentialsMatcher;
    }


}

2、接口获取用户名,密码生成UsernamePasswordToken并提交认证:

@Controller
@RequestMapping("/api")
public class ApiController {

    @Autowired
    SysMenuService sysMenuService;

    @RequestMapping("/login")
    @ResponseBody
    public ResponseVO login(String username, String password) {
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        //获取当前的Subject
        Subject currentUser = SecurityUtils.getSubject();
        try {
            // 在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查
            // 每个Realm都能在必要时对提交的AuthenticationTokens作出反应
            // 所以这一步在调用login(token)方法时,它会走到xxRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法
            currentUser.login(token);

            //认证通过,将菜单信息返回给前端展示
            if (currentUser.isAuthenticated()) {
                MenuSystem menuSystem = sysMenuService.findMenuTree();
                Gson gson = new Gson();
                String menuJson = gson.toJson(menuSystem, MenuSystem.class);
                return ResultUtil.success("登录成功!", menuJson);
            }
        } catch (UnknownAccountException e1) {
            e1.printStackTrace();
            return ResultUtil.error("用户名不存在!");
        } catch (IncorrectCredentialsException e2) {
            e2.printStackTrace();
            return ResultUtil.error("密码输入错误!");
        } catch (Exception e) {
            e.printStackTrace();
            return ResultUtil.error(e.getMessage());
        }
        return ResultUtil.error("登录失败!");
    }

说明:这里的菜单数据并没有使用shiro标签来实现,因为layuimini的菜单渲染是通过json数据的形式来实现的,所以这里我也是将菜单数据转换成了json给前端。

如果认证通过,那么通过subject.isAuthenticated()返回的结果就会是true,我们就可以认为登录成功了,此时,可以就可以将对应的菜单信息传回给前端渲染展示了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值