aisell_shiro登录权限

一、Shiro简介

(1)shiro概念
是一个强大且易用的java安全框架
(2)shiro应用程序的四大基石
身份验证(Authentication)、授权(Authorization)、密码学(Cryptography)、会话管理(Session Management)

  • 身份验证(Authentication):简称为登录
  • 授权(Authorization):访问控制的过程,什么角色访问什么权限
  • 密码学(Cryptography):用加密算法保持数据安全
  • 会话管理(Session Management):管理用户特定的会话,即使在非 Web 或 EJB 应用程序

(3)shiro的核心类是什么
SecurityManager
(4)Spring security和Apache Shiro两款安全框架的区别
Spring security: 重量级安全框架,功能更加强大,细粒度
Apache Shiro:轻量级安全框架,功能够用,粗粒度,可以通过自己控制粒度。
(5)Shiro如何实现盐值认证?
保存数据使用盐值加密保存
认证时使用盐值加密认证
两者必须加密次数和盐值一样
(6)shiro的架构:宏观上看
aisell_shiro登录权限_第1张图片
aisell_shiro登录权限_第2张图片
(7)shiro的架构:微观上看
aisell_shiro登录权限_第3张图片
aisell_shiro登录权限_第4张图片
(8)shiro的测试代码
准备好了shiro.ini文件

# -----------------------------------------------------------------------------
# 用户名:root  密码是:123456 它的角色是:admin
# 用户名:guest  密码是:guest 它的角色是:it
# -----------------------------------------------------------------------------
[users]
root = 123456, admin,it
guest = guest, guest

# -----------------------------------------------------------------------------
# admin拥有所有权限
# it拥有操作员工的所的权限
# guest拥有员工的所有权限
# -----------------------------------------------------------------------------
[roles]
admin = *
it = employee:*
guest = employee:save

测试类代码

package cn.itsource.shiro;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Test;
/*
* 用户的姓名、密码、角色和权限来自于shiro.ini文件中
* */
public class HelloShiro {

    @Test
    public void myTest() throws  Exception{
        //1.获取到最重要的securityManager核心对象
        //(1).读取shiro.ini配置文件,并拿到工厂
        Factory factory= new IniSecurityManagerFactory("classpath:shiro.ini");
        //(2).从工厂中获取到securityManager对象
        SecurityManager securityManager = factory.getInstance();

        //2.将securityManager对象设置到上下文中。目的:将对象放到一个地方,以后所有位置都可以调用
        SecurityUtils.setSecurityManager(securityManager);

        //3.获取到当前用户(如果没有登录就是游客)---Subject:用户
        Subject currentUser = SecurityUtils.getSubject();
        //currentUser.isAuthenticated():boolean类型,判断是否登录
        System.out.println("是否成功登录:"+currentUser.isAuthenticated());//false

        //4.如果用户没有登录,就让用户登录
        if(!currentUser.isAuthenticated()){//!false为true,执行
            //登录过程会出现用户名和密码错误等异常,try/catcah一下
            try {
                //(1).登录之前,先准备令牌
                UsernamePasswordToken token = new UsernamePasswordToken("root","123456");
                //(2).根据令牌登录
                currentUser.login(token);
            } catch (UnknownAccountException e) {
                e.printStackTrace();
                System.out.println("用户名错误!!");
            }catch (IncorrectCredentialsException e){
                e.printStackTrace();
                System.out.println("密码错误!!");
            }catch (AuthenticationException e){
                e.printStackTrace();
                System.out.println("未知错误!!!");
            }
        }
        //角色判断。--判断用户是否有某些角色
        System.out.println("我是一个admin角色的人:"+currentUser.hasRole("admin"));//true
        System.out.println("我是一个guest角色的人:"+currentUser.hasRole("guest"));//false

        //权限判断。--判断用户是否有某些权限
        System.out.println("我有employee:save的权限:"+currentUser.isPermitted("employee:save"));//true
        System.out.println("我有employee:delete的权限:"+currentUser.isPermitted("employee:delete"));//true
        System.out.println("我有employee:update的权限:"+currentUser.isPermitted("employee:update"));//true
        System.out.println("我有department:update的权限:"+currentUser.isPermitted("department:update"));//true

        //上面拿到令牌登录后
        System.out.println("是否成功登录:"+currentUser.isAuthenticated());//true

        //退出系统
        currentUser.logout();
        System.out.println("是否成功登录:"+currentUser.isAuthenticated());//false
    }
}

(9)自定义realm类测试
自定义的realm类—MyRealm

package cn.itsource.shiro;

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import java.util.HashSet;
import java.util.Set;
/*
* 自定义一个Realm。完成自己想要的功能
*   自定义类MyRealm继承抽象类AuthorizingRealm。
*   授权验证方法doGetAuthorizationInfo和登录验证的方法doGetAuthenticationInfo ,在其中写自己的功能
*   注意各自对应的:SimpleAuthorizationInfo 和 SimpleAuthenticationInfo
* */
public class MyRealm extends AuthorizingRealm {

//授权验证的方法部分
    /*
    *   注意:1.一定要是登录成功之后才会进入到此方法中来
    *       2.拿到用户名(根据用户名去数据库中拿对应的角色与权限)
    * */
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //1.拿到登录成功后的用户名(当前登录用户的主体)。---getPrimary(主要的)Principal(主要的)
            //将object类型强转为string类型
        String  primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
        //2.根据用户名拿到对应的角色和权限(此次也是模拟的)
        Set roles = getRoles(primaryPrincipal);
        Set perms = getPerms(primaryPrincipal);
        //3.返回对应的对象
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setRoles(roles);
        simpleAuthorizationInfo.setStringPermissions(perms);

        return simpleAuthorizationInfo;
    }

  //模拟后台数据库的角色和权限
        //模拟根据用户名拿到对应的角色
    private Set getRoles(String username){
        Set roles = new HashSet();
        roles.add("admin");
        roles.add("it");
        return roles;
    }
        //模拟根据用户名拿到对应的权限
    private Set getPerms(String username){
        Set perms = new HashSet();
        perms.add("employee:save");
        perms.add("employee:delete");
        perms.add("employee:*");
//        perms.add("*");
        return perms;
    }

//登录验证的方法部分
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
       //1.拿到用于登录的令牌。将authenticationToken强转为UsernamePasswordToken令牌---令牌token(有用户名和密码)
        UsernamePasswordToken token=(UsernamePasswordToken)authenticationToken;
       //2.根据令牌拿到用户名
        String username = token.getUsername();
        //3.根据用户名到数据库查用户的密码(此处是模拟的)
        String password = getByName(username);//就是模拟的数据库中的密码
        //若果查询的用户密码为空
        if(password==null){
            //用户名不存在,shior会自动报UnknownAccountException异常
            return null;
        }
        //4.将从数据库中查询到的用户名和密码传进去,shiro会判断密码
            //传的是数据库密码:它要把这个密码和令牌中的密码做对比,如果对应不上,就会报:IncorrectCredentialsException异常
            //记住SimpleAuthenticationInfo()方法:需要的参数:用户名、密码、随意名字(保证以后不重复即可)
        /**
         * 参数含义:
         *      Object principal :主体(用户名)
         *      Object hashedCredentials :密码
         *      ByteSource credentialsSalt :盐值
         *      String realmName :realm名称
         */
        //准备盐值--给密码加盐值
        ByteSource salt = ByteSource.Util.bytes("itsource");
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, password,salt, "xx");
        return authenticationInfo;
    }

    //Hash(散列):算法
    /**
     * 123 : 202cb962ac59075b964b07152d234b70
     * 123 + 10次:5371007260db2b98e3f7402395c45f28
     * 123+salt: 3eb58f2ed64a0508d792f44ed010c862
     * 123+salt+10次:d5a3fedf6c59c2ecbe7f7a6c1a22da37
     */
    //根据当前用户名查询到密码(模拟后台数据库)
    private String getByName(String username){
        //如果前台传过来的用户名和后台根据令牌拿到的用户名一致,就返回该用户的密码
        if("admin".equals(username)){
            return "d5a3fedf6c59c2ecbe7f7a6c1a22da37";
        }else if("guest".equals(username)){
            return "456";
        }
        //否则就返回空
        return null;
    }
}

测试类Test代码:
如果密码加盐加密。则需要在test测试类中拿到凭证适配器,然后设置加密方式和加密次数,而加盐只能在realm类中设置

package cn.itsource.shiro;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.Test;

public class MyRealmTest {

    @Test
    public void myTest() throws  Exception{
        //1.获取到重要的对象。securityManager
         //1.1 创建自己自定义的MyRealm
        MyRealm myRealm=new MyRealm();


    //如果密码是加密加盐值了
        //告诉Shiro(Realm)判断密码使用MD5加密的方式判断
        //HashedCredentials(凭证)Matcher(匹配器)  拿到凭证适配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        //设置加密的方式
        credentialsMatcher.setHashAlgorithmName("MD5");
        //设置加密次数
        credentialsMatcher.setHashIterations(10);
        //将凭证适配器交给realm,然后它就知道怎么做了
        myRealm.setCredentialsMatcher(credentialsMatcher);


        //1.2 创建一个securityManager对象
        DefaultSecurityManager securityManager=new DefaultSecurityManager();
         //1.3 将自定义的realm和securityManager对象产生关系
        securityManager.setRealm(myRealm);

        //2.将securityManager对象放到上下文中
        SecurityUtils.setSecurityManager(securityManager);

        //3.拿到当前的用户(游客)
        Subject subject = SecurityUtils.getSubject();
        System.out.println("是否登录:"+subject.isAuthenticated());

        //4.如果没有登录,给个令牌让他登录
        if(!subject.isAuthenticated()){
            try {
                //4.1准备一个令牌
                UsernamePasswordToken token = new UsernamePasswordToken("admin","123");
                //4.2 登录
                subject.login(token);
            } catch (UnknownAccountException e) {
                e.printStackTrace();
                System.out.println("账号错误");
            } catch (IncorrectCredentialsException e) {
                e.printStackTrace();
                System.out.println("密码错误");
            } catch (AuthenticationException e) {
                e.printStackTrace();
                System.out.println("未知错误!");
            }
        }
        System.out.println("是否登录:"+subject.isAuthenticated());

        //角色判断。--判断用户是否有某些角色
        System.out.println("我是一个admin嘛:"+subject.hasRole("admin"));
        System.out.println("我是一个it嘛:"+subject.hasRole("it"));
        System.out.println("我是一个guest嘛:"+subject.hasRole("guest"));

        //权限判断。--判断用户是否有某些权限
        System.out.println("我有employee:save的权限嘛:"+subject.isPermitted("employee:save"));
        System.out.println("我有employee:delete的权限嘛:"+subject.isPermitted("employee:delete"));
        System.out.println("我有dept:save的权限嘛:"+subject.isPermitted("dept:save"));
    }


    //Hash(散列):算法
    /**
     * 123 : 202cb962ac59075b964b07152d234b70
     * 123 + 10次:5371007260db2b98e3f7402395c45f28
     * 123+salt: 3eb58f2ed64a0508d792f44ed010c862
     * 123+salt+10次:d5a3fedf6c59c2ecbe7f7a6c1a22da37
     */
    @Test
    public void testHash() throws Exception{
        /**
         * String algorithmName:加密算法
         * Object source:原始密码
         * Object salt:盐值
         * hashIterations:加密次数
         */
        SimpleHash hash = new SimpleHash("MD5","123",null,10);
        System.out.println(hash);
    }
}

二、shiro集成spring

项目基本上都是通过Spring来管理bean,要想使用shiro,就必须将shiro集成到Spring中。而集成Spring的核心就是将框架中的核心类:SecurityManager 、Subject  、 Realm 交给spring创建

总的来说;就是将核心对象SecurityManager 由spring创建、将自定义的realm也叫给spring创建。
集成步骤:
(1)导入相应的jar包


 
    org.apache.shiro
    shiro-all
    1.4.0
    pom

  
  
    org.apache.shiro
    shiro-spring
    1.4.0
  

(2)在web.xml中配置shiro过滤器


  
  
    shiroFilter
    org.springframework.web.filter.DelegatingFilterProxy
    
      targetFilterLifecycle
      true
    
  
  
    shiroFilter
    /*
  

(3)配置spring和shiro集成的xml文件—applicationcontext-shiro.xml文件




    
    
        
        
    

    
    
        
        
            
            
                
                
                
                
                
            
        
    

    
    
    
    
        
    

    
    
        
        
        
        
        
        
        
        
        

        
        
    

    
    
    
    


(4)在applicationcontext.xml文件中读取shiro.xml文件


    

(5)自定义一个realm类

package cn.itsource.aisell.shiro;

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import java.util.HashSet;
import java.util.Set;
/*
* 自定义realm:从数据库或者其他文件拿值(用户信息,权限.....)
*   自定义的realm必须继承抽象类AuthorizingRealm并实现其抽象方法
*
*   此处模拟数据库中的密码又加盐又加值又加密。而此处在登录验证方法中只加了盐。
*   那么剩下的加值和加密则在applicationcontext-shiro.xml文件中去配置即可
* */
public class AiSellRealm extends AuthorizingRealm{

    //名字随意取,只是realm的一个名字而已
    @Override
    public String getName() {
        return "AiSellRealm";
    }


    //授权验证方法(必须在下面的登录验证方法成功之后才会来执行这里的代码)
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //1.拿到用户名--将object类型强转为string类型
        String username =(String) principalCollection.getPrimaryPrincipal();
        //2.根据用户名获取到用户的角色和权限
        Set roles = getRoles(username);
        Set perms = getPerms(username);
        //3.给AuthorizationInfo对象设置角色和权限,并返回   使用SimpleAuthorizationInfo类
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.setRoles(roles);
        authorizationInfo.setStringPermissions(perms);

        return authorizationInfo;
    }

    //(模拟数据库)根据用户名拿到对应的角色与权限
    private Set getRoles(String username){
        Set roles = new HashSet();
        roles.add("admin");
        roles.add("it");
        return roles;
    }
    private Set getPerms(String username){
        Set perms = new HashSet();
        perms.add("employee:index");
        perms.add("employee:*");
//        perms.add("*");
        return perms;
    }


    //登录验证方法
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //1.拿到令牌。将authenticationToken令牌强转为usernamepasswordToken类型的令牌
        UsernamePasswordToken token=(UsernamePasswordToken)authenticationToken;
        //2.根据令牌拿到用户名
        String username = token.getUsername();
        //3.再根据用户名去数据库(模拟的数据库)拿到对应的密码
        String password=getByName(username);
        //4.判断如果用户名没有密码
        if(password==null){
            //返回空,代表登录失败,shiro会自动报UnknownAccountException异常
            return null;
        }

        //5.准备好盐值
        ByteSource salt = ByteSource.Util.bytes("itsource");
        //6.返回AuthenticationInfo对象。通过SimpleAuthenticationInfo类,参数(用户名、密码、盐值、随意的realm名字)
            //用户名与密码放进去-注:它会自动的比较获取的密码与你传过来的密码
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, password, salt, getName());
        return authenticationInfo;
    }

    //根据当前登录用户拿到密码(模拟数据库)--456经过加盐遍历10此得到的加密密码42a02ed97752caf987d13d1a5cb53847
    private String getByName(String username){
        if("admin".equals(username)){
            return "42a02ed97752caf987d13d1a5cb53847";
        }else if("it".equals(username)){
            return "42a02ed97752caf987d13d1a5cb53847";
        }
        return null;
    }
}

(6)为了解决applicationcontext-shiro.xml配置文件中权限路径写死问题,单独写个java类

package cn.itsource.aisell.shiro;

import java.util.LinkedHashMap;
import java.util.Map;
/*
* 解决applicationcontext-shiro.xml配置文件中权限路径写死的问题
* */
public class FilterChainDefinitionMapBuilder {

    /*
    * 在原来applicationcontext-shiro.xml文件中配置的写死了的权限路径
    *   /jsp/login.jsp = anon
     *    /login = anon
     *   /employee/index = perms[employee:index]
     *    /dept/index = perms[dept:index]
     *    /** = authc--有顺序要求,这个一定要放在最后面
	 *
     *   key/value的结构,必须要有顺序。所以将权限装进LinkedHashMap里面
     *   LinkedHashMap:代表有顺序的
    * */
    public Map createFilterChainDefinitionMap(){
		//创建一个有顺序的map集合出来装权限路径
         Map filterChainDefinitionMap = new LinkedHashMap<>();

         //设置不需要权限也能访问的路径
        filterChainDefinitionMap.put("/jsp/login.jsp", "anon");
        filterChainDefinitionMap.put("/login", "anon");

        //设置需要权限访问的路径
//        filterChainDefinitionMap.put("/employee/index","perms[employee:index]");
        filterChainDefinitionMap.put("/dept/index","perms[dept:index]");

        //设置需要登录才能访问---这个一定要放在最后面
        filterChainDefinitionMap.put("/**","authc");

        return filterChainDefinitionMap;
    }
}

(7)登录的controller层

package cn.itsource.aisell.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class LoginController {

    @RequestMapping("/login")
    public String login(String username,String password){
        //当前台登录页面输入数据时会提交到后台来
//        System.out.println(username+":"+password);

        /*
        * 已经叫给spring去管理shiro了,所以此处就不用再创建核心对象securityManager了,
        * 也不需要拿到凭证适配器、设置加密方式、设置加密次数了,同样也不用拿自定义的realm类和
        * 核心对象securityManager发生关系,也不用将核心对象securityManager放到上下文中去
        * */
        //1.直接拿到当前的用户
        Subject currentName = SecurityUtils.getSubject();
        //2.如果当前用户没有登录,就给他个令牌叫他登录
            //currentName.isAuthenticated():布尔类型。false表示没有登录,反之则登录
        if(!currentName.isAuthenticated()){
            try {
                //3.给个令牌,将前台传过来的参数传进去
                UsernamePasswordToken token = new UsernamePasswordToken(username, password);
                //4.用户登录
                currentName.login(token);
            } catch (UnknownAccountException e) {
                e.printStackTrace();
                System.out.println("用户名错误");
            }catch (IncorrectCredentialsException e) {
                e.printStackTrace();
                System.out.println("密码错误");
            }catch (AuthenticationException e) {
                e.printStackTrace();
                System.out.println("未知错误");
            }
        }
        //成功后直接跳转到主页面。用redirect跳转,就不受mvc视图解析配置(自动生成路径)的影响了
        return "redirect:/jsp/main.jsp";
    }

    //用户名登录注销
    @RequestMapping("/logout")
    public String logout() {
        //直接拿到用户名
        Subject subject = SecurityUtils.getSubject();
        //注销登录
        subject.logout();
        //注销后直接跳转到登录页面
        return "redirect:/s/login.jsp";
    }

}

(8)相关的简单的jsp页面

login.jsp登录页面

<%--
  Created by IntelliJ IDEA.
  User: Administrator
  Date: 2019/3/19
  Time: 22:07
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>


    Title


用户名
密码:

main.jsp主页面

<%--
  Created by IntelliJ IDEA.
  User: Administrator
  Date: 2019/3/19
  Time: 22:08
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>


    Title


    主页面 注销



unauthorized.jsp没有权限的页面

<%--
  Created by IntelliJ IDEA.
  User: Administrator
  Date: 2019/3/19
  Time: 22:08
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>


    Title


    没有权限



你可能感兴趣的:(shiro)