Shiro认证授权

发布于 2021-04-17  4.93k 次阅读


一,shiro的基本概念

shiro是主流的权限管理框架:

什么是权限管理:

            基本上涉及到用户参与的系统都需要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户系统访问的控制,按照安全规则和安全cenr控制用户可以访问被授权的功能
权限管理包括用户认证和授权两部分,简称认证授权,对于需要访问控制资源的用户首先经过身份认证,认证通过之后在进行资源的访问授权认证
shiro是apache下的一个开源权限框架

subject:主体

Security Manage:安全管理

Authenticator:认证

Authorizer:授权

Session Manager:会话管理

SessionDAO

Cache Manager

Realma:获取会话和授权的数据

详解:

  • Subject:主体,可以看到主体可以是任何可以与应用交互的 “用户”;
  • SecurityManager:相当于 SpringMVC 中的 DispatcherServlet 或者 Struts2 中的 FilterDispatcher;是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。
  • Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
  • Authrizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
  • Realm:可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等等;由用户提供;注意:Shiro 不知道你的用户 / 权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的 Realm;
  • SessionManager:如果写过 Servlet 就应该知道 Session 的概念,Session 呢需要有人去管理它的生命周期,这个组件就是 SessionManager;而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境、EJB 等环境;所以呢,Shiro 就抽象了一个自己的 Session 来管理主体与应用之间交互的数据;这样的话,比如我们在 Web 环境用,刚开始是一台 Web 服务器;接着又上了台 EJB 服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到 Memcached 服务器);
  • SessionDAO:DAO 大家都用过,数据访问对象,用于会话的 CRUD,比如我们想把 Session 保存到数据库,那么可以实现自己的 SessionDAO,通过如 JDBC 写到数据库;比如想把 Session 放到 Memcached 中,可以实现自己的 Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 进行缓存,以提高性能;
  • CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能
  • Cryptography:密码模块,Shiro 提供了一些常见的加密组件用于如密码加密 / 解密的。

二,shiro的权限认证

一,shiro的maven依赖

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

二,shiro的认证

shiro的关键对象:
  1. Subject(主体):访问系统的用户,主体可以是用户,程序等,进行认证的都称为主体
  2. Principal:身份信息,主体进行身份认证的标识,标识必须具有唯一性,一个主体有多个身份,但必须有一个主身份
  3. credential:凭证信息,只有主体自己知道的安全信息,如:密码,证书

常见异常UnknownAccountException用户名不存在 IncorrectCredentialsException密码不存在

public class testAuthenticator {

public static void main(String[] args) {

//1,创建安全管理器

DefaultSecurityManager securityManager = new DefaultSecurityManager();

//2,给安全管理器设置realm(获取安全实体数据源,假如没有指定义数据源shiro.ini是默认读取的数据源)

securityManager.setRealm(new IniRealm("classpath:shiro.ini"));

//3,SecurityUtils 给全局的安全工具类设置安全管理器

SecurityUtils.setSecurityManager(securityManager);

//4,提供工具类获取subject关键对象

Subject subject = SecurityUtils.getSubject();

//5,创建令牌

UsernamePasswordToken token = new UsernamePasswordToken("wql","12s3");

try {

System.out.println(subject.isAuthenticated());

subject.login(token);

System.out.println(subject.isAuthenticated());

//常见异常UnknownAccountException用户名不存在 IncorrectCredentialsException密码不存在

}catch (UnknownAccountException a){

System.out.println("用户名不存在!!");

}catch (IncorrectCredentialsException e){

System.out.println("密码不存在");

}
二,自定义realm实现认证
自定义realm需要继承AuthorizingRealm类来实现

realm是实体数据源,默认是shiro.ini文件获取认证源数据,我们需要把realm更改成自定义的源比如从数据库中获取

主类:
//自定义Realm的实现

public class defindAuthenticator {

    public static void main(String[] args) {

        //创建securityManager

        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();

        //设置自定义realm

        defaultSecurityManager.setRealm(new authenticat());

        //创建工具类

        SecurityUtils.setSecurityManager(defaultSecurityManager);

        //subject

       Subject subject = SecurityUtils.getSubject();

       UsernamePasswordToken token = new UsernamePasswordToken("wql","123");

       try {

           subject.login(token);

       }catch (UnknownAccountException e){

           System.out.println("用户不存在!");

       }catch (IncorrectCredentialsException e){

           System.out.println("密码不存在!");

       }

    }
shiro默认realm的源码:
在默认realm情况下数据的认证在SimpleAccountRealm(在shiro.ini数据源)默认数据的认证的底层源码
//以默认数据的认证的底层源码

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

UsernamePasswordToken upToken = (UsernamePasswordToken)token;//强转令牌

SimpleAccount account = this.getUser(upToken.getUsername());//获取用户名判断

if (account != null) {

if (account.isLocked()) {

throw new LockedAccountException("Account [" + account + "] is locked.");
}

if (account.isCredentialsExpired()) {

String msg = "The credentials for account [" + account + "] are expired";

throw new ExpiredCredentialsException(msg);

}}

return account;
}

//数据的授权

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

String username = this.getUsername(principals);

this.USERS_LOCK.readLock().lock();

AuthorizationInfo var3;

try {

var3 = (AuthorizationInfo)this.users.get(username);

} finally {

this.USERS_LOCK.readLock().unlock();

}
return var3;
}
自定义Realm实现SimpleAccountRealm的父接口AuthorizingRealm:重写doGetAuthorizationInfo和doGetAuthenticationInfo
public class authenticat extends AuthorizingRealm {
    //这个方法做授权
    @Override

        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            return null;

        }

        @Override

        //这个方法只做数据名的认证,密码在底层实现,只需要提交SimpleAuthenticationInfo带上密码就行

        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

            //获取用户名

            String token = (String) authenticationToken.getPrincipal();

            //根据身份信息使用jdbc mybatis查询数据库对比

            if("wql".equals(token)){

                SimpleAuthenticationInfo simpleAuthenticationInfo=  new SimpleAuthenticationInfo("wql","123",this.getName());//其实这里应该是数据库验证

                return simpleAuthenticationInfo;
            }
            return null;
    }}

三,shiro的权限授权

一,授权的概念

授权:

       授权即访问控制,控制谁能访问资源谁不能访问资源,主体进行身份认证后,需要分配权限才能访问系统资源,对于某些资源没有权限是无法访问的。

授权中的关键对象:

主体:主体需要访问系统中的资源
 
资源:如菜单,密码修改,页面,类方法,table信息都属于资源
 
授权/许可:规定主体对资源的操作许可
 
授权流程:

授权方式:

  • RBAC基于角色的访问控制:以角色为中心的访问控制
  • RBAC基于资源的访问控制:以资源为中心的访问控制

权限字符串:

权限字符串的规则是:资源标识符:操作:资源实例标识符
 对那个资源的那个实例具有什么操作 :是分隔符,权限字符串也可以使用通配符
例子:
用户创建权限:user :create(不加实例默认为*) 或者user:create:*
用户可以修改实例wql的权限:user:updata:wql

二, shiro授权有多种实现方式

1,编程式:
Subject subject = SecurityUtil.getSubject();
if(subject.hasRole("admin")){
//有权限
}else{
//无权限
}
2,注解式
@RequiresRoles("admin")
public void hello(){
}
3,标签式
//在jsp/gsp页面通过相应的标签来完成
<shiro:hasRole name="admin">
<!--有权限-->
<shiro:hasRole>
注意:Thymeleaf中使用shiro需要额外集成

三,MD5和Salt的验证

一,MD5和Salt(概念)

MD5算法:加密后的值不可逆
作用:加密或者签名
特点:
  1. 算法不可逆,通过明文加密获取密文,但不能通过密文获取明文
  2. 如果内容相同无论执行多少次MD5生成的结果始终是一样的
其他用途对文件进行校验,判断文件是否一致
生成的结果:始终是一个16进制32位长度的字符串
注意网上的MD5解密工具,都是通过穷举算法来判断的,就是自己先把数字进行加密,把加密后的值保存到数据库中,你输入的值加密成密文和数据库中密文做判断,一致就返回数据库中密文对应的明文,看起来是进行了解密操作,其实不是
 
MD5和Salt的实际应用:
                         实际应用是将盐和散列后的值存储在数据库中,自动realm从数据库取出盐和加密后的值由shiro把获取到的用户输入密码进行加密,比较加密结果进行验证,因为MD5是不可逆的使用不能把MD5的值解密验证,只能比较加密后的值
MD5使用过程:MD5的使用必须结合上用户的注册,把用户输入的明文通过加密成密文保存进数据库

Salt(盐):Salt的出现是为了增加MD5的安全性的,Salt就是在明文后面增加一窜随机字符在进行MD5加密,
Salt的增加带来的问题:
                              因为盐是通过字符随机产生的,那么你输入密码直接MD5加密从数据库中判断是判断不出来的,因为加了随机字符串,那么你输入密码时总不可能将随机字符也写进去,程序也不可能能判断出生成的随机字符串,所有只能将盐产生的随机字符保存到数据库中,判断时一起拼接判断

一,MD5和Salt验证(操作)

一,在shiro如何使用MD5算法:

public static void main(String[] args) {

/*

Md5Hash在MD5的基础上又进行了散列处理

Md5Hash hash = new Md5Hash();这种处理方式不正确

hash.setBytes("123456".getBytes());

String a = hash.toHex();

System.out.println(a);

*/

//MD5标准用法 MD5是单独的类

Md5Hash md5Hash = new Md5Hash("123456");

System.out.println(md5Hash.toHex());

//使用MD5 + Salt处理

Md5Hash hash= new Md5Hash("123456","qwdasd");

System.out.println(hash.toHex());

//MD5 +Salt + hash散列

Md5Hash a=new Md5Hash("123456","asd",123);//这里散列123次

System.out.println(a.toHex());

}

二,在自定义的realm中加入MD5 + Salt + Hash

自定义Reaml:
public class authenticMD5 extends AuthorizingRealm {
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

String a=(String) authenticationToken.getPrincipal();

if(a.equals(a)){

return new SimpleAuthenticationInfo("wql",

"2c064f94210cdde60d828fd85a0671a3",

ByteSource.Util.bytes("123456"),//加Salt操作,在判断时会先加上salt后加密

this.getName());

}

return null;

}
main:
public static void main(String[] args) {

//1,创建安全管理器

DefaultSecurityManager securityManager = new DefaultSecurityManager();

//创建自定义Realm实例

authenticMD5 authenticMD5= new authenticMD5();

//创建Hash算法匹配器

HashedCredentialsMatcher hashedCredentialsMatcher= new HashedCredentialsMatcher();

//设置匹配器中的算法类型

hashedCredentialsMatcher.setHashAlgorithmName("md5");

//设置散列的次数

hashedCredentialsMatcher.setHashIterations(123);//默认是一次

//将算法匹配器加入到Realm中

authenticMD5.setCredentialsMatcher(hashedCredentialsMatcher);

//2,给安全管理器设置realm(获取安全实体数据源)

securityManager.setRealm(authenticMD5);

//3,SecurityUtils 给全局的安全工具类设置安全管理器

SecurityUtils.setSecurityManager(securityManager);

//4,提供工具类获取subject关键对象

Subject subject = SecurityUtils.getSubject();

//5,创建令牌

UsernamePasswordToken token = new UsernamePasswordToken("wql","123456");

try {

subject.login(token);

//常见异常UnknownAccountException用户名不存在 IncorrectCredentialsException密码不存在

}catch (UnknownAccountException a){

System.out.println("用户名不存在!!");

}catch (IncorrectCredentialsException e){

System.out.println("密码不存在");
}

}

四,shiro与Springboot的整合

一, 引入springboot-shiro依赖

<!--Springboot的shiro-->  
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-starter</artifactId>
    <version>1.5.3</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <scope>provided</scope>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.2</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.49</version>
</dependency>

二,整合思路

三,整合操作

第一步:配置SpringBoot的shirofilter拦截器
@Configuration

public class shiro_filter {

//1,创建shirofilter,负责拦截所有请求

@Bean

public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){

ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

//给拦截器工厂设置安全管理器

shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

//配置受限资源和公共资源

Map<String,String> map =new HashMap<String,String>();

map.put("/index","authc");

///认证

// 界面路径

shiroFilterFactoryBean.setLoginUrl("/login");

shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

return shiroFilterFactoryBean;

}

//2,创建安全管理器

@Bean

public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){

DefaultWebSecurityManager defaultWebSecurityManager=new DefaultWebSecurityManager();

//给安全管理器设置realm

defaultWebSecurityManager.setRealm(realm);

return defaultWebSecurityManager;

}

//3,创建自定义Realm

@Bean

public Realm getRealm(){

realm_ realm_a=new realm_();

HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();

hashedCredentialsMatcher.setHashAlgorithmName("md5");

realm_a.setCredentialsMatcher(hashedCredentialsMatcher);

return realm_a;

}}
Realm类:
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}

@Override

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

appllicationContextUtil app = new appllicationContextUtil();

//提供appicationUtil工具类获取service类

wql_service wqlService =(wql_service)app.getbean("wql_service");

String auth = (String) authenticationToken.getPrincipal();

wql w = wqlService.sele(auth);

System.out.println("+++++++++++++++++++++++++++++++");

if(!ObjectUtils.isEmpty(w)){

SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(w.getName(),w.getRandom(),this.getName());

System.out.println(w.getName()+"-----"+w.getRandom());

return simpleAuthenticationInfo;

}

return null;
}}
appication工具类:
@Component

public class appllicationContextUtil implements ApplicationContextAware {

static ApplicationContext applicationContexts;

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

applicationContexts=applicationContext;

}

public static Object getbean(String beanname){

Object s=applicationContexts.getBean(beanname);

return s;

}}
service层:
@Service("wql_service")
public class wql_service {

@Autowired

wql_mapper wqlMapper;

public wql sele(String name){

wql s=wqlMapper.select_pass(name);

return s;

}

public boolean insert_fq(wql s){

Md5Hash md5Hash = new Md5Hash(s.getRandom());

s.setSalt("asd4a");

s.setRandom(md5Hash.toHex());

boolean sd=wqlMapper.insert_pass(s);

return sd;
}}

四,shiroFilter的过滤器

配置缩写
对应的过滤器
概念
anon
AnonymousFilter
指定url可以匿名访问,就是公共资源,不需要登录认证
authc
FormAuthenticationFilter
基于表单的拦截器;如“/**=authc”,如果没有登录会跳到相应的登录页面登录;主要属性:usernameParam:表单提交的用户名参数名( username); passwordParam:表单提交的密码参数名(password); rememberMeParam:表单提交的密码参数名(rememberMe); loginUrl:登录页面地址(/login.jsp);successUrl:登录成功后的默认重定向地址; failureKeyAttribute:登录失败后错误信息存储key(shiroLoginFailure)
perms
permissionsAuthorizationFilter
需要指定权限才能访问
port
portFilter
需要指定端口才能访问
roles
HttpMethodPermissionFilter
需要指定角色才能访问
logout
PermissionAuthorizationFilter
登出过滤器,配置指定url就可以实现退出功能

 

五,shiro默认缓存Ehcache的使用

缓存(cache)的作用:计算机内存的一段数据用来减轻DB的访问数据库的压力,从而提供系统查询的效率
注意:在增删改的情况下,需要直接调用数据库的IO,还有同步数据到缓存,这个时候缓存是没有效果的

使用Shiro中的默认缓存:
导入依赖:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.5.3</version>
</dependency>

配置Realm开启缓存(在shiro拦截器中realm的方法中设置):、

@Bean

public Realm getRealm(){

realm_ realm_a=new realm_();

HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();

hashedCredentialsMatcher.setHashAlgorithmName("md5");

realm_a.setCredentialsMatcher(hashedCredentialsMatcher);

//开启Realm的缓存(使用EhCatche)

realm_a.setCacheManager(new EhCacheManager());

//开启全局的缓存

realm_a.setCachingEnabled(true);

//开启认证的缓存

realm_a.setAuthenticationCachingEnabled(true);

//开启授权的缓存

realm_a.setAuthorizationCachingEnabled(true);

//设置默认的名字

realm_a.setAuthenticationCacheName("AuthenticationionCache");

realm_a.setAuthorizationCacheName("AuthorizationCache");

return realm_a;
}
shiro整合ehcache缓存是一种本地缓存(应用内的缓存),程序的中断和断电都会消失,
大部分我们都是整合第三方缓存比如redis,应用的停止和中断都跟应用没有关系