Spring+Shiro权限管理 (一) 使用MD5+salt(盐)加密、认证

以下是基于spring和Shiro的整合,此篇要点分为两部分:新增用户时,使用MD5和盐加密用户密码;使用shiro认证用户两部分。由于该小项目是完成后总结的,步骤和正常开发可能有些出入,还有该项目异常部分应用了日志记录,具体的日志配置可参考我上篇文章

 

思路:

账户、密码认证:

1、创建Subject主体;

2、将从前端得到的账号,密码存放到Token中;

3、再使用subject.login(token)提交认证;

4、UserRealm的doGetAuthenticationInfo认证方法中,通过从token中拿到账户号,从数据查询出用户的密码和盐;new 一个SimpleAuthenticationInfo ,将账户名、密码、盐(使用ByteSource.Util.bytes()方法转换为ByteSource类型)、当前Realm的name封装进去。

4、返回SimpleAuthenticationInfo对象(该对象会传入凭证匹配器中),凭证匹配器HashedCredentialsMatcher 会根据Token(你前台登录时的明文账号密码,和数据库查询出来的加密后的密码比较)

 

实现步骤:

 

(一)准备工作(基于SSM框架创建项目):

引入maven依赖:

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
        <spring.version>5.1.8.RELEASE</spring.version>
        <jackson.version>2.9.8</jackson.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.1</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.1</version>
        </dependency>
        <!--数据库连接驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>
        <!--数据连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>

        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>javax.servlet.jsp-api</artifactId>
            <version>2.3.1</version>
            <scope>provided</scope>
        </dependency>

        <!--shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>

    </dependencies>

login.jsp(登录页面)

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
    <head>
        <title>登录界面</title>
    </head>
    <body>
        <form action="toHome" method="post">
           账号:<input name="userIdCode" type="text"/>${userName}
           <br/>
           密码:<input name="password" type="password"/>${password}
           <br/>
           <input type="submit" value="登录"/>
        </form>
    </body>
</html>

home.jsp(主页面)

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
    <head>
        <title>主页面</title>
    </head>
    <body>
        <h2>登录成功!</h2>
        <button onclick="window.location.href='toAddUser'">新增用户</button>
    </body>
</html>

addUser.jsp(新增用户界面)

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>新增用户</title>
</head>
<body>
    <form action="addUser" method="post">
        姓名:<input name="name" type="text"><br/>
        密码:<input name="password" type="password"><br/>
        年龄:<input name="age" type="number"><br/>
        性别:<input name="sex" type="radio" value="男" checked>男<input name="sex" type="radio" value="女">女<br/>
        <button type="submit">登录</button>
    </form>
</body>
</html>

showIdCode.jsp(新增成功后的反馈界面)

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>当前用户的员工编号</title>
</head>
    <body>
        <h2>新增员工成功!新增员工编号为${IdCode}!</h2>
        <br/>
        <button onclick="window.location.href='login'">
            确定
        </button>
    </body>
</html>

接着是数据库表结构:

实体类User

package com.xcj.jquery_ajax.entity;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class User {

    //用户编号(账号,自增)
    private Integer idCode;
    private String password;
    private String name;
    private int age;
    private String sex;
    //盐
    private String salt;
    
}

Dao层(UserDao):

package com.xcj.jquery_ajax.dao;

import com.xcj.jquery_ajax.entity.User;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository("UserDao")
public interface UserDao {
    public User findUserByIdCode(@Param("idCode") Integer id_code);
    public void addUser(User user);
}

UserDao.xml

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.4//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.xcj.jquery_ajax.dao.UserDao">

    <resultMap id="resultMap" type="User">
        <id property="idCode" column="id_code" javaType="Integer"/>
        <result property="password" column="password" javaType="String"/>
        <result property="name" column="name" javaType="String"/>
        <result property="age" column="age" javaType="Integer"/>
        <result property="sex" column="sex" javaType="String"/>
        <result property="salt" column="salt" javaType="String"/>
    </resultMap>
    
    <select id="findUserByIdCode" parameterType="Integer" resultMap="resultMap">
        SELECT * FROM user WHERE id_code = #{idCode}
    </select>

    <insert id="addUser" parameterType="User" useGeneratedKeys="true" keyProperty="idCode" keyColumn="idCode">
        INSERT INTO user (password,name,age,sex,salt) VALUES (#{password},#{name},#{age},#{sex},#{salt})
    </insert>

</mapper>

UserService

package com.xcj.jquery_ajax.service;

import com.xcj.jquery_ajax.entity.User;

import java.util.List;

public interface UserService {
    
    /*根据员工编号查询用户数据*/
    public User findUserByIdCode(Integer idCode);
    /*新增员工*/
    public void addUser(User user);
}

UserServiceImp

package com.xcj.jquery_ajax.service.serviceImp;

import com.xcj.jquery_ajax.dao.UserDao;
import com.xcj.jquery_ajax.entity.User;
import com.xcj.jquery_ajax.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service("UserService")
public class UserServiceImp implements UserService {

    @Autowired
    UserDao userDao;

    @Override
    public User findUserByIdCode(Integer idCode) {
        return userDao.findUserByIdCode(idCode);
    }

    @Override
    public void addUser(User user) {
        userDao.addUser(user);
    }
}

UserController

新建用户步骤:

1、新建User对象,设值(自定义随机数生成工具类MyRandomUtil,生成盐;使用SimpleHash类对密码进行加密)

2、调用UserService类新增用户方法。

shiro认证的步骤:

1、创建Subject主体;

2、将从前端得到的账号,密码存放到Token中;

3、再使用subject.login(token)提交认证,可能会发生异常,这里我只处理了两个异常。

package com.xcj.jquery_ajax.controller;

import com.xcj.jquery_ajax.entity.User;
import com.xcj.jquery_ajax.service.UserService;
import com.xcj.jquery_ajax.tool.MyRandomUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

@Controller("UserController")
public class UserController {

    @Autowired
    UserService userService;

    //跳转登录界面
    @RequestMapping("login")
    public ModelAndView login() {
        return new ModelAndView("login");
    }

    //shiro登录认证流程
    @RequestMapping("toHome")
    public ModelAndView toHome(String userIdCode , String password){
        ModelAndView modelAndView = new ModelAndView();
        
        //通过shiro的一个工具类SecurityUtil获取主体subject
        Subject subject = SecurityUtils.getSubject();

        //UsernamePasswordToken用于实现基于用户名/密码主体(Subject)身份认证。
        //UsernamePasswordToken实现了 RememberMeAuthenticationToken 和HostAuthenticationToken,可以实现“记住我”及“主机验证”的支持。
        UsernamePasswordToken token = new UsernamePasswordToken(userIdCode, password);

        try {
            //当调用subject的登入方法时,会跳转到认证的方法上。
            subject.login(token);

        } catch (UnknownAccountException e) {
            e.printStackTrace();
            modelAndView.addObject("userName", "用户id错误!");
            modelAndView.setViewName("login");
            return modelAndView;
        } catch (IncorrectCredentialsException e) {
            e.printStackTrace();
            modelAndView.addObject("password", "用户密码错误!");
            modelAndView.setViewName("login");
            return modelAndView;
        }
        modelAndView.setViewName("home");
        return modelAndView;
    }

    //跳转到新增用户界面
    @RequestMapping("toAddUser")
    public ModelAndView toAddUser(){
        return new ModelAndView("addUser");
    }

    //新增用户处理
    @RequestMapping(value = "addUser",method = RequestMethod.POST)
    public ModelAndView addUser(String name,Integer age,String sex,String password){

        User user = new User();
        user.setName(name);
        user.setAge(age);
        user.setSex(sex);
        //自定义随机数生成工具类,生成盐
        String salt = MyRandomUtil.getRandom();
        user.setSalt(salt);
        //使用SimpleHash类对密码进行加密
        String pwd = new SimpleHash("MD5",password, ByteSource.Util.bytes(salt),1).toHex();
        user.setPassword(pwd);

        userService.addUser(user);

        //idCode的值由MyBatis的useGeneratedKeys属性返回
        Integer idCode = user.getIdCode();

        return new ModelAndView("showIdCode").addObject("IdCode",idCode);   
    }

}

随机数生成工具类MyRandomUtil 

package com.xcj.jquery_ajax.tool;

import java.util.Random;

public class MyRandomUtil {

    public static String getRandom() {
        String str = "";
        char[] ch = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
                'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
                'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
        Random random = new Random();
        for (int i = 0; i < 8; i++) {
            char num = ch[random.nextInt(ch.length)];
            str += num;
        }
        return str;
    }

}

UserRealm(自定义Realm),暂时只涉及认证,授权过程将在后续更新。

认证过程:

1、从token中拿到username,即用户账号(这是我们在controller层传进去的 idCode)。

2、通过username从数据库获取User对象,获取对应密码(数据库存放的密码已经过加密处理)和salt(盐)

3、new 一个SimpleAuthenticationInfo ,将账户名、密码、盐(使用ByteSource.Util.bytes()方法转换为ByteSource类型)、当前Realm的name封装进去。

4、返回SimpleAuthenticationInfo对象(该对象会传入凭证匹配器中)。

package com.xcj.jquery_ajax.realm;

import com.xcj.jquery_ajax.entity.User;
import com.xcj.jquery_ajax.service.UserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

public class UserRealm extends AuthorizingRealm {

    @Autowired
    UserService userService;

    /*授权*/
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    /*认证*/
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

        String username = token.getUsername();

        User user = null;
        if (username != null && !username.equals("")) {
            //通过token中获取到的账号(username)从数据库中拿到user对象
            user = userService.findUserByIdCode(Integer.valueOf(username));
        }

        if (user != null) {
            String password = user.getPassword();
            String salt = user.getSalt();
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, password, ByteSource.Util.bytes(salt), this.getName());
            return simpleAuthenticationInfo;
        }
        return null;
    }
}

applicationContext.xml中,我们将UserRealm的凭证匹配器改为了HashedCredentialsMatcher。它会解析SimpleAuthenticationInfo,帮我们比较账户名、密码是否一致,如果不一致将返回对应Exception。

<?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:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.1.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd">

    <!--开启springmvc注解-->
    <!--自动扫描包下带注解的类,加载为bean-->
    <context:component-scan base-package="com.xcj.jquery_ajax"/>
    <mvc:annotation-driven/>

    <!--spring中导入properties文件-->
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location">
            <value>classpath:applicationContext.properties</value>
        </property>
    </bean>

    <!--使用Druid数据库连接池管理监控数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${spring.datasource.driver}"/>
        <property name="url" value="${spring.datasource.url}"/>
        <property name="username" value="${spring.datasource.username}"/>
        <property name="password" value="${spring.datasource.password}"/>

        <property name="initialSize" value="${spring.datasource.initialSize}"/>
        <property name="minIdle" value="${spring.datasource.minIdle}"/>
        <property name="maxActive" value="${spring.datasource.maxActive}"/>

        <property name="maxWait" value="${spring.datasource.maxWait}"/>
        <property name="timeBetweenEvictionRunsMillis" value="${spring.datasource.timeBetweenEvictionRunsMillis}"/>

        <property name="minEvictableIdleTimeMillis" value="${spring.datasource.minEvictableIdleTimeMillis}"/>
        <property name="testWhileIdle" value="${spring.datasource.testWhileIdle}"/>
        <property name="testOnBorrow" value="${spring.datasource.testOnBorrow}"/>
        <property name="testOnReturn" value="${spring.datasource.testOnReturn}"/>

        <property name="filters" value="${spring.datasource.filters}"/>
        <property name="connectionProperties" value="${spring.datasource.connectionProperties}"/>
        <property name="useGlobalDataSourceStat" value="${spring.datasource.useGlobalDataSourceStat}"/>

    </bean>

    <!--spring和mybatis整合,使用SqlSessionFactoryBean管理数据源等-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="typeAliasesPackage" value="com.xcj.jquery_ajax.entity"/>
    </bean>

    <!--MapperScannerConfigurer会查找类路径下的映射器并自动将它们创建成MapperFactoryBeans,相当于把Dao层托管给spring,需要用时直接注入-->
    <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <property name="basePackage" value="com.xcj.jquery_ajax.dao"/>
    </bean>

    <!--配置视图解析器  -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 返回视图页面的前缀 -->
        <property name="prefix" value="/WEB-INF/view/"></property>
        <!-- 返回页面的后缀 -->
        <property name="suffix" value=".jsp"></property>
    </bean>

    <!--自定义spring的全局异常处理,实现HandlerExceptionResolver接口-->
    <bean class="com.xcj.jquery_ajax.execption.MyExceptionResolver"/>

    <!--Spring MVC的web.xml文件中关于DispatcherServlet拦截url的配置为"/",拦截了所有的请求,同时*.js,*.jpg等静态资源也被拦截了,
        导致运行时跳转后的页面无法加载图片资源,所以需要配置该静态资源-->
    <mvc:resources mapping="css/**" location="css/"/>
    <mvc:resources mapping="js/**" location="js/"/>

    <!--shiro拦截器的bean-->
    <bean name="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>

        <!--设置登入页面-->
        <property name="loginUrl" value="/login"/>
        <!--设置登录成功的跳转页面-->
        <property name="successUrl" value="/toHome"/>
        <!--设置没有权限跳转的页面-->
<!--        <property name="unauthorizedUrl" value="/unauthorized"/>-->

        <!--自定义拦截器-->
        <property name="filters">
            <map>
                <!--退出拦截器-->
                <entry key="logout" value-ref="logoutFilter"/>
            </map>
        </property>
        <!--拦截器链,顺序执行-->
        <property name="filterChainDefinitions">
            <value>
                /login = anon <!--登录界面不拦截-->
                /toHome = anon
                /logout = logout<!--退出-->
                /** = authc<!--必须登录认证成功才能访问-->
            </value>
        </property>

    </bean>

    <!--shiro提供的退出拦截器,redirectUrl对应退出后跳转的地址-->
    <bean name="logoutFilter" class="org.apache.shiro.web.filter.authc.LogoutFilter">
        <property name="redirectUrl" value="/login"/>
    </bean>

    <!--SecurityManager即安全管理器,对全部的subject进行安全管理,它是shiro的核心,负责对所有的subject进行安全管理。-->
    <bean name="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realms">
            <list>
                <ref bean="userRealm"/>
            </list>
        </property>
    </bean>

    <!--自定义realm-->
    <bean name="userRealm" class="com.xcj.jquery_ajax.realm.UserRealm">
        <property name="credentialsMatcher" ref="hashedCredentialsMatcher"/>
    </bean>

    <!--凭证匹配器,用于加密密码和验证密码服务-->
    <bean name="hashedCredentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
        <property name="hashAlgorithmName" value="MD5"/>
        <property name="hashIterations" value="1"/>
    </bean>

    <!--开启shiro的注解-->
    <bean id="advisorAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
        <property name="proxyTargetClass" value="true"></property>
    </bean>
</beans>

 applicationContext.properties

#############################
## 数据库源 ##
#spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/jquery_ajax?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT
spring.datasource.username=root
spring.datasource.password=123456

# 下面为连接池的补充设置,应用到上面所有数据源中
# 初始化大小,最小,最大(连接数 = ((核心数 * 2) + 有效磁盘数))
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=10

# 配置获取连接等待超时的时间
spring.datasource.maxWait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=60000

# 配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.minEvictableIdleTimeMillis=300000
#用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
spring.datasource.validationQuery=SELECT 1 FROM DUAL
#建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
spring.datasource.testWhileIdle=true
#申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
spring.datasource.testOnBorrow=false
#归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
spring.datasource.testOnReturn=false

# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
spring.datasource.filters=stat,wall,slf4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合并多个DruidDataSource的监控数据
spring.datasource.useGlobalDataSourceStat=true

web.xml

applicationContext.xml中shiro的拦截器bean的name属性必须与web.xml的filter的name属性相同(配置了targetBeanName属性,则以targetBeanName为主)。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <display-name>jquery_ajax</display-name>


    <!-- 配置前端控制器DispatcherServlet -->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--springmvc.xml 是自己创建的SpringMVC全局配置文件,用contextConfigLocation作为参数名来加载
            如果不配置 contextConfigLocation,那么默认加载的是/WEB-INF/servlet名称-servlet.xml,在这里也就是 springmvc-servlet.xml      -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!--第一种配置:*.do,还可以写*.action等等,表示以.do结尾的或者以.action结尾的URL都由前端控制器DispatcherServlet来解析
            第二种配置:/,所有访问的 URL 都由DispatcherServlet来解析,但是这里最好配置静态文件不由DispatcherServlet来解析
            错误配置:/*,注意这里是不能这样配置的,应为如果这样写,最后转发到 jsp 页面的时候,仍然会由DispatcherServlet进行解析,
                        而这时候会找不到对应的Handler,从而报错!!!          -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!--设置字符编码过滤器-->
    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    !--配置shiro过滤器,DelegatingFilterProxy通过代理模式将spring容器中的bean和filter关联起来-->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <!-- 设置true由servlet容器控制filter的生命周期 -->
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
        <!--设置spring容器filter的bean id,如果不设置则找与filter-name一致的bean-->
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>shiroFilter</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    

    <!-- druid 监控过滤器 -->
    <filter>
        <filter-name>druidWebStatFilter</filter-name>
        <filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class>
        <init-param>
            <param-name>exclusions</param-name>
            <param-value>/assets/*,*.css,*.js,*.gif,*.jpg,*.png,*.ico,*.eot,*.svg,*.ttf,*.woff,*.jsp,*.tpl,/druid/*
            </param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>druidWebStatFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <servlet>
        <servlet-name>druidStatView</servlet-name>
        <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
        <init-param>
            <!-- 用户名 -->
            <param-name>loginUsername</param-name>
            <param-value>admin</param-value>
        </init-param>
        <init-param>
            <!-- 密码 -->
            <param-name>loginPassword</param-name>
            <param-value>123456</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>druidStatView</servlet-name>
        <url-pattern>/druid/*</url-pattern>
    </servlet-mapping>


</web-app>

因为我们在applicationContext.xml中设置的统一异常页面处理:

<bean class="com.xcj.jquery_ajax.execption.MyExceptionResolver"/>

所以,我们来实现下MyExceptionResolver

package com.xcj.jquery_ajax.execption;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
public class MyExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        //e.printStackTrace();
        log.error("异常原因:{}  ;  异常信息:{}",e.getCause(),e.getMessage());

        ModelAndView mv = new ModelAndView("error");
        mv.addObject("exception", e.toString().replaceAll("\n", "<br/>"));
        return mv;
    }
}

error.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>异常</title>
</head>
<h2>请联系管理员</h2><br/>
${exception}
</body>
</html>

以上,认证过程就搞定了。

(二)、演示效果

总结,此篇中的Shiro认证过程不算复杂,我在代码中做了详细的注释,应该也不难看懂。

如果你是刚学Shiro认证的话,不凡模仿下我这个例子。

若发现我有错误的话,欢迎指正,我们互相学习,拜拜!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值