Shiro详解

DelegatingFilterProxy解析

        在Spring配置文件中配置shiroFilter时,配置bean的id属性需要和DelegatingFilterProxy的<filter-name>一致。如果不一致,控制台将打印以下错误信息。

10-Oct-2019 18:38:14.669 信息 [RMI TCP Connection(3)-127.0.0.1] org.apache.catalina.core.ApplicationContext.log No Spring WebApplicationInitializer types detected on classpath
10-Oct-2019 18:38:14.903 信息 [RMI TCP Connection(3)-127.0.0.1] org.apache.catalina.core.ApplicationContext.log Initializing Spring root WebApplicationContext
10-Oct-2019 18:38:19.347 严重 [RMI TCP Connection(3)-127.0.0.1] org.apache.catalina.core.StandardContext.filterStart Exception starting filter [shiroFilter]
 org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'shiroFilter' available
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:775)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1221)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:294)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:204)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1111)
	at org.springframework.web.filter.DelegatingFilterProxy.initDelegate(DelegatingFilterProxy.java:337)
	at org.springframework.web.filter.DelegatingFilterProxy.initFilterBean(DelegatingFilterProxy.java:242)
	at org.springframework.web.filter.GenericFilterBean.init(GenericFilterBean.java:238)
	at org.apache.catalina.core.ApplicationFilterConfig.initFilter(ApplicationFilterConfig.java:285)
	at org.apache.catalina.core.ApplicationFilterConfig.getFilter(ApplicationFilterConfig.java:266)
	at org.apache.catalina.core.ApplicationFilterConfig.<init>(ApplicationFilterConfig.java:108)
	at org.apache.catalina.core.StandardContext.filterStart(StandardContext.java:4637)
	at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5282)
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
	at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:754)
	at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:730)
	at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:734)
	at org.apache.catalina.startup.HostConfig.manageApp(HostConfig.java:1736)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:300)
	at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
	at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801)
	at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:483)
	at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:432)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:300)
	at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
	at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801)
	at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.java:1468)
	at javax.management.remote.rmi.RMIConnectionImpl.access$300(RMIConnectionImpl.java:76)
	at javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.java:1309)
	at javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.java:1401)
	at javax.management.remote.rmi.RMIConnectionImpl.invoke(RMIConnectionImpl.java:829)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:357)
	at sun.rmi.transport.Transport$1.run(Transport.java:200)
	at sun.rmi.transport.Transport$1.run(Transport.java:197)
	at java.security.AccessController.doPrivileged(Native Method)
	at sun.rmi.transport.Transport.serviceCall(Transport.java:196)
	at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:573)
	at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:834)
	at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:688)
	at java.security.AccessController.doPrivileged(Native Method)
	at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:687)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

抛出这个错误的原因是shiro会到IOC容器中查找<filter-name>名字对应的filter bean。
在DelegatingFilterProxy类中包含一个initDelegate方法。

protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
        String targetBeanName = this.getTargetBeanName();
        Assert.state(targetBeanName != null, "No target bean name set");
        Filter delegate = (Filter)wac.getBean(targetBeanName, Filter.class);
        if (this.isTargetFilterLifecycle()) {
            delegate.init(this.getFilterConfig());
        }

        return delegate;
    }

Debug程序查看targetBeanName的值。
在这里插入图片描述
DelegatingFilterProxy实际上是Filter的一个代理对象,默认情况下,Spring会到IOC容器中找和<filter-name>对应的filter bean。也可以通过targetBeanName初始化参数来配置filter bean的id。
在这里插入图片描述
在这里插入图片描述
成功启动
在这里插入图片描述
在这里插入图片描述

filterChainDefinitions配置细节

anon:拦截器表示匿名访问(即不需要认证便可访问)
authc:拦截器表示需要身份认证通过后才可访问。
authcBasic:表示需要通过httpBasic认证。
logout:任何Session都将失效,任何身份都将失去关联
ssl:表示安全的http请求,协议为https。
url模式使用Ant风格模式
?:匹配一个字符。
*:匹配零个活多个字符串。
**:匹配路径中的零个或多个路径。
url权限采取第一次匹配优先原则

Shiro认证

步骤
1.获取当前subject,SecurityUtils.getSubject()。
2.测试当前用户是否被认证。subject的isAuthenticated()。
3.若没有认证,则将用户名和密码封装为UsernamePasswordToken对象。
4.执行登录。Subject的login()方法。
5.自定义Realm方法,从数据库中取出数据返回给Shiro。
(继承org.apache.shiro.realm包下AuthenticatingRealm类,实现其中的doGetAuthenticationInfo()方法。
6.由shiro完成密码的比对。
实现
创建表单页面

<%--
  Created by IntelliJ IDEA.
  User: admin
  Date: 2019/10/9
  Time: 21:20
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
      <h4>Login Page</h4>

      <form action="shiro/login" method="POST">
          username:<input type="text" name="username" />
          <br><br>
          password:<input type="password" name="password" />
          <br><br>
          <input type="submit" value="Submit" />
      </form>
</body>
</html>

创建处理器

package com.fengxunxinxi.shiro.controller;

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

/**
 * ShiroController class
 * @author mofengyu
 *
 * @date 2019/10/11
 */


@Controller
@RequestMapping("/shiro")
public class ShiroController {

    public String login(@RequestParam(value = "username") String username,
                        @RequestParam(value = "password") String password){

        /**
         * 获取当前的Subject
         */
        Subject subject= SecurityUtils.getSubject();

        /**
         * 判断当前的Subject是否认证。
         */
        if(!subject.isAuthenticated()){
            /**
             * 如果没有认证,将用户名和密码封装成UsernamePasswordToken对象。
             */
            UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken(username,password);
            /**
             * 设置记住我
             */
            usernamePasswordToken.setRememberMe(true);
            try {
                /**
                 * 执行认证。
                 */
                subject.login(usernamePasswordToken);
            }catch (AuthenticationException e){
                 System.out.println("认证失败:"+e.getMessage());
            }
        }
        return "redirect:/list.jsp";
    }
}

创建ShiroRealm
        ShiroRealm继承AuthenticatingRealm抽象类,实现doGetAuthenticationInfo()方法,用于认证过程中的安全数据访问。

package com.fengxunxinxi.shiro.realm;


import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.realm.Realm;

/**
 * ShiroRealm class
 * @author mofengyu
 *
 * @date 2019/10/9
 */

public class ShiroRealm extends AuthenticatingRealm {

    private static String userInfo="admin";
    private static String info="unknown";
    private static String exception="monster";
    private static String password="123456";

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        /**
         * 把AuthenticationToken转换为UsernamePasswordToken
         */
        UsernamePasswordToken usernamePasswordToken=(UsernamePasswordToken)authenticationToken;
        /**
         * 从UsernamePasswordToken中获取username
         */
        String username=usernamePasswordToken.getUsername();
        /**
         * 查询数据库,查询该用户名的相关信息。
         */

        /**
         * 若用户不存在。抛出UnknownAccountException异常。
         */
        if(info.equals(username)){
            throw new UnknownAccountException("用户不存在");
        }
        /**
         * 根据获取的用户信息决定是否抛出AuthenticationException
         */
         if(exception.equals(username)){
             throw new AuthenticationException("用户被锁定");
         }
        /**
         * 根据用户信息来构建AuthenticationInfo对象并返回。
         * principal认证的实体信息,可以是username,也可以是对应的用户实体类对象。
         */
        Object principal=userInfo;
        /**
         * credentials,密码
         */
        Object credentials=password;
        /**
         * realmName当前realm对象的name,调用父类的getName()方法。
         */
        String realmName=getName();
        SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(principal,credentials,realmName);
        return authenticationInfo;
    }
}

测试认证
在这里插入图片描述
在这里插入图片描述
添加清除认证信息

<%--
  Created by IntelliJ IDEA.
  User: admin
  Date: 2019/10/9
  Time: 21:20
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
      <h4>list Page</h4>
      <a href="shiro/logout">Logout</a>
</body>
</html>
         <property name="filterChainDefinitions">
            <value>
                /login.jsp=anon
                /shiro/login=anon
                /shiro/logout=logout
                /** =authc
            </value>
        </property>

Shiro密码比对

Debug运行测试程序

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.apache.shiro.authc.credential;

import java.security.MessageDigest;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.codec.CodecSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SimpleCredentialsMatcher extends CodecSupport implements CredentialsMatcher {
    private static final Logger log = LoggerFactory.getLogger(SimpleCredentialsMatcher.class);

    public SimpleCredentialsMatcher() {
    }

    protected Object getCredentials(AuthenticationToken token) {
        return token.getCredentials();
    }

    protected Object getCredentials(AuthenticationInfo info) {
        return info.getCredentials();
    }

    protected boolean equals(Object tokenCredentials, Object accountCredentials) {
        if (log.isDebugEnabled()) {
            log.debug("Performing credentials equality check for tokenCredentials of type [" + tokenCredentials.getClass().getName() + " and accountCredentials of type [" + accountCredentials.getClass().getName() + "]");
        }

        if (this.isByteSource(tokenCredentials) && this.isByteSource(accountCredentials)) {
            if (log.isDebugEnabled()) {
                log.debug("Both credentials arguments can be easily converted to byte arrays.  Performing array equals comparison");
            }

            byte[] tokenBytes = this.toBytes(tokenCredentials);
            byte[] accountBytes = this.toBytes(accountCredentials);
            return MessageDigest.isEqual(tokenBytes, accountBytes);
        } else {
            return accountCredentials.equals(tokenCredentials);
        }
    }

    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        Object tokenCredentials = this.getCredentials(token);
        Object accountCredentials = this.getCredentials(info);
        return this.equals(tokenCredentials, accountCredentials);
    }
}

SimpleCredentialsMatcher类中含有一个doCredentialsMatch()方法。
在这里插入图片描述
可以看到从表单输入的数据和从数据库得到的数据。点击调试的上一步。
在这里插入图片描述
程序跳转到了AuthenticatingRealm类的assertCredentialsMatch()方法。在这个方法中通过CredentialsMatcher来实现密码比对。

protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
        CredentialsMatcher cm = this.getCredentialsMatcher();
        if (cm != null) {
            if (!cm.doCredentialsMatch(token, info)) {
                String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
                throw new IncorrectCredentialsException(msg);
            }
        } else {
            throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify credentials during authentication.  If you do not wish for credentials to be examined, you can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
        }
    }

密码加密

对处理器中获取的密码加密需配置相关的credentialsMatcher。

    <bean id="jdbcRealm" class="com.fengxunxinxi.shiro.realm.ShiroRealm">
        <property name="credentialsMatcher">
            <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                <property name="hashAlgorithmName" value="MD5"></property>
                <property name="hashIterations" value="1024"></property>
            </bean>
        </property>
    </bean>

保存加密的数据到数据库中可以通过SimpleHash()。

 public static void main(String[] args) {
        String hashAlgorithmName="MD5";
        Object credentials="123456";
        Object salt=null;
        int hashIterations=1024;
        Object result=new SimpleHash(hashAlgorithmName,credentials,salt,hashIterations);
        System.out.println(result);
    }

在这里插入图片描述

盐值加密

对于相同的密码可以通过盐值加密成不同的字符串。

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        /**
         * 把AuthenticationToken转换为UsernamePasswordToken
         */
        UsernamePasswordToken usernamePasswordToken=(UsernamePasswordToken)authenticationToken;
        /**
         * 从UsernamePasswordToken中获取username
         */
        String username=usernamePasswordToken.getUsername();
        /**
         * 查询数据库,查询该用户名的相关信息。
         */

        /**
         * 若用户不存在。抛出UnknownAccountException异常。
         */
        if(info.equals(username)){
            throw new UnknownAccountException("用户不存在");
        }
        /**
         * 根据获取的用户信息决定是否抛出AuthenticationException
         */
         if(exception.equals(username)){
             throw new AuthenticationException("用户被锁定");
         }
        /**
         * 根据用户信息来构建AuthenticationInfo对象并返回。
         * principal认证的实体信息,可以是username,也可以是对应的用户实体类对象。
         */
        Object principal=userInfo;
        /**
         * credentials,密码
         */
        Object credentials=password;
        /**
         * realmName当前realm对象的name,调用父类的getName()方法。
         */
        String realmName=getName();
        /**
         * 加密的盐值。
         */
        ByteSource credentialsSalt=ByteSource.Util.bytes(userInfo);
        SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(principal,credentials,credentialsSalt,realmName);
        return authenticationInfo;
    }

对于doGetAuthenticationInf()方法返回的AuthenticationInfo对象需要带上盐值,盐值是一个ByteSource对象。
使用用户名作为盐值加密。

public static void main(String[] args) {
        String hashAlgorithmName="MD5";
        Object credentials="123456";
        Object salt= ByteSource.Util.bytes("admin");
        int hashIterations=1024;
        Object result=new SimpleHash(hashAlgorithmName,credentials,salt,hashIterations);
        System.out.println(result);
    }

在这里插入图片描述
使用MD5盐值加密后的密码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值