Spring security:重量级安全框架
Apache shiro:轻量级安全框架
因为虽然重量级的功能更加强大,细粒度更高,但是它的学习难度相比较于轻量级的Shiro要高一些。虽然功能很强大,但是还是有很多地方用不到。
Shiro:粗粒度,学习难度低,功能够用。
身份验证(登录)
授权(权限判断)
密码学(加密)
会话管理(session:任何地方都可以使用)
宏观上来看Shiro:
使用Shiro,Subject代表当前用户。
Shiro SecurityManager:Shiro的权限管理器,Shiro的所有功能都要通过它,都是靠它来完成得分。
Realm:拿取数据全靠它。获取登录用户的信息和权限。
微观上来看Shiro:
Shiro什么语言都支持,是不是web项目也无所谓。
测试Shiro
1.拿到shiro.ini配置文件,并且拿出工厂
2、从工厂中获取到SecurityManager对象
3、把SecurityManager对象设置到上下文中
4、获取到当前用户(如果没有登录就是游客)
5、如果用户没有登录。我们就要让它登录
6、要想登录,就要先准备令牌:
7、根据令牌来实现登录。
8、退出系统。
public class HelloShiro {
@Test
public void myTest() throws Exception{
// 1、获取到最重要的那个对象,SecurityManager
// 2、读取到Shiro.ini的配置文件,并且拿到工厂
Factory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
// 从工厂里面拿到SecurityManager对象
SecurityManager securityManager = factory.getInstance();
// 把SecurityManager对象设置到上下文里面
// 将它放到一个地方,然后所有的位置都可以调用
SecurityUtils.setSecurityManager(securityManager);
// 获取到当前用户,没有登录那就是游客访问
Subject currentUser = SecurityUtils.getSubject();
System.out.println("是否登录成功:"+currentUser.isAuthenticated());
//如果用户没有登录,就要让他登录
if(!currentUser.isAuthenticated()){
try {
// 使用户登录,先准备令牌
UsernamePasswordToken token = new UsernamePasswordToken("guest", "guest");
// 根据令牌来实现登录
currentUser.login(token);
} catch (AuthenticationException e) {
e.printStackTrace();
System.out.println("出现了一个未知错误");
}
}
//角色判断
System.out.println("我是一个admin角色的人:"+currentUser.hasRole("admin"));
System.out.println("我是一个it角色的人:"+currentUser.hasRole("it"));
//权限判断
System.out.println("我有employee:save的权限:"+currentUser.isPermitted("employee:save"));
System.out.println("我有employee:delete的权限:"+currentUser.isPermitted("employee:delete"));
System.out.println("我有employee:update的权限:"+currentUser.isPermitted("employee:update"));
System.out.println("我有department:update的权限:"+currentUser.isPermitted("department:update"));
//System.out.println("是否登录成功:"+currentUser.isAuthenticated());
//退出系统
//currentUser.logout();
//System.out.println("是否登录成功:"+currentUser.isAuthenticated());
}
}
未知账号异常
UnknownAccountException
不正确密码异常
IncorrectCredentialsException
package cn.cxm.shiro;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import java.util.HashSet;
import java.util.Set;
//自定义Realm,按照自己想要的效果来完成功能
//自定义Realm一般直接继承AuthorizingRealm接口,因为里面包含了身份认证和授权两个方法
public class MyRealm extends AuthorizingRealm{
// 获取到这个Realm的名称
@Override
public String getName(){
return "MyRealm";
}
// 必须是登录成功进来的,拿到用户名,根据用户名去到数据库里面拿到对应的角色与权限
// 授权验证的方法
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//1.拿到当前用户名
// 拿出当前登录用户的主体,也就是用户名
String principal = (String) principalCollection.getPrimaryPrincipal();
//2.根据用户名拿到对应的角色与权限
Set roles = getRoles(principal);
Set perms = getPerms(principal);
//3.返回对应的对象
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(roles);
authorizationInfo.setStringPermissions(perms);
return authorizationInfo;
}
//模拟根据用户名拿到对应的角色与权限
private Set getRoles(String username){
Set roles = new HashSet();
roles.add("admin");
roles.add("it");
return roles;
}
private Set getPerms(String username){
Set perms = new HashSet();
perms.add("employee:save");
perms.add("employee:delete");
perms.add("employee:*");
perms.add("*");
return perms;
}
// 登录验证(身份验证)
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1.拿到令牌(用户名密码Token)
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
//2.拿到用户名,这里的用户名是传过来的
String username = token.getUsername();
//3.根据用户名到数据库进行查询
// 这个密码是数据库的密码
String password = getByName(username);
if(password==null){
//返回空就是代表用户名不存在,shior会自动帮我们报UnknownAccountException
return null;
}
// 在这里添加盐值,通过ByteSource来添加盐值
ByteSource salt = ByteSource.Util.bytes("cxm");
//4.我们把值传进去,它会自己帮我们判断密码
// 传的是数据库密码:它要把这个密码和令牌中的密码做对象,如果对应不上,就会报:IncorrectCredentialsException
// 将盐值添加到下面
SimpleAuthenticationInfo authenticationInfo
= new SimpleAuthenticationInfo(username,password,salt,getName());
return authenticationInfo;
}
//模拟根据当前登录用户拿到密码
private String getByName(String username){
if("root".equals(username)){
return "282e4ced1f2b6fe522107c5051d27450";
}else if("guest".equals(username)){
return "guest";
}
return null;
}
}
快捷键:Ctrl+T,可以看到子类
MD5加密
验证加密的密码:
使用加密凭证匹配器
// 设置Realm的密码匹配器
// new一个密码匹配器
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
// 定义密码算法
matcher.setHashAlgorithmName("MD5");
// 定义加密次数
matcher.setHashIterations(10);
myRealm.setCredentialsMatcher(matcher);
// 在这里我们没有办法添加盐值,需要到MyRealm里面才能添加
匹配器里面只有加密的方式和次数
盐值设置不设置早匹配器里面,而是设置在Realm里面。
// 在这里添加盐值,通过ByteSource来添加盐值
ByteSource salt = ByteSource.Util.bytes("cxm");
//4.我们把值传进去,它会自己帮我们判断密码
// 传的是数据库密码:它要把这个密码和令牌中的密码做对象,如果对应不上,就会报:IncorrectCredentialsException
// 将盐值添加到下面
SimpleAuthenticationInfo authenticationInfo
= new SimpleAuthenticationInfo(username,password,salt,getName());
return authenticationInfo;
把核心对象(SecurityManager)交给Spring创建,MyRealm也交给Spring创建。
将我们的aisell项目备份后导入,
然后导包。
org.apache.shiro
shiro-all
1.4.0
pom
org.apache.shiro
shiro-spring
1.4.0
Shiro的过滤器可以拦截所有的请求。
DelegatingFilterProxy,这个过滤器什么功能都没有,他什么也不做,真正的过滤功能是在Spring中的过滤器里面完成的。是J2EE的原生过滤器,它将代码拦截以后,交给Spring来处理。
shiroFilter
org.springframework.web.filter.DelegatingFilterProxy
targetFilterLifecycle
true
shiroFilter
/*
配置applicationContext-shiro.xml,并在applicationContext.xml里面读取shiro的配置文件。
配置applicationContext-shiro.xml
web.xml里面配置的shiro过滤器名字必须和applicationContext-shiro.xml里面的真正的shiro过滤器名字一样。
package cn.cxm.aisell.shiro;
import java.util.LinkedHashMap;
//这个方法是用来定义权限的,哪些路径放行,哪些路径不放行,在这里来写
public class FilterChainDefinitionMapBuilder {
//使用集合,将需要放行的路径,需要权限的路径,需要登录访问的路径放进去
public LinkedHashMap