Shiro

 

 

 Realm相当于数据源,SecurityManager从Reaml获取相应的用户进行比较

Shiro的架构

 

 

  • Subject:任何可以与用户交互的用户
  • SecurityManager:所有具体的交互都由SecurityManager控制,它管理所有的Subject,切负责进行认证,授权,会话即缓存管理
  • Authenticator:认证器,负责Subject的认证,可以自定义实现。可以使用认证策略来定义什么情况下算用户认证通过
  • Authorizer: 授权器,即访问控制器,确定主体是否有权限进行相应的操作。
  • Realm:用于获取安全实体,可以有一个或多个。一般在用于中都需要自己实现Realm
  • SessionManager:管理Session生命周期的组件。
  • SessionDao:代表SessionManager执行Session持久化操作
  • CacheManager:缓存控制器,用来管理用户,角色,权限等缓存
  • Cryptography:密码模块,加密解密。

初步使用

导入依赖

<dependencies>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.21</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>1.7.21</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
</dependencies>

编写Shiro配置

shiro.ini

[users]
root = secret, admin
guest = guest, guest
presidentskroob = 12345, president
darkhelmet = ludicrousspeed, darklord, schwartz
lonestarr = vespa, goodguy, schwartz
[roles]
admin = *
schwartz = lightsaber:*
goodguy = winnebago:drive:eagle5

log4j.properties

log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
# General Apache libraries
log4j.logger.org.apache=WARN
# Spring
log4j.logger.org.springframework=WARN
# Default Shiro logging
log4j.logger.org.apache.shiro=INFO
# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN

使用shiro API

package com.deng;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Quickstart {
    private static final transient Logger log =
            LoggerFactory.getLogger(Quickstart.class);

    public static void main(String[] args) {
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        Subject currentUser = SecurityUtils.getSubject();
        Session session = currentUser.getSession();
        session.setAttribute("someKey", "aValue");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("Retrieved the correct value! [" + value + "]");
        }
        if (!currentUser.isAuthenticated()) {
            UsernamePasswordToken token = new
                    UsernamePasswordToken("lonestarr", "vespa");
            token.setRememberMe(true);
            try {
                currentUser.login(token);
            } catch (UnknownAccountException uae) {
                log.info("There is no user with username of " +
                        token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
                log.info("Password for account " +
                        token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) {
                log.info("The account for username " +
                        token.getPrincipal() + " is locked. " +
                        "Please contact your administrator to unlock it.");
            } catch (AuthenticationException ae) {
            }
        }
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }
        if (currentUser.isPermitted("lightsaber:wield")) {
            log.info("You may use a lightsaber ring. Use it wisely.");
        } else {
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
        }
        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
                    "Here are the keys - have fun!");
        } else {
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }
        currentUser.logout();
        System.exit(0);
    }
}

解析上面的代码:

1.通过工厂模式创建SecurityManager实例对象

Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);

2.获取当前的subject

Subject currentUser = SecurityUtils.getSubject();

3.session操作

Session session = currentUser.getSession();//获取session
session.setAttribute("someKey", "aValue");//设置session的值
String value = (String) session.getAttribute("someKey");//从session中获取值
if (value.equals("aValue")) {//判断session中是否存在值
       log.info("Retrieved the correct value! [" + value + "]");
}

4.用户认证功能

 if (!currentUser.isAuthenticated()) {//当前用户是否已经认证
            //将用户名和密码封装为UsernamePasswordToken
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            //开启记住我功能
            token.setRememberMe(true);
            try {
                currentUser.login(token);//执行登录
            } catch (UnknownAccountException uae) {//如果没有指定用户,抛出该异常
                log.info("There is no user with username of " +
                        token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {//如果密码不对,抛出该异常
                log.info("Password for account " +
                        token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) {//如果用户被锁定抛出该异常
                log.info("The account for username " +
                        token.getPrincipal() + " is locked. " +
                        "Please contact your administrator to unlock it.");
            } catch (AuthenticationException ae) {//认证异常,上面的异常都是其子类

            }
        }
        //getPrincipal()获取标识主体
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

5.角色检查

       //检测用户是否拥有某一角色
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }

6.权限检查

       //测试用户是否具有某一权限,行为
        if (currentUser.isPermitted("lightsaber:wield")) {
            log.info("You may use a lightsaber ring. Use it wisely.");
        } else {
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
        }
        //测试用户是否具有某一权限,行为,比上面更加具体
        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
                    "Here are the keys - have fun!");
        } else {
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }

7.注销

currentUser.logout();

SpringBoot集成

导入Shiro和Spring整合的依赖

<dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
</dependency>

Realm是Shiro的连接数据,创建Realm类继承AuthorizingRealm类,重写认证和授权逻辑

public class UserRealm extends AuthorizingRealm {
    //执行授权逻辑
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行了授权逻辑");
        return null;
    }
    //执行认证逻辑
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行了认证逻辑");
        return null;
    }
}

编写ShiroConfig配置类,对请求进行过滤

@Configuration
public class ShiroConfig {
    //创建ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager")DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        /*添加内置过滤器,常用过滤器如下:
           anon:无需认证就可以访问
           authc:必须认证才可以访问
           user:如果使用了记住我功能就可以直接访问
           perms:拥有某个资源权限才可以访问
           role:拥有某个角色才可以访问
        */
        Map<String,String> filterMap=new LinkedHashMap<>();
        filterMap.put("/user/add","authc");
        filterMap.put("/user/update","authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        //配置跳转页面
        shiroFilterFactoryBean.setLoginUrl("/toLogin");
        return shiroFilterFactoryBean;
    }
    //创建DefaultWebSecurityManager
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm")UserRealm userRealm){
        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
        //关联Realm
        securityManager.setRealm(userRealm);
        return securityManager;
    }
    //创建realm
    @Bean
    public UserRealm userRealm(){
        return new UserRealm();
    }

}

登录认证操作

Controller

@Controller
public class LoginController {
    //登录认证
    @RequestMapping("/login")
    public String login(String username, String password, Model model){
        //获取Subject
        Subject subject=SecurityUtils.getSubject();
        //封装用户数据
        UsernamePasswordToken token=new UsernamePasswordToken(username,password);
        //执行登录操作
        try{
            subject.login(token);//登录成功就返回首页
            return "index";
        }catch (UnknownAccountException e){
            model.addAttribute("msg","用户名不存在");
            return "login";
        }catch (IncorrectCredentialsException e){
            model.addAttribute("msg","密码错误");
            return "login";
        }
    }
}

在登录认证是会执行Realm里面的认证逻辑

编写Realm的认证逻辑

 //执行认证逻辑
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行了认证逻辑");
        String name="root";
        String password="123456";
        //将token强转为UsernamePasswordToken
        UsernamePasswordToken token=(UsernamePasswordToken)authenticationToken;
        //判断用户名,不存在直接返回
        if(!token.getUsername().equals(name)){
            return  null;
        }
        //验证密码,使用AuthenticationInfo实现类SimpleAuthenticationInfo,shiro会自动验证密码
        return new SimpleAuthenticationInfo("",password,"");
    }

 整合数据库

配置好mybatis查询逻辑

改造Realm

 //执行认证逻辑
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行了认证逻辑");
        //将token强转为UsernamePasswordToken
        UsernamePasswordToken token=(UsernamePasswordToken)authenticationToken;
        User user=userService.queryUserByName(token.getUsername());
        //判断用户名,不存在直接返回
        if(user==null){
            return  null;
        }
        //验证密码,使用AuthenticationInfo实现类SimpleAuthenticationInfo,shiro会自动验证密码
        return new SimpleAuthenticationInfo("",user.getPwd(),"");
    }

修改当前Realm的CredentialsMatcher属性可以设置加密算法

用户授权操作

在配置类中给资源设置权限

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

设置没有权限时执行逻辑

//配置未授权页面
 shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");

此时用户登录后没有访问/user/add的权限

给用户授权,编写Realm

 //执行授权逻辑
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行了授权逻辑");
        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
        //添加资源的授权字符串
        info.addStringPermission("user:add");
        return info;
    }

实际开发中可以在数据库增加一个字段,来获取不同用户的权限

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
           System.out.println("执行了=>授权逻辑PrincipalCollection");
           //给资源进行授权
           SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
           //添加资源的授权字符串
           //info.addStringPermission("user:add");
           Subject subject = SecurityUtils.getSubject(); //获得当前对象
           User currentUser = (User) subject.getPrincipal(); //拿到User对象
           info.addStringPermission(currentUser.getPerms()); //设置权限
           return info;
}

 

posted @ 2021-05-12 13:24  我还有头发  阅读(483)  评论(0编辑  收藏  举报