springboot+shiro整合简单流程(quickstart)

第一步:添加依赖

    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.9.1</version>
    </dependency>

    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring-boot-web-starter</artifactId>
        <version>1.10.0</version>
    </dependency>

第二步:创建Config类与UerRealm

在shiro中有这几个权限级别

anon: 无需认证就可以访问
authc: 必须认证了才能访问
user: 必须拥有 记住我才能访问
perms: 拥有对摸个资源的权限才能访问
role: 拥有某个角色的访问权限才能访问

从外部看,shiro通过以下三层架构来实现session,登录认证,密码加密,等待一系列功能业务
在这里插入图片描述

从内部看,shiro支持以下这几种功能
​​

​​​​

shiroConfig中主要有三个方法也基本分别对应图中的三层架构

public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager SecurityManager)

public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm)

public UserRealm userRealm()

其中UserRealm是需要我们自定义的

UerRealm需要继承AuthorizingRealm并实现里面的

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) //授权认证

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException //资格认证

就如注释上写的,doGetAuthenticationInfo主要负责资格认证,

在shiroFilterFactoryBean中我们需要通过HashMap创建一个filterMap并通过setFilterChainDefinitionMap放入创建的ShiroFilterFactoryBean类对象中(这里声明为bean)

例如以下一系列权限声明

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager SecurityManager){

        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();

        //设置安全管理器
        bean.setSecurityManager(SecurityManager);

        //添加shiro的内置过滤器

        /*
            anon: 无需认证就可以访问
            authc: 必须认证了才能访问
            user: 必须拥有 记住我才能访问
            perms: 拥有对摸个资源的权限才能访问
            role: 拥有某个角色的访问权限才能访问
         */
        HashMap filterMap = new LinkedHashMap();


        filterMap.put("/user/add","perms[user:add]");
        filterMap.put("/user/update","perms[user:update]");

        filterMap.put("/user/*","authc");
        bean.setFilterChainDefinitionMap(filterMap);

        //设置需要认证时访问的controller
        bean.setLoginUrl("/toLogin");
        //设置需要授权时访问的controller
        bean.setUnauthorizedUrl("/noAuth");

        return bean;

    }

相对的在同一个包中,我们需要注入一个DefaultWebSecurityManager,配置相关的属性,例如加密方式,迭代次数,绑定Realm等操作。

    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(){

        // 创建defaultWebSecurityManager对象
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //创建加密对象,设置相关属性
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        //采用md5加密
        matcher.setHashAlgorithmName("md5");
        //设置迭代次数
        matcher.setHashIterations(2);

        //将加密对象存储到userRealm对象当中
        userRealm.setCredentialsMatcher(matcher);
        //将userRealm存储到defaultWebSecurityManager当中
        defaultWebSecurityManager.setRealm(userRealm);

        return defaultWebSecurityManager;

    };

认证时所需要的操作(也就是authc级别)

以下是登录时所需要进行的操作,将数据加密封装到token中(这里是默认的明文,如果需要加密需要在shiroconfig中设置加密方法,salt(盐), 以及迭代次数,推荐方式为md5加密)

    @RequestMapping("/Login")
    public String Login(String username,String password,Model model){

        Subject subject = SecurityUtils.getSubject();

        UsernamePasswordToken token = new UsernamePasswordToken(username,password);

        try {
        	//值得注意的是,此时的login会调用一个名为ModularRealmAuthenticator的方法,
        	//该方法会判断是否存在多个Realm会分别调用doSingleRealmAuthentication或者doMultiRealmAuthentication方法,
        	//这两个方法都会返回一个AuthenticationInfo类对象,
        	//该对象通常包含了数据库中通过用户id查询到的密文密码以及用户输入的明文密码以及加密方式
        	//(具体实现在Realm中显示实现)方法源码放在下面
        	// 其中的登录操作是交由shiro来进行的,密码并不需要我们来验证,
        	//由shiro来做这件事(个人认为体现了shiro的安全性)
            subject.login(token);

            //通过subject获取当前用户数据
            Subject currentSubject = SecurityUtils.getSubject();
            Session session = currentSubject.getSession();
            User user = (User)currentSubject.getPrincipal();
            session.setAttribute("loginUser",user);



            return "index";

        } catch (UnknownAccountException e) {
            model.addAttribute("msg","用户名错误");
            return "Login";
        } catch (IncorrectCredentialsException e){
            model.addAttribute("msg","密码错误");
            return "Login";
        }
    }

在ModularRealmAuthenticator类中的方法中可以很清晰地看到,调用了realm中的getAuthenticationInfo方法,该方法获得了从前端传递的用户名和密码,
包装进了info对象中,具体包装过程由自己定义


    protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
        if (!realm.supports(token)) {
            String msg = "Realm [" + realm + "] does not support authentication token [" +
                    token + "].  Please ensure that the appropriate Realm implementation is " +
                    "configured correctly or that the realm accepts AuthenticationTokens of this type.";
            throw new UnsupportedTokenException(msg);
        }
        AuthenticationInfo info = realm.getAuthenticationInfo(token);
        if (info == null) {
            String msg = "Realm [" + realm + "] was unable to find account data for the " +
                    "submitted AuthenticationToken [" + token + "].";
            throw new UnknownAccountException(msg);
        }
        return info;
    }

    /**
     * Performs the multi-realm authentication attempt by calling back to a {@link AuthenticationStrategy} object
     * as each realm is consulted for {@code AuthenticationInfo} for the specified {@code token}.
     *
     * @param realms the multiple realms configured on this Authenticator instance.
     * @param token  the submitted AuthenticationToken representing the subject's (user's) log-in principals and credentials.
     * @return an aggregated AuthenticationInfo instance representing account data across all the successfully
     *         consulted realms.
     */
    protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {

        AuthenticationStrategy strategy = getAuthenticationStrategy();

        AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);

        if (log.isTraceEnabled()) {
            log.trace("Iterating through {} realms for PAM authentication", realms.size());
        }

        for (Realm realm : realms) {

            try {
                aggregate = strategy.beforeAttempt(realm, token, aggregate);
            } catch (ShortCircuitIterationException shortCircuitSignal) {
                // Break from continuing with subsequnet realms on receiving 
                // short circuit signal from strategy
                break;
            }

            if (realm.supports(token)) {

                log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm);

                AuthenticationInfo info = null;
                Throwable t = null;
                try {
                    info = realm.getAuthenticationInfo(token);
                } catch (Throwable throwable) {
                    t = throwable;
                    if (log.isDebugEnabled()) {
                        String msg = "Realm [" + realm + "] threw an exception during a multi-realm authentication attempt:";
                        log.debug(msg, t);
                    }
                }

                aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);

            } else {
                log.debug("Realm [{}] does not support token {}.  Skipping realm.", realm, token);
            }
        }

        aggregate = strategy.afterAllAttempts(token, aggregate);

        return aggregate;
    }

以下是在Realm中自己定义的认证方法,在密码或用户名错误时会抛出对应异常,如果账户被锁定也会拒绝访问并抛出异常。

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {


        UsernamePasswordToken userToken = (UsernamePasswordToken)token;

        QueryWrapper<User> qw = new QueryWrapper();

        String userName = userToken.getUsername();
        qw.select("pwd" , "id" , "locked" , "name").eq("user_id" , userName);
        User user = userMapperPlus.selectOne(qw);

        if(user == null) {
            throw new UnknownAccountException();
        }else if(user.getLocked().equals(1)){
            throw new LockedAccountException();
        }


        System.out.println(token.getPrincipal() + user.getName());

        return new SimpleAuthenticationInfo(token.getPrincipal() ,  user.getPwd(), ByteSource.Util.bytes(userToken.getUsername() + user.getName()),  userName);
    }

如果密码不正确会抛回controller层执行相应代码,如果验证成功则正常返回页面
(注意,这里的principal,也就是返回值方法的第一个参数是object类,
用来传递用户的相关信息,此信息可以通过subject对象获取)

subject获取方式为

Subject currentSubject = SecurityUtils.getSubject();
通过getPrincipal()方法可以获取

以下是授权时所要做的事(也就是perms级别)

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        //获取当前用户信息
        Subject subject = SecurityUtils.getSubject();
        User currentUser = (User)subject.getPrincipal();

        //进行授权
        info.addStringPermission(currentUser.getPerms());

        System.out.println("执行了授权认证");

        return info;
    }

bean.setUnauthorizedUrl("/noAuth");

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值