shiro以及shiro集成Spring_weixin_44906154的博客-程序员宅基地

  1. Shiro简介     
    1. 是什么?

Apache Shiro是一个强大且易用的Java安全框架,有身份验证、授权、密码学和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

Spring security 重量级安全框架

Apache Shiro轻量级安全框架

    1. shiro能干什么?

Shiro 开发团队称为“应用程序的四大基石” ——身份验证,授权,会话管理和加密作为其目标。

 Authentication(身份认证):

        

  有时也简称为“登录”,这是一个证明用户是他们所说的他们是谁的行为。

 Authorization(授权):

访问控制的过程,也就是绝对“谁”去访问“什么”权限

 Session Management:管理用户特定的会话,即使在 Web 或 EJB 应用程序。

 Cryptography:通过使用加密算法保持数据安全同时易于使用。

也提供了额外的功能来支持和加强在不同环境下所关注的方面,尤其是以下这些:

 Web Support: Shiro 的 web 支持的 API 能够轻松地帮助保护 Web 应用程序。

 Caching:缓存是 Apache Shiro 中的第一层公民,来确保安全操作快速而又高效。

 Concurrency: Apache Shiro 利用它的并发特性来支持多线程应用程序。

 Testing:测试支持的存在来帮助你编写单元测试和集成测试,并确保你的能够如预期的一样安全。

 "Run As":一个允许用户假设为另一个用户身份(如果允许)的功能,有时候在管理脚本很有用。

 "Remember Me":在会话中记住用户的身份,所以他们只需要在强制时候登录。

 

    1. 架构
      1. Shiro外部来看

从外部来看Shiro,即从应用程序角度来如何使用shiro来完成工作(认证、授权等)。

      1. Shiro内部看

即shiro内部夹走

小结:

Shiro:是一个apache的安全框架,还有spring security

Shiro:能做:身份认证(登录)authentication,授权authorization,密码学,会话管理。

应用--Subject(当前用户)---Security Mananger管理--Realm----db

  1. Shiro入门

前提:创建一个普通Maven项目

    1. 导入jar

<dependencies>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.4.0</version>
    </dependency>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

 

    1. 准备资源

这个资源我们可以到shiro的源码文件中拷备:

/shiro-root-1.4.0-RC2/samples/quickstart/src/main/resources

shiro.ini

log4j.properties

注:users下面代表的登录的用户(用户名=密码)

shiro.ini文件(对拷备过来的数据进行了解释):


# -----------------------------------------------------------------------------
#
所有的用户与对应的用户密码 ,用户的角色
# -----------------------------------------------------------------------------
[users]
# root
用户有一个密码是secret,他的角色是admin
root = secret, admin
guest = guest, guest
presidentskroob = 12345, president
# darkhelmet
用户有一个密码是ludicrousspeed,他的角色是darklord, schwartz
darkhelmet = ludicrousspeed, darklord, schwartz
lonestarr = vespa, goodguy, schwartz

# -----------------------------------------------------------------------------
#
所有的用户的角色
# -----------------------------------------------------------------------------
[roles]
# admin
这个角色有所有权限功能
admin = *
#schwartz
这个角色可以操作所有的lightsaber权限功能
schwartz = lightsaber:*
#goodguy
这个角色可以操作winnebago下面的drive权限的一个eagle5资源
goodguy = winnebago:drive:eagle5

 

    1. 测试

Session数据

登录

授权

这个文件位置 : /shiro-root-1.4.0-RC2/samples/quickstart/src/main/java

我们下面对这个文件进行了中文的解释

/**
 *
简单的入门示例,教我们怎么使用shiro中的一个API
 */

public class Quickstart {

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

   
public static void main(String[] args) {
       
//读取到shiro.ini文件,并且根据文件创建SecurityManager对象
        //  SecurityManager
:权限的核心管理对象
       
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();

       
// 为了程序正常运行,需要设计一个这个securityManager(相当于把这个管理放到环境中去了)
       
SecurityUtils.setSecurityManager(securityManager);

       
//拿到当前用户【Subject:操作当前系统的一个用户】
       
Subject currentUser = SecurityUtils.getSubject();

       
//拿到一个会话(它在咱们的所有类型系统都可以使用【不要求必需是web项目】)
       
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 + "]");
           
System.out.println("可以获取到这个值"+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());
                //token.getPrincipal()[ˈprɪnsəpl] ->
拿到主体(登录用户的主要标识【用户名】)
               
System.out.println("这没有这个账号(用户名)"+token.getPrincipal());
            }
catch (IncorrectCredentialsException ice) {
               
//incorrect [ˌɪnkəˈrekt]:不正确的,错误的
                //Credentials[krəˈdenʃlz]:
证书; 凭证,证件
                //log.info("Password for account " + token.getPrincipal() + " was incorrect!");
               
System.out.println("这个密码是错误的"+token.getPrincipal());
            }
catch (LockedAccountException lae) {
                System.
out.println("这个账号已经被锁定");
            }
           
//其它的登录失败异常【它是所有登录异常的父类】
           
catch (AuthenticationException ae) {
               
//unexpected condition?  error?
           
}
        }

       
//用户登录成功
        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.");
        }

        
//a (very powerful) Instance Level permission:
       
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. 总结

通过SecurityManger获取Suject :

SecurityUtils.setSecurityManager(securityManager);

Subject currentUser = SecurityUtils.getSubject();

通过Subject来做事情:

   获取Session-存值和获取值 Suject.getSession session.setAttribute session.getAttribute

   认证(登录):

判断是否登录Suject.isAuthenticated()

如果没有登录,通过用户名和密码创建UsernamePasswordToken

调用subject.login(UsernamePasswordToken)来进行登录判断

登陆成功: 返回principal给当前用户

登陆失败: 抛出异常

                授权

                   判断是否具有某个角色subject .hasRole

                   判断是否有权限subject .isPrermited(resource:操作)

  1. 自定义Realm
刚才咱们使用的是准备的好的一个Realm,Shiro中,除了咱们 刚才使用的iniRealm,还有普通SimpleAccountRealmJDBCRealm等等.
但是这些Realm都不能满足咱们的需求(后期咱们Realm的数据肯定是通过JPA去拿的,下一个项目要使用MyBatis去拿),而这种情况下,就需要我们自己定义一个Realm来实现相应的功能!
    1. 准备工作
自定义Realm一般直接继承AuthorizingRealm抽象类即可(里面包含身份认证与授权两个方法)

/**
 *
自定义一个Realm
 */
public class MyRealm
extends AuthorizingRealm {

    //
获取到这个Realm的名称(随便取)
    @Override
    public String getName() {
        return "MyRealm";
    }

  
 //进行授权判断(权限)
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

  
 //进行身份认证(登录)
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
           return null;
    }


}

    1. 身份认证(登录)
      1. 身份认证的代码

/**
 *
自定义一个Realm
 */
public class MyRealm extends AuthorizingRealm {

    //
获取到这个Realm的名称(随便取)
    @Override
    public String getName() {
        return "MyRealm";
    }

    //
进行授权的认证
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    //
进行登录的认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //
明显的知道:这个authenticationToken就是UsernamePasswordtoken
        UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
        String username = token.getUsername(); //
拿到用户名(:这个用户名是传过来的)
        //
这里根据用户名去获取密码(如果没有获取到,相当于这个用户不存在,就返回null)
        String password = getByName(username);
        if(password==null){
            return null;
        }
        //
创建一个简单的身份信息(把用户名与密码放进去-注:它会自动的比较获取的密码与你传过来的密码)
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username,password,getName());
        return authenticationInfo;
    }

 

//模拟从数据库中获取信息
    private String getByName(String username) {
        if("admin".equals(username)){
            return "123456";
        }else if("guest".equals(username)){
            return "abcd";
        }
        return null;
    }
}

      1. 身份认证测试

@Test
public void testMyRealm() throws Exception{

    //
创建自己定义的Realm
   
MyRealm myRealm = new MyRealm();
   //Realm放到securityManager中去
    DefaultSecurityManager securityManager = new DefaultSecurityManager();
    securityManager.setRealm(myRealm);
    //
把权限管理器放到相应的环境中(我们可以在项目任何位置拿到)
    SecurityUtils.setSecurityManager(securityManager);

    //
拿到当前用户(Subject就是当前用户,游客)
    Subject currentUser = SecurityUtils.getSubject();
    //
准备登录的令牌(准备用户名与密码)
    UsernamePasswordToken token = new UsernamePasswordToken("admin","123456");

    try {
        //
根据令牌进行功能登录(当前用户进行登录)
        currentUser.login(token);
    } catch (UnknownAccountException e) {
        System.out.println("
这个账号不存在!" + token.getPrincipal());
        e.printStackTrace();
    } catch (IncorrectCredentialsException ice) {
        System.out.println("
这个密码不存在!" + token.getPrincipal());
        ice.printStackTrace();
    }catch (AuthenticationException e){
        System.out.println("i don't k");
    }

}

    1. 授权(权限)认证
      1. 授权认证的代码

public class MyRealm extends AuthorizingRealm {

    //
获取到这个Realm的名称(随便取)
    @Override
    public String getName() {
        return "MyRealm";
    }

    //
进行授权的认证
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //
拿到认证的主要信息(用户名)
      
 String username = (String) principalCollection.getPrimaryPrincipal();
        //模拟根据用户名拿到角色信息与权限信息
        Set<String> roles = getRolesByUsername(username);
        Set<String> permissions = getPermissionsByUsername(username);
        //
拿到验证信息对象
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        //
设置用户的角色
        authorizationInfo.setRoles(roles);
        //
设置用户的权限
        authorizationInfo.setStringPermissions(permissions);
        return authorizationInfo;
    }

    //
模拟根据用户名拿到角色的功能
    private Set<String> getRolesByUsername(String username) {
        Set<String> roles = new HashSet<>();
        roles.add("admin");
        roles.add("it");
        return roles;
    }
    //
模拟根据用户名拿到权限的功能
    private Set<String> getPermissionsByUsername(String username) {
        Set<String> permissions = new HashSet<>();
        permissions.add("employee.*");
        permissions.add("department.save");
        return permissions;
    }

 ...

}

      1. 功能测试

System.out.println("是否是这个角色:"+currentUser.hasRole("admin"));
System.out.println("
是否是拥有权限:"+currentUser.isPermitted("employee.save"));

  1. 密码加密功能
    1. Shiro中密码加密

/**
 * algorithmName:
加密算法(md5,sha)
 *  source:
原始密码
 *  salt,加盐
 *  hashIterations:遍历次数
 */
SimpleHash simpleHash = new SimpleHash("MD5","admin","itsource",10);
System.out.println(simpleHash);

 

    1. 测试时让自定义Reaml加上算法

@Test
     public void testMyRealm() throws Exception{
         //
创建自己定义的Realm
         MyRealm myRealm = new MyRealm();
        //
Realm放到securityManager中去
         DefaultSecurityManager securityManager = new DefaultSecurityManager();
         securityManager.setRealm(myRealm);
         //
把权限管理器放到相应的环境中(我们可以在项目任何位置拿到)
         SecurityUtils.setSecurityManager(securityManager);

         //
设置咱们Realm的密码匹配器(我们的密码要怎么处理)
         HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
         matcher.setHashAlgorithmName("md5"); //
匹配器使用MD5的算法
         matcher.setHashIterations(10);//加密算法要迭代多少次
         myRealm.setCredentialsMatcher(matcher);

         //拿到当前用户(Subject就是当前用户,游客)
         Subject currentUser = SecurityUtils.getSubject();
         //
准备登录的令牌(准备用户名与密码) -> 这里的密码进行了加密
         UsernamePasswordToken token = new UsernamePasswordToken("admin","123456");

         try {
             //
根据令牌进行功能登录(当前用户进行登录)
             currentUser.login(token);
             System.out.println("
登录成功啦。。。。");
         } catch (UnknownAccountException e) {
             System.out.println("
这个账号不存在!" + token.getPrincipal());
             e.printStackTrace();
         } catch (IncorrectCredentialsException ice) {
             System.out.println("
这个密码不存在!" + token.getPrincipal());
             ice.printStackTrace();
         }catch (AuthenticationException e){
             System.out.println("i don't k");
         }
     }

    1. 自定义Reaml加上盐值

//进行登录的认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    //
明显的知道:这个authenticationToken就是UsernamePasswordtoken
    UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
    String username = token.getUsername(); //
拿到用户名(:这个用户名是传过来的)

    //
这里根据用户名去获取密码(如果没有获取到,相当于这个用户不存在,就返回陪我)
    String password = getByName(username);
    if(password==null){
        return null;
    }
   
//在这里加盐值需一个ByteSource对象,而Shiro提供了一个ByteSource对象给咱们
    ByteSource salt = ByteSource.Util.bytes("itsource");

    //创建一个简单的身份信息(把用户名与密码放进去-注:它会自动的比较获取的密码与你传过来的密码)
    SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username,password,
salt,getName());
    return authenticationInfo;
}

private String getByName(String username) {
    if("admin".equals(username)){
        // 4a95737b032e98a50c056c41f2fa9ec6: 123456
迭代10次不加盐的结果
        // 831d092d59f6e305ebcfa77e05135eac: 123456 迭代10次加盐(itsource)的结果
        return "831d092d59f6e305ebcfa77e05135eac"; //修改为加密加盐后的数据
    }else if("guest".equals(username)){
        return "abcd";
    }
    return null;
}

Shiro集成Spring

 

  1. 为什么要集成Spring

我们的项目基本都是通过Spring来管理bean的,如果要想使用Shiro,那就要把shiro集成到Spring。集成Spring的核心就是把框架的核心类(SecurityManager,Subject,Realm)交给Spring管理

    1. 集成
      1. 准备Spring-web项目

拷备咱们的SSJ集成项目/直接在当前配置中完成

      1. 集成
  1. 导入shiro-all-1.3.2.ja(现升级到1.4.0)r

  参考官方demo做示例:

<!-- shiro的支持包 -->
 
<dependency>
    <
groupId>org.apache.shiro</groupId>
    <
artifactId>shiro-all</artifactId>
    <
version>1.4.0</version>
    <
type>pom</type>
</
dependency>
 
<!-- shiroSpring的集成包 -->
 
<dependency>
    <
groupId>org.apache.shiro</groupId>
    <
artifactId>shiro-spring</artifactId>
    <
version>1.4.0</version>
  </
dependency>

  1. 拷贝shiroFilter配置到web.xml

<!-- Springshiro集成:需要定义一个shiro过滤器(这是一个代理过滤器,它会到spring的配置中找一个名称相同的真实过滤器) -->
<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>

 

  1. 拷贝shiro Spring配置文件并集成到Spring

applicationContext.xml 改名为applicationContext-shiro.xml并在applicationContext.xml中导入

  1. 修改配置文件

<?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-3.0.xsd">


   
<!-- 1.配置apache的管理器 -->
   
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
       
<!-- 配置一个realm,到数据库中获取权限数据 -->
       
<property name="realm" ref="jpaRealm"/>
    </
bean>


   
<!-- 2.我们可以自定义一个realm【暂时未实现功能】这个必需实现org.apache.shiro.realm.Realm接口 -->
   
<bean id="jpaRealm" class="cn.itsource.yxb.shiro.realm.JpaRealm">
    </
bean>

   
<!-- 3.lifecycleBeanPostProcessor:可以自动调用在Spring Ioc窗口中 Shiro bean的生成周期方法 -->
   
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
   
<!-- 4.启动ioc容器中使用 shiro的注解,但是必需配置在Spring Ioc容器中Shiro bean的生成周期方法 -->
   
<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>

   
<!-- 5.shiro的真实过滤器(注:这个名称必需和web.xml的代表过滤器【DelegatingFilterProxy】名称一样) -->
   
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <
property name="securityManager" ref="securityManager"/>
      
 <!-- 登录的url,如果没有登录,你访问的路径会跳到这个页面 -->
       
<property name="loginUrl" value="/s/login.jsp"/>
       
<!-- 登录成功的url,如果登录成功,会跳转到这个页面 -->
       
<property name="successUrl" value="/s/main.jsp"/>
       
<!-- 没有权限时跳转到这个位置 -->
       
<property name="unauthorizedUrl" value="/s/unauthorized.jsp"/>
       
<!--
        
   
配置哪些资源被保护,哪些资源需要权限
            anon
:不需要登录也可以访问相应的权限
            authc
:需要权限才能访问
              /**
:所有文件及其子文件
        -->
       
<property name="filterChainDefinitions">
            <
value>
                /s/login.jsp = anon
                /** = authc
            </
value>
        </
property>
    </
bean>

</
beans>

  1. 测试

除了登录界面能够被正常访问,其他的都要跳转到登录界面!

 

    1. 小结

注意:需要一个Spring的环境,不能全部都在SpringMVC那儿初始化

  1. Authentication(身份认证)
    1. 引入

在集成Spring的例子中,只有登录页面是不需要登录可以直接访问。其他的页面需要认证(即登录)后才能访问,那怎么来实现登录认证,又叫做身份认证呢?

    1. 概念

做认证需要用户名和密码

认证流程:

    1. 详细步骤分析

要想实现登录,分前台后台两部分:

前台:

实现一个login.jsp的页面,用来搜集登录信息(用户名和密码)!当点击登录时把登录信息提交到后台完成认证。

       后台:

写一个Controller接收前台传入的登录信息,完成登录认证。

具体步骤如下:

  1. 创建LoginConroller,写一个方法接收前台登录请求并接受登录信息(用户名和密码)
  2. 获取当前的 Subject. 调用 SecurityUtils.getSubject();
  3. 测试当前的用户是否已经被认证. 即是否已经登录. 调用 Subject 的 isAuthenticated()
  4. 若没有被认证, 则把用户名和密码封装为 UsernamePasswordToken 对象
  5. 执行登录: 调用 Subject 的 login(AuthenticationToken) 方法.
  6. 自定义 Realm 的方法, 从数据库中获取对应的记录, 返回给 Shiro.

入门中使用的是Shiro自带的IniRealmIniRealmini配置文件中读取用户的信息,大部分情况下需要从系统的数据库中读取用户信息,所以需要自定义realm

实际上需要继承 org.apache.shiro.realm.AuthenticatingRealm

实现 doGetAuthenticationInfo(AuthenticationToken) 方法.

  1. 由 shiro 完成对密码的比对.
    1. 简单认证实现
      1. login.jsp

<form action="/login" method="post">
   
用户名<input type="text" name="username" /> <br />
   
密码:<input type="password" name="password" /> <br />
    <
input type="submit" value="登录" />
</
form>

      1. LoginController

@Controller
public class LoginController {
   
@RequestMapping("/login")
   
public String login(String username,String password){
        /**
         *
获取当前的 Subject. 调用 SecurityUtils.getSubject();
         *
测试当前的用户是否已经被认证. 即是否已经登录. 调用 Subject isAuthenticated()
         *
若没有被认证, 则把用户名和密码封装为 UsernamePasswordToken 对象
         *
执行登录: 调用 Subject login(AuthenticationToken) 方法.
         */
        //1.
拿到访问的主体(当前登录用户)
       
Subject subject = SecurityUtils.getSubject();
        //2.
判断这个用户是否已经登录(通过验证)
       
if(!subject.isAuthenticated()){
            //3.
如果没有验证,就要完成登录
           
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
           
try{
                //4.
根据toke完成登录功能
               
subject.login(token);
            }
catch (UnknownAccountException e){
                System.
out.println("用户名不存在!!");
                e.printStackTrace();
            }
catch (IncorrectCredentialsException e){
                System.
out.println("密码不存在!");
                e.printStackTrace();
            }
catch (AuthenticationException e){
                System.
out.println("登录出错!");
                e.printStackTrace();
            }

        }
       
return "redirect:/s/main.jsp";
    }
}

 

      1. JpaRealm

 

public class JpaRealm extends AuthenticatingRealm {

   
//AuthenticationInfo:认证; 身份验证; 证明
    //
登录的时候就会调用这个方法来做验证
   
@Override
   
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
       
//System.out.println(token.getClass().getName());
        //
可以转成相应的token
        //UsernamePasswordToken upToken =(UsernamePasswordToken)token;
        //
拿到传过来的用户名
        //String username = upToken.getUsername();
        //
自定义Realm中,需要根据用户名到数据库中拿到密码,然后返回给shiro
        //String password = "123456";
        //
身份认证(用户名)
       
Object principal = token.getPrincipal();
        Object credentials =
"123456"; //密码假设是根据用户名到数据库中查询出来的
        // Object principal, Object credentials, String realmName
       
return new SimpleAuthenticationInfo(principal,credentials,getName());
    }
}

 

    1. 加密认证

分析:

  1、密码不能明文保存到数据库 加密保存,比对也要加密比对,具体的比对CredentialsMatcher,默认使用就是SimpleCredentialsMatcher,不做加密的比对.要想做md5加密要使用HashedCredentialsMatcher,并且设置加密为md5

      1. 数据库加密保存

 

      1. 加密比对代码实现

//完成登录的认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
    String username = token.getUsername();
    String dbPassword  = findByUsername(token.getUsername());
    if(dbPassword==null){
        return null;
    }
    SimpleAuthenticationInfo authorizationInfo = new SimpleAuthenticationInfo(username,dbPassword,getName());
    return authorizationInfo;
}

private String findByUsername(String username) {
    if("admin".equals(username)){
        //
这里是没有加盐的结果
        return "4a95737b032e98a50c056c41f2fa9ec6";
    }
    return null;
}

设置Realm的匹配值:

<!-- 2.我们可以自定义一个realm 这个必需实现org.apache.shiro.realm.Realm接口 -->
<
bean id="jpaRealm" class="cn.itsource.yxb.shiro.realm.JpaRealm">
    <
property name="credentialsMatcher">
        <!—
设置加密匹配方案-->
        <
bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
            <!--
编码的方式使用:md5 -->
            <
property name="hashAlgorithmName" value="MD5"/>
            <!--
编码的次数:10 -->
            <
property name="hashIterations" value="10"/>
        </
bean>
    </
property>
</
bean>

    1. 盐值加密认证

为什么要使用盐值加密。盐值加密,更安全!

      1. 数据库要加密保存

@Test
public void testHash() throws Exception{
    /**
     * algorithmName:
加密算法(md5,sha)
     *  source:
原始密码
     *  salt,加盐
     *  hashIterations:遍历次数
     */
    SimpleHash simpleHash = new SimpleHash("MD5","123456","itsource",10);
    System.out.println(simpleHash);
}

 

      1. 认证时要支持盐值  

//AuthenticationInfo:认证; 身份验证; 证明
//登录的时候就会调用这个方法来做验证
//进行登录的认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    //
明显的知道:这个authenticationToken就是UsernamePasswordtoken
    UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
    String username = token.getUsername(); //
拿到用户名(:这个用户名是传过来的)
    //
这里根据用户名去获取密码(如果没有获取到,相当于这个用户不存在,就返回陪我)
    String password = getByName(username);
    if(password==null){
        return null;
    }
    //
在这里加盐值需一个ByteSource对象,而Shiro提供了一个ByteSource对象给咱们
    ByteSource salt = ByteSource.Util.bytes("itsource");
    //创建一个简单的身份信息(把用户名与密码放进去-注:它会自动的比较获取的密码与你传过来的密码)
    SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username,password,
salt,getName());
    return authenticationInfo;
}

private String getByName(String username) {
    if("admin".equals(username)){
        // 4a95737b032e98a50c056c41f2fa9ec6: 123456
迭代10次不加盐的结果
        // 831d092d59f6e305ebcfa77e05135eac: 123456 迭代10次加盐(itsource)的结果
        return "831d092d59f6e305ebcfa77e05135eac"; //修改为加密加盐后的数据
    }else if("guest".equals(username)){
        return "abcd";
    }
    return null;
}

    1. 小结

普通认证

加密认证

加密加盐认证(采纳

  1. Authorizing(授权)
    1. 为什么要授权

用户(n)-角色(n)-权限(n)-资源(n)

有的资源需要有对应的权限才能访问,如果想要某个用户能够访问某个资源,必须给对应用户授权。

对于授权需要做两个事情:

权限拦截:当用户访问没有权限的资源时进行拦截

权限判断:给用户授权

    1. 授权
      1. 概念

 

用户--角色---权限--资源

      1. Shiro的三种权限判断方式
        1. 编程式

通过写if/else 授权代码块完成:

Subject subject = SecurityUtils.getSubject();

if(subject.hasRole(“admin”)) {

//有权限

} else {

//无权限

}

 

        1. 注解式

通过在执行的Java方法上放置相应的注解完成:

@RequiresRoles("admin")

public void hello() {

//有权限

}

 

        1. JSP/GSP 标签

在JSP/GSP 页面通过相应的标签完成:

<%@taglib prefix=”shiro” uri=”http://shiro.apache.org/tags”%>

<shiro:hasRole name="admin">

<! 有权限—>

<button>删除</button>

</shiro:hasRole>

 

//1、控制哪些资源要做权限拦截  如果不做拦截,直接放行,如果要拦截就要做权限判断

//2、获取用户能够访问资源,如果你访问资源在里面,说明有权限。可以访问,否则不能访问。

不控制直接放行,要控制查看用户是否拥有,如果有直接访问,否则不能访问

    1. 授权拦截

权限拦截同样需要从数据库中获取权限或角色信息,所以同样需要自定义Realm.

注意:现要权限写死在配置中!

<!-- 5.shiro的真实过滤器(注:这个名称必需和web.xml的代表过滤器【DelegatingFilterProxy】名称一样) -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <
property name="securityManager" ref="securityManager"/>
    <!--
登录的url,如果没有登录,你访问的路径会跳到这个页面 -->
   
<property name="loginUrl" value="/s/login.jsp"/>
    <!--
登录成功的url,如果登录成功,会跳转到这个页面 -->
   
<property name="successUrl" value="/s/main.jsp"/>
    <!--
没有权限时跳转到这个位置 -->
   
<property name="unauthorizedUrl" value="/s/unauthorized.jsp"/>
    <!--
       
配置哪些资源被保护,哪些资源需要权限
        anon
:不需要登录也可以访问相应的权限
        authc
:需要权限才能访问
          /**
:所有文件及其子文件
    -->
   
<property name="filterChainDefinitions">
        <
value>
            /s/login.jsp = anon
            /login = anon
            <!--
对资源进行权限控制,要控制的资源都应该从数据库查出,现在咱们先写死-->
           
/s/permission.jsp = perms[user:*]
            /** = authc
        </
value>
    </
property>
</
bean>

自定义Realm授权

授权需要继承 AuthorizingRealm 类, 并实现其 doGetAuthorizationInfo 方法

AuthorizingRealm 类继承自 AuthenticatingRealm, 但没有实现 AuthenticatingRealm 中的doGetAuthenticationInfo, 所以认证和授权只需要继承 AuthorizingRealm 就可以了. 同时实现他的两个抽象方法.

具体实现:

 

public class JpaRealm extends AuthorizingRealm {
   
//AuthorizationInfo:授权(是否有权限进入操作)
    //
我们只需要把相应的权限交给Shiro,它就会自动比对
   
@Override
   
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
       
//拿到主体信息(指的就是当前登录用户名) -> 咱们的权限应该是根据当前用户进行查询到的
       
String username = (String) principals.getPrimaryPrincipal();
        System.
out.println("后面需要根据用户名获取资源"+username);
       
//获取权限资源(这里假设已经根据用户名到数据库中获取到了)
       
Set<String> permissions = new HashSet<>();
        permissions.add(
"user:*");
        //拿到授权对象,并且所有权限交给它
       
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setStringPermissions(permissions);
       
//返回授权对象
       
return simpleAuthorizationInfo;
    }


    //AuthenticationInfo:认证; 身份验证; 证明
    //
登录的时候就会调用这个方法来做验证
   
@Override
   
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
       
/**
         * Object principal
:主体(现在就是登录的用户名)
         * Object hashedCredentials
:编码后的认证密码
         * ByteSource credentialsSalt:
加盐
         * String realmName
         */
        //
身份认证(用户名)
       
Object principal = token.getPrincipal();
       
//密码假设是根据用户名到数据库中查询出来的(数据库密码是经过加密的)
       
Object credentials = "d5a3fedf6c59c2ecbe7f7a6c1a22da37";
       
//加盐的数据
       
ByteSource byteSource = ByteSource.Util.bytes("itsource");
       
return new SimpleAuthenticationInfo(principal,credentials,byteSource,getName());
    }
}

    1. 小结

授权拦截:

    写死

    通过factoryBean动态获取

通过Realm获取用户权限返回给框架

  1. 项目中使用
    1. 授权数据库查询

     要控制权限的资源做拦截

/**
 *
准备一个构造器类
 */
public class FilterChainDefinitionMapBuilder {
   
public Map<String,String> createFilterChainDefinitionMap(){
        Map<String, String> filterChainDefinitionMap =
new LinkedHashMap();
        filterChainDefinitionMap.put(
"/s/login.jsp","anon");
        filterChainDefinitionMap.put(
"/login","anon");
        //
这个值之后从数据库中查询到【用户-角色-权限-资源】
        filterChainDefinitionMap.put("/s/permission.jsp","perms[user:*]");
        filterChainDefinitionMap.put(
"//**","authc");
       
return  filterChainDefinitionMap;
    }
}

    <!-- 5.shiro的真实过滤器(注:这个名称必需和web.xml的代表过滤器【DelegatingFilterProxy】名称一样) -->
   
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <
property name="securityManager" ref="securityManager"/>
       
<!-- 登录的url,如果没有登录,你访问的路径会跳到这个页面 -->
       
<property name="loginUrl" value="/s/login.jsp"/>
       
<!-- 登录成功的url,如果登录成功,会跳转到这个页面 -->
       
<property name="successUrl" value="/s/main.jsp"/>
       
<!-- 没有权限时跳转到这个位置 -->
       
<property name="unauthorizedUrl" value="/s/unauthorized.jsp"/>
       
<!--
           
配置哪些资源被保护,哪些资源需要权限
            anon
:不需要登录也可以访问相应的权限
            authc
:需要权限才能访问
              /**
:所有文件及其子文件
        -->
        <!--<property name="filterChainDefinitions">-->
            <!--<value>-->
                <!--/s/login.jsp = anon-->
                <!--/login = anon-->
                <!--
对资源进行权限控制,要控制的资源都应该从数据库查出,现在咱们先写死-->
                <!--/s/permission.jsp = perms[user:*]-->
                <!--/** = authc-->
            <!--</value>-->
        <!--</property>-->
        <!--
这个配置我们可以直接给一个map(动态的可以从代码中获取) -->
       
<property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property>
    </bean>

   
<!-- 这个bean是帮助咱们获取相应的值:它会到一个工厂bean中通过对应的方法拿到相应的值 -->
   
<bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder" factory-method="createFilterChainDefinitionMap"></bean>
   
<!-- 配置可以创建 -->
   
<bean id="filterChainDefinitionMapBuilder" class="cn.itsource.yxb.shiro.realm.FilterChainDefinitionMapBuilder"></bean>
</beans>

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_44906154/article/details/95801253

智能推荐

微信扫码支付 模式一 (JSAPI)_ouyuyao的博客-程序员宅基地

这个微信支付是静态二维码支付,就是店面贴着一个二维码,让消费者自己扫自己输入金额,自己发起支付的支付方式。要准备的东西比较麻烦: 1、到微信公众号平台设置Oauth2的网页验证域名(用于获取code,code用于拿到发起支付的openId),格式是www.xxxx.com/file1/file2/,不需要https:// 要精确到发起支付页面的当前路径 2、配置Oauth2网页验证域名的时候,需

一文梳理金融风控建模全流程(Python)_风度78的博客-程序员宅基地

一、信贷风控简介 信贷风控是数据挖掘算法最成功的应用之一,这在于金融信贷行业的数据量很充足,需求场景清晰及丰富。信贷风控简单来说就是判断一个人借了钱后面(如下个月的还款日)会不会按期还钱。更专业来说,信贷风控是还款能力及还款意愿的综合考量,根据这预先的判断为信任依据进行放贷,以此大大提高了金融业务效率。与其他机器学习的工业场景不同,金融是极其厌恶风险的领域,其特殊性在于非...

lambda引入时 Error:Execution failed for task ':test:transformClassesWithRetrolambdaForDeb_这是一个秘密的博客-程序员宅基地

在引入tatarka:gradle-retrolambda时报错Error:Execution failed for task ':test:transformClassesWithRetrolambdaForDeb 将版本改为3.7.0即可(我引入的是3.5.0错了)classpath 'me.tatarka:gradle-retrolambda:3.7.0'...

【python】清空jupyter notebook所有输出的命令_嘤桃子的博客-程序员宅基地_jupyter 清空输出

在跑六百万数据集时加了print,导致输出的内容太多,不仅跑了几个小时,还让ipynb文件非常大根本打不开!!清空jupyter notebook输出的命令:jupyter nbconvert --ClearOutputPreprocessor.enabled=True --inplace xxxx.ipynb如果想把清空所有输出后的源代码文件另存为,命令为:jupyter nbconvert --ClearOutputPreprocessor.enabled=True --to notebook

如何利用github在.md文件中添加图片_曦曦湘涟的博客-程序员宅基地_github md图片

1、在github上的仓库建立一个存放图片的库,库的名字随意。如:Images-blog2、将需要在.md文件中显示的图片整理在一个文件夹中,然后push到Images-blog库中3、然后打开github官网,进入仓库的Images-blog库中,打开图片点击红框所示的按钮,copy地址4、在.md文件中填入:![Image](https://raw.githubuserconte...

前端工程化和webpack_温柔且无情的博客-程序员宅基地

前端工程化和webpack前端工程化简介前端工程化指的是:在企业级的前端项目开发中,把前端开发所需的工具、技术、流程、经验等进行规范化、标准化。企业中的Vue项目和React项目,都是基于 工程化 的方式进行开发的。工程化开发好处:自成体系、标准的开发方案和流程前端开发四个现代化:模块化 (JavaScript模块化、CSS模块化、静态资源模块化)组件化 (复用现有的UI结构、样式、行为)规范化 (目录结构的划分、编码规范化、接口规范化、文档规范化、Git分支管理)自动化 (自

随便推点

16 Java数组--数组的定义、声明和创建_一起加油(ง •̀_•́)ง的博客-程序员宅基地

数组的定义数组是相同类型数据的有序集合数组描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成每一个数据称作一个数组元素,每个数组元素可以通过一个下标来访问它们。数组的声明首先必须声明数组变量,才能在程序中使用数组dataType[] arrayRefVar; //首选声明方法或dataType arrayRefVar[];Java语言使用new操作符来创建数组dataType[] arrayRefVar = new dataType[arraySize];

Image Smoothing via L0 Gradient Minimization基于C++的代码实现_铿锵的玫瑰的博客-程序员宅基地

论文解析图像梯度L0范数最小化图像梯度最小化平滑---一维信号图像梯度最小化平滑---二维图像源代码//实现L0测度平滑Mat L0Smoothing(Mat &amp;amp; image8UC3, double lambda = 2e-2, double kappa = 2.0){ //将输入的图片转换为三通道的Double类型,按原来的像素值进行1...

vue传递json数据给springboot_长春小霸王的博客-程序员宅基地_vue传递json数据

1、前端export function DelProduct(data) { return request({ url: baseURL+'/basefile/Product/delProduct/', //contentType: 'application/x-www-form-urlencoded;charset=utf-8', dataType:'json', contentType:'application/json;charset=UTF-8',