使用Shiro完成用户认证
环境:MyEclipse2017、JDK1.7、Spring4.1
步骤:
- 创建WEB项目添加Spring4.1支持并配置SpringMVC
- 引入Shiro相关jar包
- 编写Realm
- 配置Shiro前端过滤器
- 配置安全管理器及Shiro过滤器
- 编写Controller进行测试
步骤一:创建WEB项目添加Spring4.1支持并配置SpringMVC
此处大约省略了有一万字。。。。。。
步骤二:引入Shiro相关jar包
shiro-all-1.2.3.jar
slf4j-api-1.7.25.jar
slf4j-log4j12-1.7.25.jar
步骤三:编写Realm
package com.hcq.bean;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.realm.AuthenticatingRealm;
//Realm:访问数据库
public class Realm extends AuthenticatingRealm{
/*
* 1.doGetAuthenticationInfo方法:获取认证信息,进行认证
*
* 2.AuthenticationInfo接口:可以使用SimpleAuthenticationInfo实现类,封装数据库中用户信息
*
* 3.AuthenticationToken参数:封装用户输入的认证信息
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken Token=(UsernamePasswordToken)token;//认证信息
String principal=Token.getUsername();
String realmName=this.getName();
//查询一波数据库
try {
Class.forName("com.mysql.jdbc.Driver");
Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/first","root", "root");
PreparedStatement pstm=conn.prepareStatement("select password from user where name=?");
pstm.setString(1, principal);
ResultSet rs=pstm.executeQuery();
if(rs.next()){
String credentials=rs.getString(1);
//将数据库中查询到的info返回用于匹配认证信息。参数:(用户名,用户密码,Realm名称)
SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(principal, credentials, realmName);
return info;
}else{
throw new RuntimeException("用户不存在");
}
} catch (Exception e) {
System.out.println("数据库链接异常");
e.printStackTrace();
}
return null;
}
}
步骤四:配置Shiro前端过滤器
此处为了便于管理,我将Shiro同SpringMVC一样单独配置在一个xml中,这个xml名为“shiro-config.xml”,并且在第12行将此配置文件注册到了上下文中(也可以直接在Spring容器中进行配置)。由下面的配置信息可知Shiro的前端控制器是一个过滤器org.springframework.web.filter.DelegatingFilterProxy
web.xml配置如下
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" 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>ShiroTest</display-name>
<!-- Spring IOC容器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:applicationContext.xml
classpath:shiro-config.xml
</param-value>
</context-param>
<!-- Spring MVC -->
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--Shiro-->
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
步骤五:配置安全管理器及Shiro过滤器
创建一个配置信息如下的shiro-config.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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
">
<!-- 配置安全管理器 -->
<bean class="org.apache.shiro.web.mgt.DefaultWebSecurityManager" id="securityManager">
<property name="realm" ref="AuRealm"/>
</bean>
<!-- Realm -->
<bean id="AuRealm" class="com.hcq.bean.Realm"></bean>
<!-- 配置Shiro过滤器 -->
<bean id="ShiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/index.jsp"/><!--未登录时跳转的页面-->
<property name="unauthorizedUrl" value="/Error.jsp"/><!--权限不足所跳转的页面-->
<!-- 定义过滤链 -->
<property name="filterChainDefinitions">
<value>
/index.jsp=anon<!--可以匿名访问-->
/hello.action=anon
/logout=logout<!--注销-->
/**=authc<!--需要进行过滤的请求-->
</value>
</property>
</bean>
</beans>
步骤六:编写Controller进行测试
package com.hcq.controller;
@Controller
public class Test {
@RequestMapping("hello")
public String hello(String name,String password){
Subject user=SecurityUtils.getSubject();//封装用户信息
if(!user.isAuthenticated()){//判断用户认证状态
UsernamePasswordToken token = new UsernamePasswordToken(name, password);//封装认证信息
try {
user.login(token);//执行认证
System.out.println("认证成功!");
} catch (Exception e) {
System.out.println("认证失败!");
}
}else{
System.out.println("用户已认证!");
}
return "Home";
}
}
Shiro加密认证
在数据中,存储的都是经过加密后的用户密码,如果Shiro认证不加密,那么用户认证时所输入的明文将会与数据库中的不匹配,所以我们需要把用户输入的明文加密后再与数据库进行匹配。Shiro中加密认证非常简单,只需要在Realm中将Realm添加一个加密器的属性即可。
更改后的Realm配置如下
<bean id="AuRealm" class="com.hcq.bean.Realm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="MD5"/><!--加密方式-->
<property name="hashIterations" value="1024"/><!--加密次数-->
</bean>
</property>
</bean>
添加加密器后,当用户调用subject.login方法时,Shiro底层会将UsernamePasswordToken中用户输入的明文进行加密,然后在数据库中进行匹配,匹配失败会产生对应异常。
盐值加密
为了进一步提高数据的安全性,在Shiro中可以通过盐值加密,对相同密码而产生唯一的密文。就像现实生活中的西红柿炒鸡蛋,食材都是西红柿+鸡蛋却可以因为调料的不同而产生不同的味道。
//普通加密认证
SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(principal, credentials, realmName);
//盐值加密
ByteSource salt=ByteSource.Util.bytes(principal);//盐:principal
info=new SimpleAuthenticationInfo(principal, credentials, salt, realmName);
由上面的代码段可以看到,盐值加密认证就比普通加密认证多了一个ByteSource参数,而这个参数一般为数据库中的主键。总结一下就是:通过主键的唯一性从而产生不同的密文。