Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序
从外部查看shiro框架
应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject
api | 说明 |
---|---|
Subject | 主体,代表当前’用户’。这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者 |
Shiro SecurityManager | 安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,可以把它看成DispatcherServlet前端控制器 |
Realm | 域,Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。 |
内部结构框架
组件 | 说明 |
---|---|
Subject | 主体,可以看到主体可以是任何可以与应用交互的“用户” |
SecurityManager | 相当于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;是Shiro的心脏; 所有具体的交互都通过SecurityManager进行控制; 它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。 |
Authenticator | 认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现; 其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了 |
Authorizer | 授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作; 即控制着用户能访问应用中的哪些功能 |
Realm | 可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的; 可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;由用户提供; 注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储; 所以我们一般在应用中都需要实现自己的Realm |
SessionManager | Session需要有人去管理它的生命周期,这个组件就是SessionManager; 而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境、EJB等环境; Shiro就抽象了一个自己的Session来管理主体与应用之间交互的数据; 这样的话,比如我们在Web环境用,刚开始是一台Web服务器,接着又上了台EJB服务器; 这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到Memcached服务器) |
SessionDAO | 数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,通过如JDBC写到数据库; 比如想把Session放到Memcached中,可以实现自己的Memcached SessionDAO; 另外SessionDAO中可以使用Cache进行缓存,以提高性能 |
CacheManager | 缓存控制器,来管理如用户、角色、权限等的缓存的; 因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能 |
Cryptography | 密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密的 |
单词 | 说明 |
---|---|
Subject | Subject |
Security | 安全 |
Realm | 领域、范围 |
Autherticator | 认证器 |
Authentication | 认证 |
Authorizer | 授权器 |
Authorization | Authorization |
Cryptography | 密码、加密 |
Credential | 证书、凭证 密码 |
Matcher | 匹配器 |
Principal | 身份 |
Shiro中的shiro.ini说明
shiro.ini放置在classpath路径下shiro会自动查找。
ini配置文件中有四大主要配置类
1.main
2.users
3.roles
4.urls
main
提供了对根对象securityManager及其依赖对象的配置
#创建对象
securityManager=org.apache.shiro.mgt.DefaultSecurityManager
其构造器必须是public空参构造器,通过反射创建相应的实例。
对象名=全限定类名 相对于调用public无参构造器创建对象
对象名.属性名=值 相当于调用setter方法设置常量值
对象名.属性名=$对象引用 相当于调用setter方法设置对象引用
users
提供了对用户/密码及其角色的配置,用户名=密码,角色1,角色2
username=password,role1,role2
例如:配置用户名/密码及其角色,格式:“用户名=密码,角色1,角色2”,角色部分可省略。如:
[users]
zhang=123,role1,role2
wang=123
roles
提供了角色及权限之间关系的配置,角色=权限1,权限2 role1 = permission1 , permission2
例如:配置角色及权限之间的关系,格式:“角色=权限1,权限2”;如:
[roles]
role1=user:create,user:update
role2=*
如果只有角色没有对应的权限,可以不配roles
urls
用于web,提供了对web url拦截相关的配置,url=拦截器[参数],拦截器
/index.html = anon
/admin/** = authc, roles[admin],perms["permission1"]
第一个案例
添加对应的依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.6.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
添加shiro.ini文件
[users]
root = 1234
# 账号为root 密码是 1234
认证操作
public static void main(String[] args) {
//需要验证的账号密码
String userName = "root";
String password = "1234";
//1.获取SecurityManager工厂对象
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro3.ini");
//2.通过Factory对象获取SecurityManager对象
SecurityManager securityManager = factory.getInstance();
//3.将SecurityManager对象添加到SecurityUtil(当前运行环境)中
SecurityUtils.setSecurityManager(securityManager);
//4.获取Subject对象 类似于Mybatis中的SqlSession
Subject subject = SecurityUtils.getSubject();
AuthenticationToken token = new UsernamePasswordToken(userName,password);
//登陆认证
try {
subject.login(token);
System.out.println("登陆成功");
} catch (UnknownAccountException e) {
System.out.println("账号出错...");
} catch(IncorrectCredentialsException e){
System.out.println("密码出错...");
}
//获取登陆状态
System.out.println(subject.isAuthenticated());
}
流程描述
源码流程分析
1.创建token令牌,token中有用户提交的认证信息即账号和密码
2.执行subject.login(token),最终由securityManager通过Authenticator进行认证
3.Authenticator的实现ModularRealmAuthenticator调用realm从ini配置文件取用户真实的账号和密码,这里使用的是IniRealm(shiro自带)
4.IniRealm先根据token中的账号去ini中找该账号,如果找不到则给ModularRealmAuthenticator返回null,如果找到则匹配密码,匹配密码成功则认证通过
自定义Realm的实现
创建自定义Realmjava类
创建一个java文件继承AuthorizingRealm类,重写两个抽象方法
doGetAuthenticationInfo 完成账号认证的方法
doGetAuthorizationInfo 完成用户授权的方法
//自定义Realm 继承AuthorizingRealm父类
public class MyRealm extends AuthorizingRealm{
/**
* 认证的方法
* 只做账号认证, 同时查询出对应的账号的密码 ,会封装到AuthenticationInfo对象
* 密码验证是框架完成的
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
System.out.println("需要认证的账号: "+upToken.getUsername());
//根据账号去数据库中查
if(!"root".equals(upToken.getUsername())){
return null;
}
System.out.println(upToken.getPassword());
//假设查询的密码是1234
String password = "1234";
//认证对象(可以是账号或User对象) 密码 realm名称 自定义的
return new SimpleAuthenticationInfo(upToken.getUsername(),password,"MyRealm");
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// TODO Auto-generated method stub
return null;
}
}
配置ini.xml文件
[main]
#自定义 realm
customRealm=com.i.realm.MyRealm
#将realm设置到securityManager
securityManager.realms=$customRealm
测试代码和上面一样
加密
加密,是以某种特殊的算法改变原有的信息数据,使得未授权的用户即使获得了已加密的信息,但因不知解密的方法,仍然无法了解信息的内容
概念
数据加密的基本过程就是对原来为明文的文件或数据按某种算法进行处理,使其成为不可读的一段代码,通常称为"密文",使其只能在输入相应的密钥之后才能显示出本来内容,通过这样的途径来达到保护数据不被非法人窃取、阅读的目的。该过程的逆过程为解密
,即将该编码信息转化为其原来数据的过程。
加密分类
对称加密
双方使用的同一个密钥,既可以加密又可以解密,这种加密方法称为对称加密,也称为单密钥加密
非对称加密
一对密钥由公钥和私钥组成(可以使用很多对密钥)。
私钥解密公钥加密数据,公钥解密私钥加密数据(私钥公钥可以互相加密解密)。
加密算法分类
单向加密
单向加密是 不可逆 的,也就是只能加密,不能解密。
通常用来传输类似用户名和密码,直接将加密后的数据提交到后台,因为后台不需要知道用户名和密码,可以直接将收到的加密后的数据存储到数据库
双向加密
通常分为对称性加密算法和非对称性加密算法,对于对称性加密算法,
信息接收双方都需事先知道密匙和加解密算法且其密匙是相同的,之后便是对数据进行 加解密了。
非对称算法与之不同,发送双方A,B事先均生成一堆密匙,
然后A将自己的公有密匙发送给B,B将自己的公有密匙发送给A,如果A要给B发送消息,则先需要用B的公有密匙进行消息加密,
然后发送给B端,此时B端再用自己的私有密匙进行消息解密,B向A发送消息时为同样的道理。
;
;
算法 | 描述 |
---|---|
DES(Data Encryption Standard) | 数据加密标准,速度较快,适用于加密大量数据的场合 |
3DES(Triple DES) | 是基于DES,对一块数据用三个不同的密钥进行三次加密,强度更高 |
RC2和 RC4 | 用变长密钥对大量数据进行加密,比 DES 快 |
IDEA(International Data Encryption Algorithm) | 国际数据加密算法:使用 128 位密钥提供非常强的安全性 |
RSA | 由 RSA 公司发明,是一个支持变长密钥的公共密钥算法,需要加密的文件块的长度也是可变的 |
DSA(Digital Signature Algorithm) | 数字签名算法,是一种标准的 DSS(数字签名标准) |
AES(Advanced Encryption Standard) | 高级加密标准,是下一代的加密算法标准,速度快,安全级别高,目前 AES 标准的一个实现是 Rijndael 算法 |
BLOWFISH | 它使用变长的密钥,长度可达448位,运行速度很快 |
MD5 (Message-Digest Algorithm) | 消息摘要算法,一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致 |
MD5的使用
public static void main(String[] args) {
//对单个信息加密
Md5Hash md5 = new Md5Hash("1234");
System.out.println(md5);
//加密添加盐值 增大解密难度
md5 = new Md5Hash("1234","iii");
System.out.println(md5);
//明文 盐值 迭代次数
md5 = new Md5Hash("1234","iii",1024);
System.out.println(md5);
输出结果
81dc9bdb52d04dc20036dbd8313ed055
0c0f795c9c79e9729a804025eae59df6
b3f8aa64ef17d41dff1e5bbd7e6d5766
Sha1Hash方式
40位
//Sha1Hash方式
Sha1Hash s = new Sha1Hash("1234");
System.out.println(s);
//盐值
s = new Sha1Hash("1234","iii");
System.out.println(s);
//明文 盐值 迭代次数
s = new Sha1Hash("1234","iii",1024);
System.out.println(s);
输出结果
7110eda4d09e062aa5e4a390b0a572ac0d2c0220
b157403f6da40398a2711dbd194cd92a4377437a
4de1f44e9cc52ab105336e67a9c9943f5a8c43bf
加盐的原理:
给原文加入随机数生成新的MD5的值
盐值的作用
使用MD5存在一个问题,相同的password生成的hash值是相同的,如果两个用户设置了相同的密码,那么数据库中会存储两个相同的值,这是极不安全的,加Salt可以在一定程度上解决这一问题,所谓的加Salt方法,就是加点‘佐料’。其基本想法是这样的,当用户首次提供密码时(通常是注册时)由系统自动往这个密码里撒一些‘佐料’,然后在散列,而当用户登录时,系统为用户提供的代码上撒上相同的‘佐料’,然后散列,再比较散列值,来确定密码是否正确。
shiro中使用MD5加密
认证方法中修改
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
System.out.println("需要认证的账号: "+upToken.getUsername());
//根据账号去数据库中查
if(!"root".equals(upToken.getUsername())){
return null;
}
System.out.println(upToken.getPassword());
//假设查询的密码是1234
String password = "123456789";
//1234 根据 盐值 iii 加密获取的密文
String password2 = "0c0f795c9c79e9729a804025eae59df6";
//认证对象(可以是账号或User对象) 密码 realm名称 自定义的
//return new SimpleAuthenticationInfo(upToken.getUsername(),password,"MyRealm");
return new SimpleAuthenticationInfo(upToken.getUsername(), password2, new SimpleByteSource("iii"), "MyRealm");
}
ini.xml文件修改
[main]
#定义凭证匹配器
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#散列算法
credentialsMatcher.hashAlgorithmName=md5
#散列次数
credentialsMatcher.hashIterations=1
#将凭证匹配器设置到realm
customRealm=com.i.realm.MyRealm
customRealm.credentialsMatcher=$credentialsMatcher
securityManager.realms=$customRealm
授权操作
授权,又称作为访问控制,是对资源的访问管理的过程,即对于认证通过的用户,授予他可以访问某些资源的权限
授权的方式
shiro支持三种方式的授权:
代码触发
通过写if/else 授权代码块完成
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
//有权限
} else {
//无权限
}
注解触发
通过在执行的Java方法上放置相应的注解完成
@RequiresRoles("admin")
public void hello() {
//有权限
}
标签触发
在JSP/GSP 页面通过相应的标签完成
<shiro:hasRole name="admin">
<!— 有权限—>
</shiro:hasRole>
授权流程图
简单授权实现
在ini.xml文件中设置
[users]
#用户zhang的密码是123,此用户具有role1和role2两个角色
zhang=1234,role1,role2
wang=1234,role2
[roles]
#角色role1对资源user拥有create、update权限
role1=user:create,user:update
#角色role2对资源user拥有create、delete权限
role2=user:create,user:delete
#角色role3对资源user拥有create权限
role3=user:create
验证角色和权限
public static void main(String[] args) {
//需要验证的账号密码
String userName = "zhang";
String password = "1234";
//1.获取SecurityManager工厂对象
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//2.通过Factory对象获取SecurityManager对象
SecurityManager securityManager = factory.getInstance();
//3.将SecurityManager对象添加到SecurityUtil(当前运行环境)中
SecurityUtils.setSecurityManager(securityManager);
//4.获取Subject对象 类似于Mybatis中的SqlSession
Subject subject = SecurityUtils.getSubject();
AuthenticationToken token = new UsernamePasswordToken(userName,password);
//登陆认证
try {
subject.login(token);
System.out.println("登陆成功");
} catch (UnknownAccountException e) {
System.out.println("账号出错...");
} catch(IncorrectCredentialsException e){
System.out.println("密码出错...");
}
//认证成功才有授权
//权限的校验
System.out.println("具有role1角色? "+subject.hasRole("role1"));
System.out.println("具有role1角色? "+subject.hasRole("role3"));
//hasAllRoles 必须包含所有的角色信息
System.out.println(subject.hasAllRoles(Arrays.asList("role1","role2","role3")));
//hasRoles
boolean[] har = subject.hasRoles(Arrays.asList("role1","role2","role3"));
for (boolean b : har) {
System.out.println(b);
}
//以check 开头的方法都是通过exection 来给出结果 如果没有报异常 说明具有
//检查是否存在该角色和权限,如果不存在则会抛异常
//subject.checkRole("role1");
System.out.println("判断具体的权限");
System.out.println("是否有创建的权限: "+subject.isPermitted("user:create"));
System.out.println("是否有删除体的权限: "+subject.isPermitted("user:delete"));
}
输出结果:
自定义Realm授权
重写授权的方法
/**
* 授权的方法
* principals 认证的身份类型和上面 doGetAuthenticationInfo 方法返回
* SimpleAuthenticationInfo 对象的第一个参数类型有关
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String userName = (String) principals.getPrimaryPrincipal();
System.out.println("授权的账号: "+userName);
//可以根据登陆的账号信息 去数据库中查询出对应的所有角色和权限
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRole("role1");
info.addRole("role2");
info.addStringPermission("user:create");
return info;
}
测试方法和之前一样
public static void main(String[] args) {
//需要验证的账号密码
String userName = "root";
String password = "1234";
//1.获取SecurityManager工厂对象
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro2.ini");
//2.通过Factory对象获取SecurityManager对象
SecurityManager securityManager = factory.getInstance();
//3.将SecurityManager对象添加到SecurityUtil(当前运行环境)中
SecurityUtils.setSecurityManager(securityManager);
//4.获取Subject对象 类似于Mybatis中的SqlSession
Subject subject = SecurityUtils.getSubject();
AuthenticationToken token = new UsernamePasswordToken(userName,password);
//登陆认证
try {
subject.login(token);
System.out.println("登陆成功");
} catch (UnknownAccountException e) {
System.out.println("账号出错...");
} catch(IncorrectCredentialsException e){
System.out.println("密码出错...");
}
System.out.println(subject.hasRole("role2"));
System.out.println(subject.hasRole("role3"));
System.out.println(subject.hasRole("role5"));
System.out.println(subject.isPermitted("user:create"));
System.out.println(subject.isPermitted("user:update"));
}
内置过滤器介绍
内置过滤器对应关系
内置过滤器说明
过滤器 | 过滤器 |
---|---|
anon | 例子/admins/** =anon #没有参数,表示可以匿名使用。 |
authc | 例如/admins/user/** =authc #表示需要认证(登录)才能使用,没有参数 |
roles | 例子/admins/user/**=roles[admin] , #参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如 admins/user/**=roles[“admin,guest”] , 每个参数通过才算通过,相当于hasAllRoles()方法。 |
perms | 例子/admins/user/**=perms[user:add:*] , #参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms[“user:add:*,user:modify:*”] ,当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。 |
rest | 例子/admins/user/**=rest[user] , #根据请求的方法,相当于/admins/user/ |
port | 例子/admins/user/**=port[8081] , #当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString ,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString是你访问的url里的?后面的参数。 |
authcBasic | 例如/admins/user/** =authcBasic #没有参数表示httpBasic认证 |
ssl | 例子/admins/user/** =ssl #没有参数,表示安全的url请求,协议为https |
user | 例如/admins/user/** =user #没有参数表示必须存在用户,当登入操作时不做检查 |
整合SSM
导包
<dependencies>
<!-- Spring相关jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>4.3.21.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.21.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.3</version>
</dependency>
<!-- SpringMVC相关依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.21.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<!-- mybatis依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<!-- mybatis和spring整合包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!-- mysql驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.43</version>
</dependency>
<!-- shiro相关的依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.3</version>
</dependency>
<!-- c3p0 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.3</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
</dependencies>
<build>
<!-- tomcat插件 -->
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<!-- 端口号 -->
<port>80</port>
<!-- /:表示访问路径 省略项目名 -->
<path>/</path>
<!-- 设置编码方式 -->
<uriEncoding>utf-8</uriEncoding>
</configuration>
</plugin>
</plugins>
</build>
配置文件
1.applicationContext-base
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 开启扫描 -->
<context:component-scan base-package="com.i.service.impl" use-default-filters="true">
<!-- 在Spring容器中,扫描除了Controller之外的其他组件 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 配置Mybatis接口扫描路径 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.i.mapper"/>
</bean>
<!-- 引入第三方属性文件 注意:在web项目中要加 " classpath: " -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置数据源 -->
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.name}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 整合Mybatis 当spring容器加载时 会同步加载mybatis中的SqlSessionFactory对象-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactoryBean">
<!-- 配置Mybatis的配置文件 如果是在web项目中 同样要加上 "classpath" -->
<property name="configLocation" value="classpath:mybatis-cfg.xml"/>
<!-- 配置关联数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- 配置别名 -->
<property name="typeAliasesPackage" value="com.i.pojo"/>
</bean>
</beans>
2.applicationContext-shiro
<?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 class="org.apache.shiro.authc.credential.HashedCredentialsMatcher" id="credentialsMatcher">
<!-- 设置加密的算法和迭代次数 -->
<property name="hashAlgorithmName" value="md5"/>
<property name="hashIterations" value="1024"/>
</bean>
<!-- 注册自定义Realm -->
<bean class="com.i.realm.MyRealm" id="myRealm">
<!-- 配置凭证匹配器 -->
<property name="credentialsMatcher" ref="credentialsMatcher"/>
</bean>
<!-- 注册SecurityManager -->
<bean class="org.apache.shiro.web.mgt.DefaultWebSecurityManager" id="securityManager">
<!-- 配置自定义Realm -->
<property name="realm" ref="myRealm"/>
</bean>
<!-- 注册ShiroFilterFactoryBean 注意id必须和web.xml中注册的targetBeanName的值一致 -->
<bean class="org.apache.shiro.spring.web.ShiroFilterFactoryBean" id="shiro">
<!-- 注册SecurityManager -->
<property name="securityManager" ref="securityManager"/>
<!-- 登陆地址 如果用户请求的地址是 login 那么会对该地址认证-->
<property name="loginUrl" value="/login"/>
<!-- 登陆成功的跳转地址 -->
<property name="successUrl" value="/success.jsp"/>
<!-- 访问未授权的页面跳转的地址 -->
<property name="unauthorizedUrl" value="/refuse.jsp"/>
<!-- 设置过滤器链 -->
<property name="filterChainDefinitions">
<value>
<!-- 加载顺序从上往下
authc:需要认证
anon:可以匿名访问的资源
-->
/login=authc
/login.jsp=anon
/**=authc
</value>
</property>
</bean>
</beans>
3.spring-mvc
<?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/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 开启扫描 -->
<context:component-scan base-package="com.i.controller" use-default-filters="false">
<!-- SpringMVC容器只扫描Controller -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 开启SpringMVC注解的使用 -->
<mvc:annotation-driven/>
<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 配置前后端 -->
<property name="suffix" value=".jsp"/>
</bean>
<!-- 配置静态资源放行 -->
<mvc:resources location="/js/" mapping="/js/**"></mvc:resources>
<mvc:resources location="/css/" mapping="/css/**"></mvc:resources>
<mvc:resources location="/images/" mapping="/images/**"></mvc:resources>
<!-- 配置异常处理bean -->
<bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!-- 根据servlet报的错,作为键名来匹配,找上了就跳对应的资源 -->
<prop key="java.lang.NullPointerException">error.jsp</prop>
</props>
</property>
</bean>
</beans>
4.web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<display-name>shiro-ssm</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<!-- 配置Spring配置文件的位置 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 前端控制器 -->
<servlet>
<servlet-name>spring-mvc</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>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring-mvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 编码格式过滤 -->
<filter>
<filter-name>character</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>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>character</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>shiro</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
5.mybatis-cfg
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
</configuration>
6.db.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/demo?useSSL=true&useUnicode=true&characterEncoding=utf-8
jdbc.name=root
jdbc.password=1234
7.log4j.properties
log4j.logger.com.i.mapper=DEBUG, CONSOLE,LOGFILE
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=-%c-%M-%d{yyyy-MM-dd HH:mm:ss,SSS}-%m%n
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=D:/axis.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=-%c-%M-%d{yyyy-MM-dd HH:mm:ss,SSS}-%m%n
完成realm的认证功能
//认证方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取账号信息
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
// 验证账号
List<User> list = service.login(username);
if(list == null && list.size() != 1){
return null;
}
User user = list.get(0);
// 验证密码
return new SimpleAuthenticationInfo(user, user.getPassword(), new SimpleByteSource(user.getSalt()), "MyRealm");
}
完成controller
@Controller
public class AuthController {
//设定登录失败跳转的资源以及获取失败的信息
@RequestMapping("/login")
public String login(Model m,HttpServletRequest request){
//获取认证失败的信息
Object obj = request.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME);
System.out.println("======="+obj);
return "/login";
}
}
前端页面
<h5>${msg}</h5>
<form action="login" method="post">
账号:<input type="text" name="username"><br>
密码:<input type="text" name="password"><br>
<input type="submit" value="提交">
</form>