Java框架(十八)之shiro安全(权限)框架

一、简介

1.概念

• Apache Shiro 是 Java 的一个安全(权限)框架。 springsecurity • Shiro
可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。 • Shiro
可以完成:认证(登录判断)、授权(把用户的权限授予用户)、加密、会话管理、与Web 集成、缓存 等。 •
下载:http://shiro.apache.org/

2.功能简介

在这里插入图片描述

3.核心介绍

Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用
户是否能进行什么操作,如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户
对某个资源是否具有某个权限;
Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有
信息都在会话中;会话可以是普通 JavaSE 环境,也可以是 Web 环境的;
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Support:Web 支持,可以非常容易的集成到Web 环境;
Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可
以提高效率;
Concurrency:Shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能
• 把权限自动传播过去;
Testing:提供测试支持;
Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登
录了

二、Shiro架构

1.外部架构

在这里插入图片描述

2.架构图介绍

Subject:应用代码直接交互的对象是 Subject,也就是说 Shiro 的对外
API 核心就是 Subject。Subject 代表了当前“用户”,与 Subject 的所有交互都会委托给 SecurityManager;

SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager 交互;且其管理着所有 Subject;可以看出它是 Shiro的核心,
它负责与 Shiro 的其他组件进行交互,它相当于 SpringMVC 中DispatcherServlet 的角色

Realm:Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;
也需要从 Realm 得到用户相应的角色/权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource

3.外部架构图

在这里插入图片描述

4.核心名词简介

Subject:任何可以与应用交互的“用户”;
SecurityManager :相当于SpringMVC 中的 DispatcherServlet;是 Shiro 的心脏;
所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进
行认证、授权、会话及缓存的管理。
Authenticator:负责 Subject 认证,是一个扩展点,可以自定义实现;可以使用认证
策略(Authentication Strategy),即什么情况下算用户认证通过了;
Authorizer:授权器、即访问控制器,用来决定主体是否有权限进行相应的操作;即控
制着用户能访问应用中的哪些功能;
Realm:可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体
的;可以是JDBC 实现,也可以是内存实现等等;由用户提供;所以一般在应用中都需要
实现自己的 Realm;
SessionManager:管理 Session 生命周期的组件;而 Shiro 并不仅仅可以用在 Web
环境,也可以用在如普通的 JavaSE 环境
CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据
基本上很少改变,放到缓存中后可以提高访问的性能
Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于如密码加密/解密。

初步认识(maven-java project)

pom.xml
<dependency>
  		<groupId>org.apache.shiro</groupId>
  		<artifactId>shiro-all</artifactId>
  		<version>1.4.0</version>
  		<type>pom</type>
  	</dependency>
  	  <dependency>
        	<groupId>org.slf4j</groupId>
        	<artifactId>slf4j-api</artifactId>
        	<version>1.7.12</version>
        </dependency>
        <dependency>
        	<groupId>org.slf4j</groupId>
        	<artifactId>slf4j-log4j12</artifactId>
        	<version>1.7.12</version>
        </dependency>
	<dependency>
        	<groupId>log4j</groupId>
        	<artifactId>log4j</artifactId>
        	<version>1.2.16</version>
      </dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc     和web项目整合-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>4.3.13.RELEASE</version>
</dependency>
Quickstart.java
package com.tf;
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.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) {
        //使用shiro.ini文件初始化工厂
        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("name", "xiaopang");
        String value = (String) session.getAttribute("name");
        if (value.equals("xiaopang")) {
            log.info("name"+value);
        }*/
        //当前用户认证?
      if (!currentUser.isAuthenticated()) {
            //将用户名和密码封装为一个UsernamePasswordToken对象
            UsernamePasswordToken token = new UsernamePasswordToken("admin", "111");
            //记住功能   web工程可以用到
            token.setRememberMe(true);
            try {
                currentUser.login(token);
            } catch (UnknownAccountException uae) {
                log.info("账户不存在 " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
                log.info("密码错误" + token.getPrincipal() );
            } catch (LockedAccountException lae) {
                log.info("账户被冻结 " + token.getPrincipal() );
            }
               catch (AuthenticationException ae) {
               log.info("未知异常!");
            }
        }
         log.info("User " + currentUser.getPrincipal() );
        //具有role?
        if (currentUser.hasRole("ceo")) {
            log.info("具有ceo角色");
        } else {
            log.info("不具有ceo角色");
        }
        if (currentUser.hasRole("admin")) {
            log.info("具有admin角色");
        } else {
            log.info("不具有admin角色");
        }
        //具有权限?
        if (currentUser.isPermitted("sys:user:list")) {
            log.info("具有sys:user:list");
        } else {
            log.info("不具有sys:user:list");
        }
        //退出功能 web工程用到
        currentUser.logout();
        System.exit(0);
    }
}
shiro.ini
# ----------------用户-------------------------------------------------------------
[users]
# 定义一个root的用户 密码secret 角色:admin
root = secret, admin
admin =111, admin
# 用户:lonestarr  密码:vespa  角色:goodguy和schwartz
lonestarr = vespa, goodguy, schwartz

# -----------------角色权限------------------------------------------------------------
[roles]
# admin用户具有所有权限
admin = *

#  goodguy 具有sys:user下的所有权限
goodguy = sys:user:*

#  schwartz 具有sys:user:delete的权限
schwartz = sys:user:delete

#  后期这个数据应该从数据库查找
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=TRACE

# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN

进一步学习

初步认识后,我们会发现用户信息如果定义在配置文件ini中,不是太理想,而应该定义到数据库中。
接下来我们可以先来模拟一下数据库的操作。需要自定义一个realm。
##自定义一个Realm类
① 编写Realm类继承AuthorizingRealm类

package com.tf.shiro02;

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 java.util.ArrayList;
import java.util.List;

/*
*  shiro涉及最少5张表
*  用户表    角色表    权限表
*    用户角色表    角色权限表
* */
public class MyRealm extends AuthorizingRealm {
   //授权
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
      System.out.println("授权===========");
        //把当前用户的角色和权限查询出来
        // 通过shiro提供的api授权处理即可
        // AuthorizationInfo 的一个子接口SimpleAuthorizationInfo
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
          //模拟数据库的数据
        List<String> roles = new ArrayList<String >();
        roles.add("admin");
        roles.add("ceo");
        roles.add("PD");
        List<String> permissions = new ArrayList<String>();
        permissions.add("admin");
        permissions.add("sys:user:add");
        permissions.add("sys:user:update");
          info.addRoles(roles);
          info.addStringPermissions(permissions);
        return info;
    }
  //认证 主要是用来 比对 用户名和密码
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
       System.out.println("认证===========");
       String username = (String)authenticationToken.getPrincipal();
       String password = new String((char[])authenticationToken.getCredentials());
        //调用service层
        //模拟service
        String uname= "admin";
        String pwd = "jbgsn";
        if(username.equals(username)){
            if(password.equals(pwd)){
                AuthenticationInfo info = new SimpleAuthenticationInfo(username,password,this.getName());
                return info;
            }else{
               throw new IncorrectCredentialsException("密码不存在!");
            }

        }else{
            throw  new UnknownError("账户不存在!");
        }
    }
}

②:shiro02.ini

#自定义Realm的全路径
customRealm=com.tf.shiro02.MyRealm
securityManager.realms=$customRealm

③: TestShiro02

package com.tf.shiro02;
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.subject.Subject;
import org.apache.shiro.util.Factory;
public class TestShiro02 {
    public static void main(String[] args){
     Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro02.ini");
     //import org.apache.shiro.mgt.SecurityManager;  不要导错包了
    SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        Subject currrentUser = SecurityUtils.getSubject();
        //用户认证?
        if(!currrentUser.isAuthenticated()){
            //用户名和密码以后可以从数据库里面获取
            UsernamePasswordToken token = new UsernamePasswordToken("admin","jbgsn");
           try{
               currrentUser.login(token);
           }catch (UnknownAccountException uae) {
               System.out.println("账户不存在 " + token.getPrincipal());
           } catch (IncorrectCredentialsException ice) {
               System.out.println("密码错误" + token.getCredentials() );
           } catch (LockedAccountException lae) {
              System.out.println("账户被冻结 " + token.getPrincipal() );
           }
           catch (AuthenticationException ae) {
               System.out.println("未知异常!");
           }
           //角色判断
            if(currrentUser.hasRole("ceo")){
              System.out.println("具有ceo角色");
            }else{
                System.out.println("不具有ceo角色");
            }
               //权限
            if(currrentUser.isPermitted("sys:user:add")){
               System.out.println("具有sys:user:add权限");
            }else{
               System.out.println("不具有sys:user:add权限");
            }
        }else{
            System.out.println("认证成功,可以登陆了");
        }

    }
}

结果:

授权=========== 具有ceo角色

授权=========== 具有sys:user:add权限
结果会发现调用了两次授权,正常情况其实调用一次就行了,这是因为没有用到缓存,后面和spring的整合可以加上这个缓存.

加密功能

shiro提供了加密功能(MD5Hash类)
package com.tf.shiro02;
import org.apache.shiro.crypto.hash.Md5Hash;
public class Md5 {
    public static void main(String[] args){
         Md5Hash md5Hash1 = new Md5Hash("jbgsn","admin");
        Md5Hash md5Hash2 = new Md5Hash("jbgsn","admin",1024);
        System.out.println(md5Hash1);
        System.out.println(md5Hash2);
    }
}

密码 :jbgsn 盐值:admin(尽量是唯一的,比如:当前用户名) 加密次数:1024
加密的结果是32位
结果:
027dd732a22adc14c5e1900b7087ade9
7a85bf432ebb5bf9f96f755ba4ad6450

shiro和SSM整合(web project)

参照下载的 shiro 源码中的samples\spring 配置web.xml 文件和 Spring 的配置文件

web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">
<!--加载spring的配置文件-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:app*.xml</param-value>
    </context-param>
<!--springMVC的配置文件-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

  <!--先拦截所有路径,再考虑放行-->
       <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

       <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
    </servlet>

    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

      <welcome-file-list>
        <welcome-file>login.jsp</welcome-file>
    </welcome-file-list>

</web-app>

• DelegatingFilterProxy 作用是自动到 Spring 容器查找名字为 shiroFilter(filter-name)的 bean 并把所有 Filter
的操作委托给它。

URL 匹配顺序

• URL 权限采取第一次匹配优先的方式,即从头开始
使用第一个匹配的 url 模式对应的拦截器链。
• 如:
– /bb/=filter1
– /bb/aa=filter2
– /=filter3
– 如果请求的url是“/bb/aa”,因为按照声明顺序进行匹
配,那么将使用 filter1 进行拦截。

applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <!-- 缓存 -->
    <property name="cacheManager" ref="cacheManager"/>
    <!--自定义realm-->
    <property name="realm" ref="MyRealm"/>
</bean>

        <!--缓存管理-->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
</bean>

        <!--自定义realm-->
<bean id="MyRealm" class="com.tf.shiro02.MyRealm">
</bean>

        <!--shiro注解在spring相关类中生效 -->
        <!--生命周期-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
      depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>


        <!--id :shiroFilter必须和web.xml中的DelegatingFilterProxy的servlet-name的id一致-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!--登录页面-->
<property name="loginUrl" value="/login.jsp"/>
<!--登录成功后的页面-->
<property name="successUrl" value="/success.jsp"/>
<!--没有权限的页面-->
<property name="unauthorizedUrl" value="/unauthorized.jsp"/>
<!--自定义设置-->
<!--anon: 匿名访问
    authc:认证后访问
    切记有先后顺序
-->
<property name="filterChainDefinitions">
    <value>
        /favicon.ico = anon
        /login = anon
        /logout = anon
        /css**=anon
        # allow WebStart to pull the jars for the swing app:
        /*.jar = anon
        # everything else requires authentication:
        /** = authc
    </value>
</property>
</bean>
</beans>
ehcache.xml
<ehcache>

    <diskStore path="java.io.tmpdir/shiro-spring-sample"/>

    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            />

    <cache name="shiro-activeSessionCache"
           maxElementsInMemory="10000"
           eternal="true"
           overflowToDisk="true"
           diskPersistent="true"
           diskExpiryThreadIntervalSeconds="600"/>

    <cache name="org.apache.shiro.realm.SimpleAccountRealm.authorization"
           maxElementsInMemory="100"
           eternal="false"
           timeToLiveSeconds="600"
           overflowToDisk="false"/>

</ehcache>
springmvc-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
        http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.0.xsd">


    <!--扫描controller包-->
    <context:component-scan base-package="com.tf.controller"></context:component-scan>
    <!-- 注解驱动 -->
    <mvc:annotation-driven></mvc:annotation-driven>
   <!--放行静态资源-->
    <mvc:default-servlet-handler></mvc:default-servlet-handler>

    <!-- 配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>


  <!-- shiro注解在spring相关类中生效
       @RequiresRoles("Manager")  //具有Manager角色才能访问
       如果在service层使用这些注解,则配置到spring配置文件中即可
        但,如果在controller中使用注解,需要把这几个配置配置到springmvc的配置文件中
    -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
          depends-on="lifecycleBeanPostProcessor"/>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>
</beans>
MyController
package com.tf.controller;

import org.apache.shiro.SecurityUtils;
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 MyController {
    @RequestMapping("/index")
    public String show(){
      return "main";
    }
    @RequestMapping("/login")
    public String login() {
        //模拟
        String username = "admin";
        String password = "jbgsn";
        Subject currentUser = SecurityUtils.getSubject();
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
        try {
            currentUser.login(usernamePasswordToken);
            //自动调用Realm 进行授权
            return "main";
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return null;
        }
    }


}
MyRealm
package com.tf.shiro02;

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 java.util.ArrayList;
import java.util.List;

/*
*  shiro涉及最少5张表
*  用户表    角色表    权限表
*    用户角色表    角色权限表
* */
public class MyRealm extends AuthorizingRealm {
   //授权
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
      System.out.println("授权===========");
        //把当前用户的角色和权限(菜单表或资源表)查询出来
        // 通过shiro提供的api授权处理即可
        // AuthorizationInfo 的一个子接口SimpleAuthorizationInfo
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
          //模拟数据库的数据
        List<String> roles = new ArrayList<String >();
        roles.add("admin");
        roles.add("ceo");
        roles.add("PD");
        List<String> permissions = new ArrayList<String>();
        permissions.add("admin");
        permissions.add("sys:user:add");
        permissions.add("sys:user:update");
          info.addRoles(roles);
          info.addStringPermissions(permissions);
        return info;
    }
  //认证 主要是用来 比对 用户名和密码
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
       System.out.println("认证===========");
      //用户输入的用户名和密码
       UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
       String username = token.getUsername();
       String password =new String(token.getPassword());

        //调用service层
        //模拟service
        String uname= "admin";
        String pwd = "jbgsn";
        if(username.equals(uname)){
            if(password.equals(pwd)){
                /*
                * 参数一:user对象 ,这里先因为没有封装对象,先传入username
                * 参数二:密码
                * 参数三:自定义realm的名字
                * */
                AuthenticationInfo info = new SimpleAuthenticationInfo(username,password,this.getName());
                return info;
            }else{
               throw new IncorrectCredentialsException("密码不存在!");
            }

        }else{
            throw  new UnknownError("账户不存在!");
        }
    }
}

验证结果:
一:先访问/index 是不能访问到main.jsp
二:应该先访问/login 才能访问到main.jsp
注意:如果这两步做颠倒了,需要先清理一下浏览器的缓存,再测试

授权

授权,也叫访问控制,即在应用中控制谁访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)。

主体(Subject):访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只有授权后才允许访问相应的资源。

资源(Resource):在应用中用户可以访问的 URL,比如访问 JSP 页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。

权限(Permission):安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如:访问用户列表页面查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权限控制)等。权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允许。

Shiro 支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限,即实例级别的)

角色(Role):权限的集合,一般情况下会赋予用户角色而不是权限,即这样用户可以拥有一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限。

有三种方式可以实现

一:编程式

@RequestMapping("/login")
    public String login(){
        String username = "admin";
        String password = "jbgsn";

        Subject currentUser = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
       // token.setRememberMe(true);//记住我功能   会话区间,直接能访问成功界面
         try{
           //subject.isRemembered();记住我
           subject.login(token);//自定义realm

            System.out.println(currentUser.hasRole("admin")?"具有admin角色":"不具有admin角色");
            System.out.println(currentUser.hasRole("PM")?"具有PM角色":"不具有PM角色");
            System.out.println(currentUser.hasRole("CEO")?"具有CEO角色":"不具有CEO角色");
            System.out.println(currentUser.isPermitted("sys:menu:add")?"具有新增菜单权限":"不具有菜单新增权限");
            System.out.println(currentUser.isPermitted("sys:menu:list")?"具有查询菜单权限":"不具有查询菜单权限");
            System.out.println(currentUser.isPermitted("sys:order:add")?"具有新增订单权限":"不具有新增订单权限");

           return "redirect:index";
       }catch(Exception e){
           System.out.println(e.getMessage());
           return  null;
       }
    }

这个显然很麻烦,而且可移植性不好,故不提倡使用

二:注解式

/**
     * 判断权限的第二种方式 注解式
     * @return
     */
    @RequiresRoles("Manager")  //具有Manager角色才能访问
    @RequestMapping("/index")
    public String index(){

        return "main";
    }

    @RequiresRoles("admin")  //具有admin角色才能访问
    @RequestMapping("/index1")
    public String index1(){

        return "main";
    }

    @RequiresPermissions({"sys:menu:list","sys:user:list"})
    @RequestMapping("/index2")
    public String index2(){

        return "main";
    }

但是记得:

<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
      depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    <property name="securityManager" ref="securityManager"/>
</bean>

第三种:使用shiro提供的标签

main.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--第一步导入标签库 只能再jsp中使用--%>
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<html>
<head>
    <title>主页</title>
</head>
<body>
<shiro:principal></shiro:principal>你好!
<shiro:hasRole name="admin">
    具有admin角色
</shiro:hasRole>
<shiro:hasPermission name="sys:user:add">
    添加用户
</shiro:hasPermission>
<shiro:hasPermission name="sys:user:delete">
    删除用户
</shiro:hasPermission>
</body>
</html>

Shiro 标签

• Shiro 提供了 JSTL 标签用于在 JSP 页面进行权限控制,如根据登录用户显示相应的页面按钮。

guest 标签
用户没有身份验证时显示相应信息,即游客访问信息:

user 标签:
用户已经经过认证/记住我登录后显示相应的信息。

authenticated 标签:
用户已经身份验证通过,即记住我
Subject.login登录成功,不是记住我登录的

notAuthenticated 标签:
用户未进行身份验证,即没有调用Subject.login进行登录,包括记住我自动登录的也属于未进行身份验证。

pincipal 标签:
显示用户身份信息,默认调用
Subject.getPrincipal() 获取,即 Primary Principal。

hasRole 标签:
如果当前Subject有任意一个角色(或的关系)将显示body体内容。

hasAnyRoles 标签:
如果当前Subject有任意一个角色(或的关系)将显示body体内容。

lacksRole
如果当前 Subject 没有角色将显示 body 体内容

hasPermission
如果当前 Subject 有权限将显示 body 体内容

lacksPermission
如果当前Subject没有权限将显示body体内容。

权限注解

@RequiresAuthentication:表示当前Subject已经通过login 进行了身份验证;即 Subject. isAuthenticated() 返回 true

@RequiresUser:表示当前 Subject 已经身份验证或者通过记住我登录的。

@RequiresGuest:表示当前Subject没有身份验证或通过记住我登录过,即是游客身份。

@RequiresRoles(value={“admin”, “user”}, logical= Logical.AND):表示当前 Subject 需要角色 admin 和user
@RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR):表示当前 Subject 需要权限 user:a 或
user:b。

@RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR):表示当前 Subject 需要权限 user:a 或
user:b。

身份验证基本流程
1、收集用户身份/凭证,即如用户名/密码
2、调用 Subject.login 进行登录,如果失败将得到相应的 AuthenticationException 异常,根据异常提示用户
错误信息;否则登录成功
3、创建自定义的 Realm 类,继承org.apache.shiro.realm.AuthorizingRealm 类,实现doGetAuthenticationInfo() 方法

AuthenticationException
如果身份验证失败请捕获 AuthenticationException 或其子类
最好使用如“用户名/密码错误”而不是“用户名错误”/“密码错误”,防止一些恶意用户非法扫描帐号库;
Realm
Realm:Shiro 从 Realm 获取安全数据(如用户、角色、权限),即 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作

Realm 的继承关系:
一般继承 AuthorizingRealm(授权)即可;其继承了AuthenticatingRealm(即身份验证),而且也间接继承了CachingRealm(带有缓存实现)。

Authenticator
Authenticator 的职责是验证用户帐号,是 Shiro API 中身份验证核心的入口点:如果验证成功,将返回AuthenticationInfo 验证信息;此信息中包含了身份及凭证;如果验证失败将抛出相应的 AuthenticationException 异常

SecurityManager 接口继承了 Authenticator,另外还有一个ModularRealmAuthenticator实现,其委托给多个Realm 进行
验证,验证规则通过 AuthenticationStrategy 接口指定

AuthenticationStrategy
realm 的认证方式

AuthenticationStrategy 接口的默认实现:
FirstSuccessfulStrategy:只要有一个 Realm 验证成功即可,只返回第一个 Realm 身份验证成功的认证信息,其他的忽略;

AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和FirstSuccessfulStrategy 不同,将返回所有Realm身份验证成功的认证信息

AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了。

ModularRealmAuthenticator 默认是 AtLeastOneSuccessfulStrategy策略

授权流程

流程如下:
1、首先调用 Subject.isPermitted*/hasRole* 接口,其会委托给SecurityManager,而 SecurityManager 接着会委托给 Authorizer;
2、Authorizer是真正的授权者,如果调用如isPermitted(“user:view”),其首先会通过 PermissionResolver 把字符串转换成相应的 Permission 实例;
3、在进行授权之前,其会调用相应的 Realm 获取 Subject 相应的角色/权限用于匹配传入的角色/权限;
4、Authorizer 会判断 Realm 的角色/权限是否和传入的匹配,如果有多个Realm,会委托给 ModularRealmAuthorizer 进行循环判断,
如果匹配如 isPermitted*/hasRole* 会返回true,否则返回false表示授权失败。

ModularRealmAuthorizer
ModularRealmAuthorizer 进行多 Realm 匹配流程:
1、首先检查相应的 Realm 是否实现了实现了Authorizer;
2、如果实现了 Authorizer,那么接着调用其相应的isPermitted*/hasRole* 接口进行匹配;
3、如果有一个Realm匹配那么将返回 true,否则返回 false。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值