(1)shiro概念
是一个强大且易用的java安全框架
(2)shiro应用程序的四大基石
身份验证(Authentication)、授权(Authorization)、密码学(Cryptography)、会话管理(Session Management)
(3)shiro的核心类是什么
SecurityManager
(4)Spring security和Apache Shiro两款安全框架的区别
Spring security: 重量级安全框架,功能更加强大,细粒度
Apache Shiro:轻量级安全框架,功能够用,粗粒度,可以通过自己控制粒度。
(5)Shiro如何实现盐值认证?
保存数据使用盐值加密保存
认证时使用盐值加密认证
两者必须加密次数和盐值一样
(6)shiro的架构:宏观上看
(7)shiro的架构:微观上看
(8)shiro的测试代码
准备好了shiro.ini文件
# -----------------------------------------------------------------------------
# 用户名:root 密码是:123456 它的角色是:admin
# 用户名:guest 密码是:guest 它的角色是:it
# -----------------------------------------------------------------------------
[users]
root = 123456, admin,it
guest = guest, guest
# -----------------------------------------------------------------------------
# admin拥有所有权限
# it拥有操作员工的所的权限
# guest拥有员工的所有权限
# -----------------------------------------------------------------------------
[roles]
admin = *
it = employee:*
guest = employee:save
测试类代码
package cn.itsource.shiro;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Test;
/*
* 用户的姓名、密码、角色和权限来自于shiro.ini文件中
* */
public class HelloShiro {
@Test
public void myTest() throws Exception{
//1.获取到最重要的securityManager核心对象
//(1).读取shiro.ini配置文件,并拿到工厂
Factory factory= new IniSecurityManagerFactory("classpath:shiro.ini");
//(2).从工厂中获取到securityManager对象
SecurityManager securityManager = factory.getInstance();
//2.将securityManager对象设置到上下文中。目的:将对象放到一个地方,以后所有位置都可以调用
SecurityUtils.setSecurityManager(securityManager);
//3.获取到当前用户(如果没有登录就是游客)---Subject:用户
Subject currentUser = SecurityUtils.getSubject();
//currentUser.isAuthenticated():boolean类型,判断是否登录
System.out.println("是否成功登录:"+currentUser.isAuthenticated());//false
//4.如果用户没有登录,就让用户登录
if(!currentUser.isAuthenticated()){//!false为true,执行
//登录过程会出现用户名和密码错误等异常,try/catcah一下
try {
//(1).登录之前,先准备令牌
UsernamePasswordToken token = new UsernamePasswordToken("root","123456");
//(2).根据令牌登录
currentUser.login(token);
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户名错误!!");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误!!");
}catch (AuthenticationException e){
e.printStackTrace();
System.out.println("未知错误!!!");
}
}
//角色判断。--判断用户是否有某些角色
System.out.println("我是一个admin角色的人:"+currentUser.hasRole("admin"));//true
System.out.println("我是一个guest角色的人:"+currentUser.hasRole("guest"));//false
//权限判断。--判断用户是否有某些权限
System.out.println("我有employee:save的权限:"+currentUser.isPermitted("employee:save"));//true
System.out.println("我有employee:delete的权限:"+currentUser.isPermitted("employee:delete"));//true
System.out.println("我有employee:update的权限:"+currentUser.isPermitted("employee:update"));//true
System.out.println("我有department:update的权限:"+currentUser.isPermitted("department:update"));//true
//上面拿到令牌登录后
System.out.println("是否成功登录:"+currentUser.isAuthenticated());//true
//退出系统
currentUser.logout();
System.out.println("是否成功登录:"+currentUser.isAuthenticated());//false
}
}
(9)自定义realm类测试
自定义的realm类—MyRealm
package cn.itsource.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。完成自己想要的功能
* 自定义类MyRealm继承抽象类AuthorizingRealm。
* 授权验证方法doGetAuthorizationInfo和登录验证的方法doGetAuthenticationInfo ,在其中写自己的功能
* 注意各自对应的:SimpleAuthorizationInfo 和 SimpleAuthenticationInfo
* */
public class MyRealm extends AuthorizingRealm {
//授权验证的方法部分
/*
* 注意:1.一定要是登录成功之后才会进入到此方法中来
* 2.拿到用户名(根据用户名去数据库中拿对应的角色与权限)
* */
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//1.拿到登录成功后的用户名(当前登录用户的主体)。---getPrimary(主要的)Principal(主要的)
//将object类型强转为string类型
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
//2.根据用户名拿到对应的角色和权限(此次也是模拟的)
Set roles = getRoles(primaryPrincipal);
Set perms = getPerms(primaryPrincipal);
//3.返回对应的对象
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setRoles(roles);
simpleAuthorizationInfo.setStringPermissions(perms);
return simpleAuthorizationInfo;
}
//模拟后台数据库的角色和权限
//模拟根据用户名拿到对应的角色
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.拿到用于登录的令牌。将authenticationToken强转为UsernamePasswordToken令牌---令牌token(有用户名和密码)
UsernamePasswordToken token=(UsernamePasswordToken)authenticationToken;
//2.根据令牌拿到用户名
String username = token.getUsername();
//3.根据用户名到数据库查用户的密码(此处是模拟的)
String password = getByName(username);//就是模拟的数据库中的密码
//若果查询的用户密码为空
if(password==null){
//用户名不存在,shior会自动报UnknownAccountException异常
return null;
}
//4.将从数据库中查询到的用户名和密码传进去,shiro会判断密码
//传的是数据库密码:它要把这个密码和令牌中的密码做对比,如果对应不上,就会报:IncorrectCredentialsException异常
//记住SimpleAuthenticationInfo()方法:需要的参数:用户名、密码、随意名字(保证以后不重复即可)
/**
* 参数含义:
* Object principal :主体(用户名)
* Object hashedCredentials :密码
* ByteSource credentialsSalt :盐值
* String realmName :realm名称
*/
//准备盐值--给密码加盐值
ByteSource salt = ByteSource.Util.bytes("itsource");
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, password,salt, "xx");
return authenticationInfo;
}
//Hash(散列):算法
/**
* 123 : 202cb962ac59075b964b07152d234b70
* 123 + 10次:5371007260db2b98e3f7402395c45f28
* 123+salt: 3eb58f2ed64a0508d792f44ed010c862
* 123+salt+10次:d5a3fedf6c59c2ecbe7f7a6c1a22da37
*/
//根据当前用户名查询到密码(模拟后台数据库)
private String getByName(String username){
//如果前台传过来的用户名和后台根据令牌拿到的用户名一致,就返回该用户的密码
if("admin".equals(username)){
return "d5a3fedf6c59c2ecbe7f7a6c1a22da37";
}else if("guest".equals(username)){
return "456";
}
//否则就返回空
return null;
}
}
测试类Test代码:
如果密码加盐加密。则需要在test测试类中拿到凭证适配器,然后设置加密方式和加密次数,而加盐只能在realm类中设置
package cn.itsource.shiro;
import org.apache.shiro.authc.AuthenticationException;
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.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
public class MyRealmTest {
@Test
public void myTest() throws Exception{
//1.获取到重要的对象。securityManager
//1.1 创建自己自定义的MyRealm
MyRealm myRealm=new MyRealm();
//如果密码是加密加盐值了
//告诉Shiro(Realm)判断密码使用MD5加密的方式判断
//HashedCredentials(凭证)Matcher(匹配器) 拿到凭证适配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//设置加密的方式
credentialsMatcher.setHashAlgorithmName("MD5");
//设置加密次数
credentialsMatcher.setHashIterations(10);
//将凭证适配器交给realm,然后它就知道怎么做了
myRealm.setCredentialsMatcher(credentialsMatcher);
//1.2 创建一个securityManager对象
DefaultSecurityManager securityManager=new DefaultSecurityManager();
//1.3 将自定义的realm和securityManager对象产生关系
securityManager.setRealm(myRealm);
//2.将securityManager对象放到上下文中
SecurityUtils.setSecurityManager(securityManager);
//3.拿到当前的用户(游客)
Subject subject = SecurityUtils.getSubject();
System.out.println("是否登录:"+subject.isAuthenticated());
//4.如果没有登录,给个令牌让他登录
if(!subject.isAuthenticated()){
try {
//4.1准备一个令牌
UsernamePasswordToken token = new UsernamePasswordToken("admin","123");
//4.2 登录
subject.login(token);
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("账号错误");
} catch (IncorrectCredentialsException e) {
e.printStackTrace();
System.out.println("密码错误");
} catch (AuthenticationException e) {
e.printStackTrace();
System.out.println("未知错误!");
}
}
System.out.println("是否登录:"+subject.isAuthenticated());
//角色判断。--判断用户是否有某些角色
System.out.println("我是一个admin嘛:"+subject.hasRole("admin"));
System.out.println("我是一个it嘛:"+subject.hasRole("it"));
System.out.println("我是一个guest嘛:"+subject.hasRole("guest"));
//权限判断。--判断用户是否有某些权限
System.out.println("我有employee:save的权限嘛:"+subject.isPermitted("employee:save"));
System.out.println("我有employee:delete的权限嘛:"+subject.isPermitted("employee:delete"));
System.out.println("我有dept:save的权限嘛:"+subject.isPermitted("dept:save"));
}
//Hash(散列):算法
/**
* 123 : 202cb962ac59075b964b07152d234b70
* 123 + 10次:5371007260db2b98e3f7402395c45f28
* 123+salt: 3eb58f2ed64a0508d792f44ed010c862
* 123+salt+10次:d5a3fedf6c59c2ecbe7f7a6c1a22da37
*/
@Test
public void testHash() throws Exception{
/**
* String algorithmName:加密算法
* Object source:原始密码
* Object salt:盐值
* hashIterations:加密次数
*/
SimpleHash hash = new SimpleHash("MD5","123",null,10);
System.out.println(hash);
}
}
项目基本上都是通过Spring来管理bean,要想使用shiro,就必须将shiro集成到Spring中。而集成Spring的核心就是将框架中的核心类:SecurityManager 、Subject 、 Realm 交给spring创建
总的来说;就是将核心对象SecurityManager 由spring创建、将自定义的realm也叫给spring创建。
集成步骤:
(1)导入相应的jar包
org.apache.shiro
shiro-all
1.4.0
pom
org.apache.shiro
shiro-spring
1.4.0
(2)在web.xml中配置shiro过滤器
shiroFilter
org.springframework.web.filter.DelegatingFilterProxy
targetFilterLifecycle
true
shiroFilter
/*
(3)配置spring和shiro集成的xml文件—applicationcontext-shiro.xml文件
(4)在applicationcontext.xml文件中读取shiro.xml文件
(5)自定义一个realm类
package cn.itsource.aisell.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并实现其抽象方法
*
* 此处模拟数据库中的密码又加盐又加值又加密。而此处在登录验证方法中只加了盐。
* 那么剩下的加值和加密则在applicationcontext-shiro.xml文件中去配置即可
* */
public class AiSellRealm extends AuthorizingRealm{
//名字随意取,只是realm的一个名字而已
@Override
public String getName() {
return "AiSellRealm";
}
//授权验证方法(必须在下面的登录验证方法成功之后才会来执行这里的代码)
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//1.拿到用户名--将object类型强转为string类型
String username =(String) principalCollection.getPrimaryPrincipal();
//2.根据用户名获取到用户的角色和权限
Set roles = getRoles(username);
Set perms = getPerms(username);
//3.给AuthorizationInfo对象设置角色和权限,并返回 使用SimpleAuthorizationInfo类
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:index");
perms.add("employee:*");
// perms.add("*");
return perms;
}
//登录验证方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1.拿到令牌。将authenticationToken令牌强转为usernamepasswordToken类型的令牌
UsernamePasswordToken token=(UsernamePasswordToken)authenticationToken;
//2.根据令牌拿到用户名
String username = token.getUsername();
//3.再根据用户名去数据库(模拟的数据库)拿到对应的密码
String password=getByName(username);
//4.判断如果用户名没有密码
if(password==null){
//返回空,代表登录失败,shiro会自动报UnknownAccountException异常
return null;
}
//5.准备好盐值
ByteSource salt = ByteSource.Util.bytes("itsource");
//6.返回AuthenticationInfo对象。通过SimpleAuthenticationInfo类,参数(用户名、密码、盐值、随意的realm名字)
//用户名与密码放进去-注:它会自动的比较获取的密码与你传过来的密码
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, password, salt, getName());
return authenticationInfo;
}
//根据当前登录用户拿到密码(模拟数据库)--456经过加盐遍历10此得到的加密密码42a02ed97752caf987d13d1a5cb53847
private String getByName(String username){
if("admin".equals(username)){
return "42a02ed97752caf987d13d1a5cb53847";
}else if("it".equals(username)){
return "42a02ed97752caf987d13d1a5cb53847";
}
return null;
}
}
(6)为了解决applicationcontext-shiro.xml配置文件中权限路径写死问题,单独写个java类
package cn.itsource.aisell.shiro;
import java.util.LinkedHashMap;
import java.util.Map;
/*
* 解决applicationcontext-shiro.xml配置文件中权限路径写死的问题
* */
public class FilterChainDefinitionMapBuilder {
/*
* 在原来applicationcontext-shiro.xml文件中配置的写死了的权限路径
* /jsp/login.jsp = anon
* /login = anon
* /employee/index = perms[employee:index]
* /dept/index = perms[dept:index]
* /** = authc--有顺序要求,这个一定要放在最后面
*
* key/value的结构,必须要有顺序。所以将权限装进LinkedHashMap里面
* LinkedHashMap:代表有顺序的
* */
public Map createFilterChainDefinitionMap(){
//创建一个有顺序的map集合出来装权限路径
Map filterChainDefinitionMap = new LinkedHashMap<>();
//设置不需要权限也能访问的路径
filterChainDefinitionMap.put("/jsp/login.jsp", "anon");
filterChainDefinitionMap.put("/login", "anon");
//设置需要权限访问的路径
// filterChainDefinitionMap.put("/employee/index","perms[employee:index]");
filterChainDefinitionMap.put("/dept/index","perms[dept:index]");
//设置需要登录才能访问---这个一定要放在最后面
filterChainDefinitionMap.put("/**","authc");
return filterChainDefinitionMap;
}
}
(7)登录的controller层
package cn.itsource.aisell.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class LoginController {
@RequestMapping("/login")
public String login(String username,String password){
//当前台登录页面输入数据时会提交到后台来
// System.out.println(username+":"+password);
/*
* 已经叫给spring去管理shiro了,所以此处就不用再创建核心对象securityManager了,
* 也不需要拿到凭证适配器、设置加密方式、设置加密次数了,同样也不用拿自定义的realm类和
* 核心对象securityManager发生关系,也不用将核心对象securityManager放到上下文中去
* */
//1.直接拿到当前的用户
Subject currentName = SecurityUtils.getSubject();
//2.如果当前用户没有登录,就给他个令牌叫他登录
//currentName.isAuthenticated():布尔类型。false表示没有登录,反之则登录
if(!currentName.isAuthenticated()){
try {
//3.给个令牌,将前台传过来的参数传进去
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//4.用户登录
currentName.login(token);
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户名错误");
}catch (IncorrectCredentialsException e) {
e.printStackTrace();
System.out.println("密码错误");
}catch (AuthenticationException e) {
e.printStackTrace();
System.out.println("未知错误");
}
}
//成功后直接跳转到主页面。用redirect跳转,就不受mvc视图解析配置(自动生成路径)的影响了
return "redirect:/jsp/main.jsp";
}
//用户名登录注销
@RequestMapping("/logout")
public String logout() {
//直接拿到用户名
Subject subject = SecurityUtils.getSubject();
//注销登录
subject.logout();
//注销后直接跳转到登录页面
return "redirect:/s/login.jsp";
}
}
(8)相关的简单的jsp页面
login.jsp登录页面
<%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2019/3/19
Time: 22:07
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
Title
main.jsp主页面
<%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2019/3/19
Time: 22:08
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
Title
主页面 注销
unauthorized.jsp没有权限的页面
<%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2019/3/19
Time: 22:08
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
Title
没有权限